| | | 1 | | @using OzonDomains.Models.OzonWebhook |
| | | 2 | | @model OzonOrdersWeb.Areas.Studio2.ViewModels.OzonWebhookViewModels.OzonWebhookIndexViewModel |
| | | 3 | | @* Webhook Ozon — серверный грид (grid/webhook); «Открыть» — модалка с полным payload. *@ |
| | | 4 | | |
| | | 5 | | @using Services.OzonWebhookServices.Monitoring |
| | | 6 | | @{ |
| | 0 | 7 | | var health = Model.Availability; |
| | 0 | 8 | | var isAdminUser = User.IsInRole("Admin") || User.IsInRole("SuperAdmin"); |
| | 0 | 9 | | var healthRows = new List<object>(); |
| | 0 | 10 | | foreach (var cabinet in health.Cabinets) |
| | | 11 | | { |
| | 0 | 12 | | if (cabinet.Error != null) |
| | | 13 | | { |
| | 0 | 14 | | healthRows.Add(new { cabinet = cabinet.ClientName, address = "—", status = "Ошибка проверки", reason = "Не у |
| | 0 | 15 | | continue; |
| | | 16 | | } |
| | 0 | 17 | | if (cabinet.Endpoints.Count == 0) |
| | | 18 | | { |
| | 0 | 19 | | healthRows.Add(new { cabinet = cabinet.ClientName, address = "—", status = "Нет адресов", reason = "Адреса w |
| | 0 | 20 | | continue; |
| | | 21 | | } |
| | 0 | 22 | | foreach (var endpoint in cabinet.Endpoints) |
| | | 23 | | { |
| | 0 | 24 | | var status = endpoint.IsChecking ? "На проверке" : !endpoint.Enabled ? "Отключён" : endpoint.Availability sw |
| | 0 | 25 | | { |
| | 0 | 26 | | OzonWebhookAvailability.Available => "Доступен", |
| | 0 | 27 | | OzonWebhookAvailability.Unstable => "Нестабилен", |
| | 0 | 28 | | OzonWebhookAvailability.Unavailable => "Недоступен", |
| | 0 | 29 | | _ => "На проверке" |
| | 0 | 30 | | }; |
| | 0 | 31 | | var tone = endpoint.IsProblem ? "danger" : endpoint.IsChecking || endpoint.Availability == OzonWebhookAvaila |
| | 0 | 32 | | var reason = string.Join(" ", new[] { endpoint.Enabled ? null : OzonWebhookAvailabilitySnapshot.ReasonText(e |
| | 0 | 33 | | healthRows.Add(new |
| | 0 | 34 | | { |
| | 0 | 35 | | cabinet = cabinet.ClientName, |
| | 0 | 36 | | address = endpoint.DisplayUrl + (endpoint.Monitored ? string.Empty : " (чужой адрес, не контролируется)" |
| | 0 | 37 | | status = endpoint.StatusDateUtc.HasValue ? status + " · " + endpoint.StatusDateUtc.Value.ToLocalTime().T |
| | 0 | 38 | | reason, tone, canEnable = isAdminUser && endpoint.Monitored && !endpoint.Enabled, |
| | 0 | 39 | | clientId = cabinet.ClientId, urlId = endpoint.Id |
| | 0 | 40 | | }); |
| | | 41 | | } |
| | | 42 | | } |
| | | 43 | | } |
| | | 44 | | |
| | | 45 | | <section class="oz-wh-health @(health.HasProblem ? "oz-wh-health--bad" : "")" id="ozWebhookHealth"> |
| | | 46 | | <div class="oz-wh-health__head"> |
| | | 47 | | <strong><i class="fas fa-heart-pulse"></i> Доступность пуш-адресов (мониторинг Ozon)</strong> |
| | | 48 | | <span class="oz-wh-health__meta"> |
| | 0 | 49 | | @(health.CheckedAtUtc.HasValue ? "Проверено " + health.CheckedAtUtc.Value.ToLocalTime().ToString("dd.MM.yyyy |
| | | 50 | | </span> |
| | 0 | 51 | | @if (isAdminUser) |
| | | 52 | | { |
| | | 53 | | <button type="button" class="btn btn-sm btn-outline-secondary" id="ozWebhookHealthRefresh">Проверить сейчас< |
| | | 54 | | } |
| | | 55 | | </div> |
| | | 56 | | <div class="oz-wh-health__hint"> |
| | | 57 | | Ozon замеряет ответ раз в ночь: до 1500 мс — доступен, 1500–2500 мс — нестабилен, свыше 2500 мс — недоступен. |
| | | 58 | | Три дня «недоступен» подряд — Ozon отключает отправку, и статусы заказов перестают приходить пушами. |
| | | 59 | | </div> |
| | | 60 | | <div id="ozWebhookHealthGrid" class="bsx-grid oz-wh-health__grid"></div> |
| | | 61 | | </section> |
| | | 62 | | |
| | | 63 | | <script> |
| | | 64 | | (function () { |
| | 0 | 65 | | var rows = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(healthRows)); |
| | | 66 | | var gridHost = document.getElementById('ozWebhookHealthGrid'); |
| | | 67 | | var BG = window.BsxGrid; |
| | | 68 | | var esc = BG ? BG.esc : function (value) { return String(value ?? ''); }; |
| | | 69 | | if (BG && gridHost) { |
| | | 70 | | var healthGrid = BG.localTable(gridHost, { |
| | | 71 | | key: 'jobLogsWebhookHealth', |
| | | 72 | | emptyText: 'Данных о пуш-адресах пока нет.', |
| | | 73 | | getRowId: function (p) { return `${p.data.cabinet}-${p.data.urlId || p.data.address}`; }, |
| | | 74 | | gridOptions: { domLayout: 'autoHeight', rowHeight: 40 }, |
| | | 75 | | columnDefs: [ |
| | | 76 | | { field: 'cabinet', headerName: 'Кабинет', width: 190, filter: 'agTextColumnFilter' }, |
| | | 77 | | { field: 'address', headerName: 'Адрес', flex: 1, minWidth: 330, filter: 'agTextColumnFilter', toolt |
| | | 78 | | { field: 'status', headerName: 'Статус', width: 190, filter: 'agTextColumnFilter', |
| | | 79 | | cellRenderer: function (p) { return p.data ? `<span class="bsx-tone bsx-tone--${esc(p.data.tone |
| | | 80 | | { field: 'reason', headerName: 'Причина', flex: 1, minWidth: 220, filter: 'agTextColumnFilter', tool |
| | | 81 | | { colId: 'actions', headerName: '', width: 115, sortable: false, filter: false, lockVisible: true, c |
| | | 82 | | cellRenderer: function (p) { return p.data && p.data.canEnable ? `<button type="button" class="b |
| | | 83 | | ] |
| | | 84 | | }); |
| | | 85 | | healthGrid.setRows(rows); |
| | | 86 | | } |
| | | 87 | | |
| | | 88 | | function post(url) { return fetch(url, { method: 'POST', headers: { Accept: 'application/json' } }).then(functio |
| | | 89 | | var refresh = document.getElementById('ozWebhookHealthRefresh'); |
| | | 90 | | if (refresh) refresh.addEventListener('click', function () { |
| | | 91 | | refresh.disabled = true; |
| | | 92 | | post('/Studio2/JobLogs/webhook-health/refresh').then(function () { location.reload(); }).catch(function () { |
| | | 93 | | }); |
| | | 94 | | gridHost?.addEventListener('click', async function (event) { |
| | | 95 | | var btn = event.target.closest('.oz-wh-enable'); |
| | | 96 | | if (!btn) return; |
| | | 97 | | if (!(await St2Dialog.confirm('Включить отправку пуш-уведомлений на этот адрес? Убедитесь, что адрес отв |
| | | 98 | | btn.disabled = true; |
| | | 99 | | post('/Studio2/JobLogs/webhook-health/enable?clientId=' + btn.dataset.clientId + '&urlId=' + btn.dataset |
| | | 100 | | .then(function (d) { if (d && d.success) location.reload(); else { alert((d && d.message) || 'Не уда |
| | | 101 | | .catch(function () { btn.disabled = false; }); |
| | | 102 | | }); |
| | | 103 | | })(); |
| | | 104 | | </script> |
| | | 105 | | |
| | | 106 | | <div class="job-logs-intro"> |
| | | 107 | | <i class="fas fa-circle-info"></i> |
| | | 108 | | <span> |
| | | 109 | | <strong>Received</strong> — принято, ещё не обработано; <strong>Processed</strong> — применено к заказу; |
| | | 110 | | <strong>Skipped</strong> — тип сообщения не обрабатывается; <strong>Error</strong> — обработчик упал, |
| | | 111 | | причина в колонке «Ошибка / результат». |
| | | 112 | | </span> |
| | | 113 | | </div> |
| | | 114 | | |
| | | 115 | | <div class="job-logs-workflow"> |
| | | 116 | | <div class="job-logs-workflow-step"><span class="job-logs-workflow-num">1</span><div><strong>Ozon отправляет webhook |
| | | 117 | | <div class="job-logs-workflow-step"><span class="job-logs-workflow-num">2</span><div><strong>Сообщение в очереди</st |
| | | 118 | | <div class="job-logs-workflow-step"><span class="job-logs-workflow-num">3</span><div><strong>Обработчик обновляет за |
| | | 119 | | </div> |
| | | 120 | | |
| | 0 | 121 | | @await Html.PartialAsync("_JobLogsGridPanel", new OzonOrdersWeb.Areas.Studio2.ViewModels.JobLogs.JobLogsGridPanelViewMod |
| | 0 | 122 | | "Уведомления Ozon", "fa-bell text-primary", "Отправление, тип, ошибка…", |
| | 0 | 123 | | "Статусы: Received — принято, Processed — применено к заказу, Skipped — тип не обрабатывается, Error — обработчик уп |
| | | 124 | | |
| | | 125 | | <div class="modal fade" id="jobLogsWebhookDetailModal" tabindex="-1"> |
| | | 126 | | <div class="modal-dialog modal-xl modal-dialog-scrollable"> |
| | | 127 | | <div class="modal-content"> |
| | | 128 | | <div class="modal-header"> |
| | | 129 | | <h5 class="modal-title">Webhook #<span id="jobLogsWebhookDetailId"></span></h5> |
| | | 130 | | <button type="button" class="btn-close" data-bs-dismiss="modal"></button> |
| | | 131 | | </div> |
| | | 132 | | <div class="modal-body"> |
| | | 133 | | <pre id="jobLogsWebhookDetailBody" class="job-logs-webhook-payload mb-0"></pre> |
| | | 134 | | </div> |
| | | 135 | | </div> |
| | | 136 | | </div> |
| | | 137 | | </div> |