Merge pull request #5119 from zyhfish/task/fix-4936

update the cookie consent control.
This commit is contained in:
Shaun Walker 2025-02-28 10:48:21 -05:00 committed by GitHub
commit 6a2ae2153a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 421 additions and 65 deletions

View File

@ -114,6 +114,16 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookieconsent" HelpText="Specify if cookie consent is enabled on this site. Please make sure your using theme supports Cookie Consent when enable this option." ResourceKey="CookieConsent">Cookie Consent: </Label>
<div class="col-sm-9">
<select id="cookieconsent" class="form-select" @bind="@_cookieconsent">
<option value="">@SharedLocalizer["Disabled"]</option>
<option value="optin">@Localizer["OptIn"]</option>
<option value="optout">@Localizer["OptOut"]</option>
</select>
</div>
</div>
</div>
</Section>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
@ -419,6 +429,7 @@
private string _themetype = "";
private string _containertype = "";
private string _admincontainertype = "";
private string _cookieconsent = "";
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
private string _textEditor = "";
@ -509,6 +520,7 @@
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
// functionality
var textEditors = ServiceProvider.GetServices<ITextEditor>();
@ -720,6 +732,9 @@
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
//cookie consent
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
// functionality
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);

View File

@ -426,6 +426,17 @@
<data name="System" xml:space="preserve">
<value>System</value>
</data>
<data name="CookieConsent.HelpText" xml:space="preserve">
<value>Specify if cookie consent is enabled on this site. Please make sure your using theme supports Cookie Consent when enable this option.</value>
</data>
<data name="CookieConsent.Text" xml:space="preserve">
<value>Cookie Consent:</value>
</data>
<data name="OptIn" xml:space="preserve">
<value>Opt-In</value>
</data>
<data name="OptOut" xml:space="preserve">
<value>Opt-Out</value>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme</value>
</data>

View File

@ -117,16 +117,13 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConsentBody" xml:space="preserve">
<value>
&lt;div class="gdpr-consent-bar bg-light text-dark p-3 fixed-bottom"&gt;
&lt;div class="container-fluid d-flex justify-content-between align-items-center"&gt;
&lt;div&gt;
By clicking "Accept", you agree us to use cookies to ensure you get the best experience on our website.
&lt;/div&gt;
&lt;button class="btn btn-primary" type="submit"&gt;Accept&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
</value>
<data name="Confirm" xml:space="preserve">
<value>Confirm</value>
</data>
<data name="ConsentDescription" xml:space="preserve">
<value>I agree to using cookies to provide the best user experience for this site.</value>
</data>
<data name="Privacy" xml:space="preserve">
<value>Privacy</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

View File

@ -16,10 +16,20 @@ namespace Oqtane.Services
private string ApiUrl => CreateApiUrl("CookieConsent");
/// <inheritdoc />
public async Task<bool> CanTrackAsync()
public async Task<bool> IsActionedAsync()
{
return await GetJsonAsync<bool>($"{ApiUrl}/CanTrack");
return await GetJsonAsync<bool>($"{ApiUrl}/IsActioned");
}
public async Task<bool> CanTrackAsync(bool optOut)
{
return await GetJsonAsync<bool>($"{ApiUrl}/CanTrack?optout=" + optOut);
}
public async Task<string> CreateActionedCookieAsync()
{
var cookie = await GetStringAsync($"{ApiUrl}/CreateActionedCookie");
return cookie ?? string.Empty;
}
public async Task<string> CreateConsentCookieAsync()
@ -27,5 +37,11 @@ namespace Oqtane.Services
var cookie = await GetStringAsync($"{ApiUrl}/CreateConsentCookie");
return cookie ?? string.Empty;
}
public async Task<string> WithdrawConsentCookieAsync()
{
var cookie = await GetStringAsync($"{ApiUrl}/WithdrawConsentCookie");
return cookie ?? string.Empty;
}
}
}

View File

@ -9,16 +9,34 @@ namespace Oqtane.Services
/// </summary>
public interface ICookieConsentService
{
/// <summary>
/// Get cookie consent bar actioned status
/// </summary>
/// <returns></returns>
Task<bool> IsActionedAsync();
/// <summary>
/// Get cookie consent status
/// </summary>
/// <returns></returns>
Task<bool> CanTrackAsync();
Task<bool> CanTrackAsync(bool optOut);
/// <summary>
/// Grant cookie consent
/// create actioned cookie
/// </summary>
/// <returns></returns>
Task<string> CreateActionedCookieAsync();
/// <summary>
/// create consent cookie
/// </summary>
/// <returns></returns>
Task<string> CreateConsentCookieAsync();
/// <summary>
/// widhdraw consent cookie
/// </summary>
/// <returns></returns>
Task<string> WithdrawConsentCookieAsync();
}
}

View File

@ -1,42 +1,155 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject ISettingService SettingService
@inject ICookieConsentService CookieConsentService
@inject IStringLocalizer<CookieConsent> Localizer
@if (showBanner)
@if (_enabled && !Hidden)
{
<form method="post" @formname="CookieConsentForm" @onsubmit="async () => await AcceptPolicy()" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@if (ChildContent != null)
{
@ChildContent
}
else
{
@((MarkupString)Convert.ToString(Localizer["ConsentBody"]))
}
</form>
<div class="gdpr-consent-bar bg-light text-dark @(_showBanner ? "p-3" : "p-0") pe-5 fixed-bottom">
<form method="post" @formname="CookieConsentForm" @onsubmit="async () => await AcceptPolicy()" data-enhance>
@if (_showBanner)
{
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<div class="container-fluid d-flex justify-content-between align-items-center">
<div>
@((MarkupString)Convert.ToString(Localizer["ConsentDescription"]))
@if (PageState.RenderMode == RenderModes.Static)
{
<input type="checkbox" name="cantrack" checked="@_canTrack" value="1" class="form-check-input ms-2" />
}
else
{
<input type="checkbox" name="cantrack" @bind="@_canTrack" value="1" class="form-check-input ms-2" />
}
</div>
<div>
<button class="btn btn-primary" type="submit">@((MarkupString)Convert.ToString(Localizer["Confirm"]))</button>
@if (ShowPrivacyLink)
{
<a class="btn btn-secondary ms-2" href="/privacy" target="_blank">@((MarkupString)Convert.ToString(Localizer["Privacy"]))</a>
}
</div>
</div>
}
</form>
<form method="post" @formname="CookieConsentToggleForm" @onsubmit="async () => await ToggleBanner()" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@if(_showBanner)
{
<input type="hidden" name="showbanner" value="false" />
<button type="submit" class="btn btn-light text-dark btn-sm position-absolute btn-hide">
<i class="oi oi-chevron-bottom"></i>
</button>
}
else
{
<input type="hidden" name="showbanner" value="true" />
<button type="submit" class="btn btn-light text-dark btn-sm position-absolute btn-show">
<i class="oi oi-chevron-top"></i>
</button>
}
</form>
</div>
}
@code {
private bool showBanner;
private bool _showBanner;
private bool _enabled;
private bool _optout;
private bool _actioned;
private bool _canTrack;
private bool _consentPostback;
private bool _togglePostback;
[Parameter]
public RenderFragment ChildContent { get; set; } = null;
public bool Hidden { get; set; }
[Parameter]
public bool ShowPrivacyLink { get; set; } = true;
[SupplyParameterFromForm(FormName = "CookieConsentToggleForm")]
public string ShowBanner {
get => "";
set
{
_showBanner = bool.Parse(value);
_togglePostback = true;
}
}
[SupplyParameterFromForm(FormName = "CookieConsentForm")]
public string CanTrack
{
get => "";
set
{
_canTrack = !string.IsNullOrEmpty(value);
_consentPostback = true;
}
}
protected override async Task OnInitializedAsync()
{
showBanner = !(await CookieConsentService.CanTrackAsync());
var cookieConsentSetting = SettingService.GetSetting(PageState.Site.Settings, "CookieConsent", string.Empty);
_enabled = !string.IsNullOrEmpty(cookieConsentSetting);
_optout = cookieConsentSetting == "optout";
_actioned = await CookieConsentService.IsActionedAsync();
if (!_consentPostback)
{
_canTrack = await CookieConsentService.CanTrackAsync(_optout);
}
if(!_togglePostback)
{
_showBanner = !_actioned;
}
}
private async Task AcceptPolicy()
{
var cookieString = await CookieConsentService.CreateConsentCookieAsync();
var cookieString = string.Empty;
if(_optout)
{
cookieString = _canTrack ? await CookieConsentService.WithdrawConsentCookieAsync() : await CookieConsentService.CreateConsentCookieAsync();
}
else
{
cookieString = _canTrack ? await CookieConsentService.CreateConsentCookieAsync() : await CookieConsentService.WithdrawConsentCookieAsync();
}
if (!string.IsNullOrEmpty(cookieString))
{
var interop = new Interop(JSRuntime);
await interop.SetCookieString(cookieString);
showBanner = false;
_actioned = true;
_showBanner = false;
StateHasChanged();
}
}
private async Task ToggleBanner()
{
if (!_actioned)
{
var cookieString = await CookieConsentService.CreateActionedCookieAsync();
if (!string.IsNullOrEmpty(cookieString))
{
var interop = new Interop(JSRuntime);
await interop.SetCookieString(cookieString);
_actioned = true;
}
}
if(PageState.RenderMode == RenderModes.Interactive)
{
_showBanner = !_showBanner;
StateHasChanged();
}
}
}

View File

@ -22,7 +22,7 @@
</div>
</div>
</div>
<Pane Name="Top Full Width" />
<Pane Name="Top Full Width" />
<div class="container">
<div class="row">
<div class="col-md-12">
@ -108,13 +108,13 @@
<Pane Name="Footer" />
}
<CookieConsent />
</div>
</div>
</main>
@code {
public override string Name => "Default Theme";
public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
private bool _login = true;
private bool _register = true;

View File

@ -13,9 +13,9 @@
<select id="scope" class="form-select" value="@_scope" @onchange="(e => ScopeChanged(e))">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<option value="site">@Localizer["Site"]</option>
<option value="site">@Localizer["Site"]</option>
}
<option value="page">@Localizer["Page"]</option>
<option value="page">@Localizer["Page"]</option>
</select>
</div>
</div>
@ -92,7 +92,7 @@
settings = SettingService.MergeSettings(PageState.Site.Settings, settings);
_login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-");
_register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-");
_footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-");
_footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-");
}
await Task.Yield();
}

View File

@ -27,6 +27,7 @@ namespace Oqtane.UI
public bool IsInternalNavigation { get; set; }
public Guid RenderId { get; set; }
public bool Refresh { get; set; }
public bool AllowCookies { get; set; }
public List<Page> Pages
{

View File

@ -31,6 +31,8 @@
@inject IUrlMappingRepository UrlMappingRepository
@inject IVisitorRepository VisitorRepository
@inject IJwtManager JwtManager
@inject ICookieConsentService CookieConsentService
@inject ISettingService SettingService
@if (_initialized)
{
@ -107,6 +109,7 @@
private string _styleSheets = "";
private string _scripts = "";
private string _message = "";
private bool _allowCookies;
private PageState _pageState;
// CascadingParameter is required to access HttpContext
@ -140,6 +143,9 @@
_prerender = site.Prerender;
_fingerprint = site.Fingerprint;
var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty);
_allowCookies = string.IsNullOrEmpty(cookieConsentSettings) || await CookieConsentService.CanTrackAsync(cookieConsentSettings == "optout");
var modules = new List<Module>();
Route route = new Route(url, alias.Path);
@ -170,7 +176,7 @@
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
}
if (site.VisitorTracking)
if (site.VisitorTracking && _allowCookies)
{
TrackVisitor(site.SiteId);
}
@ -245,7 +251,8 @@
ReturnUrl = "",
IsInternalNavigation = false,
RenderId = Guid.NewGuid(),
Refresh = true
Refresh = true,
AllowCookies = _allowCookies
};
}
else

View File

@ -19,10 +19,22 @@ namespace Oqtane.Controllers
_cookieConsentService = cookieConsentService;
}
[HttpGet("CanTrack")]
public async Task<bool> CanTrack()
[HttpGet("IsActioned")]
public async Task<bool> IsActioned()
{
return await _cookieConsentService.CanTrackAsync();
return await _cookieConsentService.IsActionedAsync();
}
[HttpGet("CanTrack")]
public async Task<bool> CanTrack(string optout)
{
return await _cookieConsentService.CanTrackAsync(bool.Parse(optout));
}
[HttpGet("CreateActionedCookie")]
public async Task<string> CreateActionedCookie()
{
return await _cookieConsentService.CreateActionedCookieAsync();
}
[HttpGet("CreateConsentCookie")]
@ -30,5 +42,11 @@ namespace Oqtane.Controllers
{
return await _cookieConsentService.CreateConsentCookieAsync();
}
[HttpGet("WithdrawConsentCookie")]
public async Task<string> WithdrawConsentCookie()
{
return await _cookieConsentService.WithdrawConsentCookieAsync();
}
}
}

View File

@ -55,6 +55,5 @@ namespace Oqtane.Extensions
public static IApplicationBuilder UseExceptionMiddleWare(this IApplicationBuilder builder)
=> builder.UseMiddleware<ExceptionMiddleware>();
}
}

View File

@ -169,6 +169,33 @@ namespace Oqtane.SiteTemplates
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Privacy",
Parent = "",
Path = "privacy",
Icon = Icons.Eye,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Privacy Policy", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>By using our website, you agree to this privacy policy. We value your privacy and are committed to protecting your personal information. This policy outlines how we collect, use, and safeguard your data when you visit our website or use our services.</p>"
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Not Found",

View File

@ -75,6 +75,9 @@ namespace Oqtane.Infrastructure
case "6.1.0":
Upgrade_6_1_0(tenant, scope);
break;
case "6.1.1":
Upgrade_6_1_1(tenant, scope);
break;
}
}
}
@ -457,6 +460,41 @@ namespace Oqtane.Infrastructure
RemoveAssemblies(tenant, assemblies, "6.1.0");
}
private void Upgrade_6_1_1(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>
{
new PageTemplate
{
Name = "Privacy",
Parent = "",
Path = "privacy",
Icon = Icons.Eye,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Privacy Policy", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>By using our website, you agree to this privacy policy. We value your privacy and are committed to protecting your personal information. This policy outlines how we collect, use, and safeguard your data when you visit our website or use our services.</p>"
}
}
}
};
AddPagesToSites(scope, tenant, pageTemplates);
}
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
{
var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>();

View File

@ -1,10 +1,13 @@
using System;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using Oqtane.Documentation;
using Oqtane.Shared;
namespace Oqtane.Services
{
@ -12,27 +15,106 @@ namespace Oqtane.Services
public class ServerCookieConsentService : ICookieConsentService
{
private readonly IHttpContextAccessor _accessor;
private readonly CookiePolicyOptions _cookiePolicyOptions;
public ServerCookieConsentService(IHttpContextAccessor accessor)
public ServerCookieConsentService(IHttpContextAccessor accessor, IOptions<CookiePolicyOptions> cookiePolicyOptions)
{
_accessor = accessor;
_cookiePolicyOptions = cookiePolicyOptions.Value;
}
public Task<bool> CanTrackAsync()
public Task<bool> IsActionedAsync()
{
var consentFeature = _accessor.HttpContext?.Features.Get<ITrackingConsentFeature>();
var canTrack = consentFeature?.CanTrack ?? true;
var actioned = false;
if (_accessor.HttpContext != null)
{
var cookieValue = GetCookieValue("actioned");
actioned = cookieValue == Constants.CookieConsentActionCookieValue;
}
return Task.FromResult(actioned);
}
public Task<bool> CanTrackAsync(bool optOut)
{
var canTrack = true;
if (_accessor.HttpContext != null)
{
var cookieValue = GetCookieValue("consent");
var saved = cookieValue == Constants.CookieConsentCookieValue;
if (optOut)
{
canTrack = string.IsNullOrEmpty(cookieValue) || !saved;
}
else
{
canTrack = cookieValue == Constants.CookieConsentCookieValue;
}
}
return Task.FromResult(canTrack);
}
public Task<string> CreateActionedCookieAsync()
{
var cookieString = CreateCookieString(false, string.Empty);
return Task.FromResult(cookieString);
}
public Task<string> CreateConsentCookieAsync()
{
var consentFeature = _accessor.HttpContext?.Features.Get<ITrackingConsentFeature>();
consentFeature?.GrantConsent();
var cookie = consentFeature?.CreateConsentCookie() ?? string.Empty;
var cookieString = CreateCookieString(true, Constants.CookieConsentCookieValue);
return Task.FromResult(cookieString);
}
return Task.FromResult(cookie);
public Task<string> WithdrawConsentCookieAsync()
{
var cookieString = CreateCookieString(true, string.Empty);
return Task.FromResult(cookieString);
}
private string GetCookieValue(string type)
{
var cookieValue = string.Empty;
if (_accessor.HttpContext != null)
{
var value = _accessor.HttpContext.Request.Cookies[Constants.CookieConsentCookieName];
var index = type == "actioned" ? 1 : 0;
cookieValue = !string.IsNullOrEmpty(value) && value.Contains("|") ? value.Split('|')[index] : string.Empty;
}
return cookieValue;
}
private string CreateCookieString(bool saved, string savedValue)
{
var cookieString = string.Empty;
if (_accessor.HttpContext != null)
{
var savedCookie = saved ? savedValue : GetCookieValue("consent");
var actionedCookie = Constants.CookieConsentActionCookieValue;
var cookieValue = $"{savedCookie}|{actionedCookie}";
var options = _cookiePolicyOptions.ConsentCookie.Build(_accessor.HttpContext);
if (!_accessor.HttpContext.Response.HasStarted)
{
_accessor.HttpContext.Response.Cookies.Append(
Constants.CookieConsentCookieName,
cookieValue,
new CookieOptions()
{
Expires = options.Expires,
IsEssential = true,
SameSite = options.SameSite,
Secure = options.Secure
}
);
}
//get the cookie string from response header
cookieString = options.CreateCookieHeader(Constants.CookieConsentCookieName, Uri.EscapeDataString(cookieValue)).ToString();
}
return cookieString;
}
}
}

View File

@ -169,13 +169,6 @@ namespace Oqtane
options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type
});
services.TryAddSwagger(_useSwagger);
//add cookie consent policy
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.ConsentCookieValue = Constants.CookieConsentCookieValue;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -232,7 +225,6 @@ namespace Oqtane
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseCookiePolicy();
if (_useSwagger)
{

View File

@ -121,6 +121,16 @@
color: white;
}
/* cookie consent */
.gdpr-consent-bar .btn-show{
bottom: 0;
right: 5px;
}
.gdpr-consent-bar .btn-hide{
top: 0;
right: 5px;
}
@media (max-width: 767.98px) {
.main .top-row {
display: none;

View File

@ -100,6 +100,16 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu
z-index: 1000;
}
/* cookie consent */
.gdpr-consent-bar .btn-show{
bottom: 0;
right: 5px;
}
.gdpr-consent-bar .btn-hide{
top: 0;
right: 5px;
}
@media (max-width: 767.98px) {
.app-menu {

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{
public class Constants
{
public static readonly string Version = "6.1.0";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0";
public static readonly string Version = "6.1.1";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1";
public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater";
@ -91,7 +91,9 @@ namespace Oqtane.Shared
public const string BootstrapStylesheetUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css";
public const string BootstrapStylesheetIntegrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==";
public const string CookieConsentCookieValue = "true";
public const string CookieConsentCookieName = "Oqtane.CookieConsent";
public const string CookieConsentCookieValue = "yes";
public const string CookieConsentActionCookieValue = "yes";
// Obsolete constants
const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames";