| | | 1 | | @model OzonOrdersWeb.Areas.Studio2.ViewModels.EmailRouting.EmailRoutingSchedulerViewModel |
| | | 2 | | |
| | | 3 | | @{ |
| | 0 | 4 | | ViewData["Title"] = "Планировщик импорта из почты"; |
| | | 5 | | } |
| | | 6 | | |
| | 0 | 7 | | @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 | | |
| | 0 | 25 | | @if (TempData["TransactionResult"] != null) |
| | | 26 | | { |
| | 0 | 27 | | <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> |
| | 0 | 36 | | @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> |
| | 0 | 47 | | <span class="font-monospace">@Model.RecurringJobId</span> |
| | | 48 | | </div> |
| | | 49 | | <div class="col-md-3"> |
| | | 50 | | <div class="small text-muted">Последний запуск</div> |
| | 0 | 51 | | <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> |
| | 0 | 55 | | <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> |
| | 0 | 59 | | <span class="font-monospace">@(Model.CronExpression ?? "—")</span> |
| | | 60 | | </div> |
| | | 61 | | <div class="col-md-4"> |
| | | 62 | | <div class="small text-muted">Состояние последнего выполнения</div> |
| | 0 | 63 | | <span>@(Model.LastJobState ?? "—")</span> |
| | | 64 | | </div> |
| | | 65 | | <div class="col-md-4"> |
| | | 66 | | <div class="small text-muted">Макс. писем за запуск</div> |
| | 0 | 67 | | <span>@Model.MaxEmailsPerRun</span> |
| | 0 | 68 | | <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"> |
| | 0 | 78 | | @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= |
| | 0 | 146 | | @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 |
| | 0 | 153 | | @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, |
| | 0 | 185 | | rows: @Json.Serialize(Model.RecentExecutions.Select(r => new |
| | 0 | 186 | | { |
| | 0 | 187 | | id = r.Id, startedAtUtc = r.StartedAtUtc, completedAtUtc = r.CompletedAtUtc, triggerType = r.Trigger |
| | 0 | 188 | | status = r.Status.ToString(), messagesProcessed = r.MessagesProcessed, |
| | 0 | 189 | | uploaded = r.AttachmentsUploaded, failed = r.AttachmentsFailed, rejected = r.AttachmentsRejected |
| | 0 | 190 | | })), |
| | | 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 | | } |