< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_EmailRoutingScheduler_Index
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/EmailRoutingScheduler/Index.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 21
Coverable lines: 21
Total lines: 207
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 72
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()0%930300%
<ExecuteAsync()0%1482380%
<ExecuteAsync()100%210%
<ExecuteAsync()100%210%
<ExecuteAsync()0%2040%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/EmailRoutingScheduler/Index.cshtml

#LineLine coverage
 1@model OzonOrdersWeb.Areas.Studio2.ViewModels.EmailRouting.EmailRoutingSchedulerViewModel
 2
 3@{
 04    ViewData["Title"] = "Планировщик импорта из почты";
 5}
 6
 07@await Html.PartialAsync("_BsxGridShell")
 8<link rel="stylesheet" href="~/css/Studio2/EmailRouting/email-routing-results-index.css" asp-append-version="true" />
 9<link rel="stylesheet" href="~/css/Studio2/shared/quick-links-chip.css" asp-append-version="true" />
 10
 11<div class="container-fluid err-page py-3 bsx-surface">
 12    <div class="err-hero app-section-hero app-section-hero--import d-flex flex-wrap justify-content-between align-items-
 13        <div>
 14            <h1><i class="fas fa-clock text-primary me-2"></i>Планировщик импорта из почты</h1>
 15            <div class="subtitle">Автоматический запуск обработки входящих писем</div>
 16            <div class="subtitle-help">Задача Hangfire периодически получает новые письма и импортирует подходящие вложе
 17            <div class="err-quick-links app-section-hero__quick-links">
 18                <a asp-area="Studio2" asp-controller="EmailRoutingResults" asp-action="Index"><i class="bi bi-list-check
 19                <a asp-area="Studio2" asp-controller="EmailRoutingRules" asp-action="Index"><i class="bi bi-funnel"></i>
 20                <a href="/Hangfire" target="_blank"><i class="bi bi-box-arrow-up-right"></i>Hangfire</a>
 21            </div>
 22        </div>
 23    </div>
 24
 025    @if (TempData["TransactionResult"] != null)
 26    {
 027        <div class="alert alert-info">@TempData["TransactionResult"]</div>
 28    }
 29
 30    <div class="card er-module-card mb-3">
 31        <div class="card-header fw-semibold">Текущее состояние задачи</div>
 32        <div class="card-body">
 33            <div class="row g-3">
 34                <div class="col-md-3">
 35                    <div class="small text-muted">Статус</div>
 036                    @if (Model.IsScheduled)
 37                    {
 38                        <span class="badge bg-success">Включена</span>
 39                    }
 40                    else
 41                    {
 42                        <span class="badge bg-secondary">Выключена</span>
 43                    }
 44                </div>
 45                <div class="col-md-3">
 46                    <div class="small text-muted">Job ID</div>
 047                    <span class="font-monospace">@Model.RecurringJobId</span>
 48                </div>
 49                <div class="col-md-3">
 50                    <div class="small text-muted">Последний запуск</div>
 051                    <span>@(Model.LastExecution?.ToLocalTime().ToString("dd.MM.yyyy HH:mm:ss") ?? "—")</span>
 52                </div>
 53                <div class="col-md-3">
 54                    <div class="small text-muted">Следующий запуск</div>
 055                    <span>@(Model.NextExecution?.ToLocalTime().ToString("dd.MM.yyyy HH:mm:ss") ?? "—")</span>
 56                </div>
 57                <div class="col-md-4">
 58                    <div class="small text-muted">Cron</div>
 059                    <span class="font-monospace">@(Model.CronExpression ?? "—")</span>
 60                </div>
 61                <div class="col-md-4">
 62                    <div class="small text-muted">Состояние последнего выполнения</div>
 063                    <span>@(Model.LastJobState ?? "—")</span>
 64                </div>
 65                <div class="col-md-4">
 66                    <div class="small text-muted">Макс. писем за запуск</div>
 067                    <span>@Model.MaxEmailsPerRun</span>
 068                    <span class="text-muted small">(@Model.Settings.MaxBatchesPerRun × @Model.Settings.FetchLimit)</span
 69                </div>
 70            </div>
 71        </div>
 72    </div>
 73
 74    <div class="card er-module-card">
 75        <div class="card-header fw-semibold">Настройки обработки и расписания</div>
 76        <div class="card-body">
 77            <form asp-area="Studio2" asp-controller="EmailRoutingScheduler" asp-action="Save" method="post">
 078                @Html.AntiForgeryToken()
 79                <div asp-validation-summary="ModelOnly" class="text-danger mb-2"></div>
 80
 81                <h6 class="text-muted mb-3">Расписание</h6>
 82                <div class="row g-3 mb-4">
 83                    <div class="col-md-4">
 84                        <label asp-for="Settings.IntervalMinutes" class="form-label">Повторять каждые N минут</label>
 85                        <input asp-for="Settings.IntervalMinutes" type="number" min="1" max="1440" class="form-control" 
 86                        <span asp-validation-for="Settings.IntervalMinutes" class="text-danger small"></span>
 87                        <div class="form-text">От 1 до 1440 минут (MSK)</div>
 88                    </div>
 89                    <div class="col-md-4">
 90                        <label asp-for="Settings.MaxBatchesPerRun" class="form-label">Пакетов за запуск</label>
 91                        <input asp-for="Settings.MaxBatchesPerRun" type="number" min="1" max="100" class="form-control" 
 92                        <span asp-validation-for="Settings.MaxBatchesPerRun" class="text-danger small"></span>
 93                        <div class="form-text">Сколько раз подряд читать IMAP за один Hangfire-запуск</div>
 94                    </div>
 95                </div>
 96
 97                <h6 class="text-muted mb-3">IMAP: выборка писем</h6>
 98                <div class="row g-3 mb-4">
 99                    <div class="col-md-4">
 100                        <label asp-for="Settings.FetchLimit" class="form-label">Писем за пакет</label>
 101                        <input asp-for="Settings.FetchLimit" type="number" min="1" max="500" class="form-control" />
 102                        <span asp-validation-for="Settings.FetchLimit" class="text-danger small"></span>
 103                    </div>
 104                    <div class="col-md-4">
 105                        <label asp-for="Settings.FetchScanLimit" class="form-label">Сканировать последних UID</label>
 106                        <input asp-for="Settings.FetchScanLimit" type="number" min="10" max="1000" class="form-control" 
 107                        <span asp-validation-for="Settings.FetchScanLimit" class="text-danger small"></span>
 108                        <div class="form-text">Сколько последних UID проверять на новые письма</div>
 109                    </div>
 110                    <div class="col-md-4">
 111                        <label asp-for="Settings.FetchWindowDays" class="form-label">Окно по дате (дней)</label>
 112                        <input asp-for="Settings.FetchWindowDays" type="number" min="1" max="30" class="form-control" />
 113                        <span asp-validation-for="Settings.FetchWindowDays" class="text-danger small"></span>
 114                        <div class="form-text">Не брать письма старше N суток (от начала суток MSK)</div>
 115                    </div>
 116                </div>
 117
 118                <h6 class="text-muted mb-3">Правила импорта</h6>
 119                <div class="row g-3 mb-4">
 120                    <div class="col-md-6">
 121                        <div class="form-check">
 122                            <input asp-for="Settings.AllowMultiMatch" class="form-check-input" type="checkbox" />
 123                            <label asp-for="Settings.AllowMultiMatch" class="form-check-label">
 124                                Разрешить несколько правил на одно письмо
 125                            </label>
 126                        </div>
 127                        <div class="form-text">Если выключено — применяется только первое подходящее правило</div>
 128                    </div>
 129                </div>
 130
 131                <div class="d-flex gap-2 flex-wrap align-items-center">
 132                    <button type="submit" class="btn btn-primary">
 133                        <i class="fas fa-save me-1"></i>Сохранить и включить расписание
 134                    </button>
 135                    <span class="text-muted small">
 136                        IMAP host/логин — на странице
 137                        <a asp-area="Studio2" asp-controller="EmailRoutingResults" asp-action="Index">результатов импорт
 138                    </span>
 139                </div>
 140            </form>
 141
 142            <hr class="my-3" />
 143
 144            <div class="d-flex gap-2 flex-wrap">
 145                <form asp-area="Studio2" asp-controller="EmailRoutingScheduler" asp-action="RunNow" method="post" class=
 0146                    @Html.AntiForgeryToken()
 147                    <button type="submit" class="btn btn-outline-success">
 148                        <i class="fas fa-play me-1"></i>Запустить сейчас
 149                    </button>
 150                </form>
 151                <form asp-area="Studio2" asp-controller="EmailRoutingScheduler" asp-action="Disable" method="post" class
 152                      data-st2-confirm="Отключить периодическую задачу обработки писем?" data-st2-confirm-variant="warni
 0153                    @Html.AntiForgeryToken()
 154                    <button type="submit" class="btn btn-outline-danger">
 155                        <i class="fas fa-stop me-1"></i>Отключить расписание
 156                    </button>
 157                </form>
 158                <a href="/Hangfire" target="_blank" class="btn btn-outline-secondary">
 159                    <i class="fas fa-external-link-alt me-1"></i>Hangfire Dashboard
 160                </a>
 161            </div>
 162        </div>
 163    </div>
 164
 165    <div class="card er-module-card mt-3">
 166        <div class="card-header fw-semibold">История запусков</div>
 167        <div class="card-body p-2">
 168            <div id="emailRunsGrid" class="bsx-grid"></div>
 169        </div>
 170    </div>
 171</div>
 172
 173@section Scripts {
 174    <partial name="_ValidationScriptsPartial" />
 175    <script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script>
 176    <script>
 177        (function () {
 178            const D = window.BsxDirectory;
 179            const STATUS = { Success: ['Успешно', 'success'], Failed: ['Ошибка', 'danger'], Running: ['Выполняется', 'ac
 180            const fmt = v => v ? new Date(v).toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', year: '2-digit'
 181            D.grid({
 182                key: 'dirEmailRuns',
 183                prefix: 'emailRuns',
 184                autoHeight: true,
 0185                rows: @Json.Serialize(Model.RecentExecutions.Select(r => new
 0186                {
 0187                    id = r.Id, startedAtUtc = r.StartedAtUtc, completedAtUtc = r.CompletedAtUtc, triggerType = r.Trigger
 0188                    status = r.Status.ToString(), messagesProcessed = r.MessagesProcessed,
 0189                    uploaded = r.AttachmentsUploaded, failed = r.AttachmentsFailed, rejected = r.AttachmentsRejected
 0190                })),
 191                gridOptions: { overlayNoRowsTemplate: '<span class="bsx-grid-empty">Запусков пока нет</span>' },
 192                columnDefs: [
 193                    D.numCol({ field: 'id', headerName: 'ID', width: 90, sort: 'desc' }),
 194                    { field: 'startedAtUtc', headerName: 'Запуск', width: 170, valueFormatter: p => fmt(p.value) },
 195                    D.setCol({ field: 'triggerType', headerName: 'Тип', width: 140 }),
 196                    D.setCol({ field: 'status', headerName: 'Статус', width: 150, filterParams: { labels: Object.fromEnt
 197                      cellRenderer: p => { const s = STATUS[p.value]; return D.tone(s ? s[0] : p.value, s ? s[1] : 'mute
 198                    D.numCol({ field: 'messagesProcessed', headerName: 'Писем', width: 100 }),
 199                    D.numCol({ field: 'uploaded', headerName: 'Загружено', width: 120 }),
 200                    D.numCol({ field: 'failed', headerName: 'Ошибок', width: 110, cellClass: p => p.value > 0 ? 'bsx-ton
 201                    D.numCol({ field: 'rejected', headerName: 'Отклонено', width: 120 }),
 202                    { field: 'completedAtUtc', headerName: 'Завершён', flex: 1, minWidth: 170, valueFormatter: p => fmt(
 203                ]
 204            });
 205        })();
 206    </script>
 207}