enhanced scheduler to support one-time jobs, fixed pager component so that top/bottom have consistent UX, fixed Blazor theme z-index issues caused by input-group in Bootstrap 5, improved password reset instructions in email notification
This commit is contained in:
@ -38,6 +38,7 @@
|
|||||||
<option value="d">@Localizer["Day(s)"]</option>
|
<option value="d">@Localizer["Day(s)"]</option>
|
||||||
<option value="w">@Localizer["Week(s)"]</option>
|
<option value="w">@Localizer["Week(s)"]</option>
|
||||||
<option value="M">@Localizer["Month(s)"]</option>
|
<option value="M">@Localizer["Month(s)"]</option>
|
||||||
|
<option value="O">@Localizer["Once"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,82 +97,89 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private int _jobId;
|
private int _jobId;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _jobType = string.Empty;
|
private string _jobType = string.Empty;
|
||||||
private string _isEnabled = "True";
|
private string _isEnabled = "True";
|
||||||
private string _interval = string.Empty;
|
private string _interval = string.Empty;
|
||||||
private string _frequency = string.Empty;
|
private string _frequency = string.Empty;
|
||||||
private DateTime? _startDate = null;
|
private DateTime? _startDate = null;
|
||||||
private string _startTime = string.Empty;
|
private string _startTime = string.Empty;
|
||||||
private DateTime? _endDate = null;
|
private DateTime? _endDate = null;
|
||||||
private string _endTime = string.Empty;
|
private string _endTime = string.Empty;
|
||||||
private string _retentionHistory = string.Empty;
|
private string _retentionHistory = string.Empty;
|
||||||
private DateTime? _nextDate = null;
|
private DateTime? _nextDate = null;
|
||||||
private string _nextTime = string.Empty;
|
private string _nextTime = string.Empty;
|
||||||
private string createdby;
|
private string createdby;
|
||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
Job job = await JobService.GetJobAsync(_jobId);
|
Job job = await JobService.GetJobAsync(_jobId);
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
_name = job.Name;
|
_name = job.Name;
|
||||||
_jobType = job.JobType;
|
_jobType = job.JobType;
|
||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
_interval = job.Interval.ToString();
|
_interval = job.Interval.ToString();
|
||||||
_frequency = job.Frequency;
|
_frequency = job.Frequency;
|
||||||
_startDate = job.StartDate;
|
_startDate = job.StartDate;
|
||||||
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
|
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
|
||||||
{
|
{
|
||||||
_startTime = job.StartDate.Value.ToString("HH:mm");
|
_startTime = job.StartDate.Value.ToString("HH:mm");
|
||||||
}
|
}
|
||||||
_endDate = job.EndDate;
|
_endDate = job.EndDate;
|
||||||
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
|
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
|
||||||
{
|
{
|
||||||
_endTime = job.EndDate.Value.ToString("HH:mm");
|
_endTime = job.EndDate.Value.ToString("HH:mm");
|
||||||
}
|
}
|
||||||
_retentionHistory = job.RetentionHistory.ToString();
|
_retentionHistory = job.RetentionHistory.ToString();
|
||||||
_nextDate = job.NextExecution;
|
_nextDate = job.NextExecution;
|
||||||
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
|
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
|
||||||
{
|
{
|
||||||
_nextTime = job.NextExecution.Value.ToString("HH:mm");
|
_nextTime = job.NextExecution.Value.ToString("HH:mm");
|
||||||
}
|
}
|
||||||
createdby = job.CreatedBy;
|
createdby = job.CreatedBy;
|
||||||
createdon = job.CreatedOn;
|
createdon = job.CreatedOn;
|
||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
modifiedon = job.ModifiedOn;
|
modifiedon = job.ModifiedOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
var job = await JobService.GetJobAsync(_jobId);
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
job.Name = _name;
|
job.Name = _name;
|
||||||
job.JobType = _jobType;
|
job.JobType = _jobType;
|
||||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
job.IsEnabled = Boolean.Parse(_isEnabled);
|
||||||
job.Frequency = _frequency;
|
job.Frequency = _frequency;
|
||||||
job.Interval = int.Parse(_interval);
|
if (job.Frequency == "O") // once
|
||||||
|
{
|
||||||
|
job.Interval = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.Interval = int.Parse(_interval);
|
||||||
|
}
|
||||||
job.StartDate = _startDate;
|
job.StartDate = _startDate;
|
||||||
if (job.StartDate != null)
|
if (job.StartDate != null)
|
||||||
{
|
{
|
||||||
|
@ -49,60 +49,62 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Job> _jobs;
|
private List<Job> _jobs;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
_jobs = await JobService.GetJobsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
||||||
{
|
{
|
||||||
var status = string.Empty;
|
var status = string.Empty;
|
||||||
if (!isEnabled)
|
if (!isEnabled)
|
||||||
{
|
{
|
||||||
status = Localizer["Disabled"];
|
status = Localizer["Disabled"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (isExecuting)
|
if (isExecuting)
|
||||||
{
|
{
|
||||||
status = Localizer["Executing"];
|
status = Localizer["Executing"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status = Localizer["Idle"];
|
status = Localizer["Idle"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string DisplayFrequency(int interval, string frequency)
|
private string DisplayFrequency(int interval, string frequency)
|
||||||
{
|
{
|
||||||
var result = $"{Localizer["Every"]} {interval.ToString()} ";
|
var result = "";
|
||||||
switch (frequency)
|
switch (frequency)
|
||||||
{
|
{
|
||||||
case "m":
|
case "m":
|
||||||
result += Localizer["Minute"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Minute"];
|
||||||
break;
|
break;
|
||||||
case "H":
|
case "H":
|
||||||
result += Localizer["Hour"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Hour"];
|
||||||
break;
|
break;
|
||||||
case "d":
|
case "d":
|
||||||
result += Localizer["Day"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Day"];
|
||||||
break;
|
break;
|
||||||
case "w":
|
case "w":
|
||||||
result += Localizer["Week"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Week"];
|
||||||
break;
|
break;
|
||||||
case "M":
|
case "M":
|
||||||
result += Localizer["Month"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Month"];
|
||||||
break;
|
break;
|
||||||
}
|
case "O":
|
||||||
|
result = Localizer["Once"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
|
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
|
||||||
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required />
|
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required />
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
@ -107,8 +107,8 @@
|
|||||||
<li class="page-item@((_page > 1) ? "" : " disabled")">
|
<li class="page-item@((_page > 1) ? "" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
|
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@ -135,7 +135,7 @@
|
|||||||
<li class="page-item@((_page < _pages) ? "" : " disabled")">
|
<li class="page-item@((_page < _pages) ? "" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
|
<li class="page-item@((_endPage < _pages) ? "" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
@ -145,7 +145,7 @@
|
|||||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link">Page @_page of @_pages</a>
|
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
@ -189,4 +189,7 @@
|
|||||||
<data name="Week(s)" xml:space="preserve">
|
<data name="Week(s)" xml:space="preserve">
|
||||||
<value>Week(s)</value>
|
<value>Week(s)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Once" xml:space="preserve">
|
||||||
|
<value>Execute Once</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -189,4 +189,7 @@
|
|||||||
<data name="Week" xml:space="preserve">
|
<data name="Week" xml:space="preserve">
|
||||||
<value>Week(s)</value>
|
<value>Week(s)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Once" xml:space="preserve">
|
||||||
|
<value>Execute Once</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -210,7 +210,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
|
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
|
||||||
@((MarkupString) Message)
|
@((MarkupString) Message)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,6 @@ using Oqtane.Extensions;
|
|||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||||
|
@ -389,7 +389,7 @@ namespace Oqtane.Controllers
|
|||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
if (identityuser != null)
|
if (identityuser != null && !string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
@ -398,13 +398,13 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", user.Username);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username} - Error {Error}", user.Username, result.Errors.ToString());
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", user.Username);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}And Token {Token}", user.Username, token);
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,9 +420,14 @@ namespace Oqtane.Controllers
|
|||||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
if (identityuser != null)
|
if (identityuser != null)
|
||||||
{
|
{
|
||||||
|
user = _users.GetUser(user.Username);
|
||||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||||
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!";
|
string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
|
||||||
|
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
||||||
|
"\n\nIf you did not request to reset your password you can safely ignore this message." +
|
||||||
|
"\n\nThank You!";
|
||||||
|
|
||||||
var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null);
|
var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null);
|
||||||
_notifications.AddNotification(notification);
|
_notifications.AddNotification(notification);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
|
||||||
@ -451,13 +456,13 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", user.Username);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username} - Error {Error}", user.Username, result.Errors.ToString());
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", user.Username);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username} And Token {Token}", user.Username, token);
|
||||||
user = null;
|
user = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,11 @@ namespace Oqtane.Infrastructure
|
|||||||
|
|
||||||
// update the job
|
// update the job
|
||||||
job.NextExecution = CalculateNextExecution(NextExecution, job);
|
job.NextExecution = CalculateNextExecution(NextExecution, job);
|
||||||
|
if (job.Frequency == "O") // one time
|
||||||
|
{
|
||||||
|
job.EndDate = DateTime.UtcNow;
|
||||||
|
job.NextExecution = null;
|
||||||
|
}
|
||||||
job.IsExecuting = false;
|
job.IsExecuting = false;
|
||||||
jobs.UpdateJob(job);
|
jobs.UpdateJob(job);
|
||||||
|
|
||||||
@ -174,6 +179,8 @@ namespace Oqtane.Infrastructure
|
|||||||
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
|
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "O": // one time
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (nextExecution < DateTime.UtcNow)
|
if (nextExecution < DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
left: 275px;
|
left: 275px;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
z-index: 3
|
z-index: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@ -145,13 +145,13 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1
|
z-index: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .top-row {
|
.main .top-row {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 2
|
z-index: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
.main > div {
|
.main > div {
|
||||||
@ -207,7 +207,7 @@
|
|||||||
top: 150px;
|
top: 150px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
|
Reference in New Issue
Block a user