fix(scheduler): различать неизвестную историю и подтверждённые запуски
This commit is contained in:
1 parent
933a4eaf57
commit
1f75bc5cea
6 files changed
+95
-18
No files matched your search
@@ -25,6 +25,7 @@ import {
|
||||
type SchedulerRunRowDto,
|
||||
type UptimeSettingsDto,
|
||||
} from "@/lib/scheduler-settings"
|
||||
import { listSchedulerRuns } from "@/shared/api/scheduler"
|
||||
import { requestJson, ApiClientError } from "@/shared/api/http-client"
|
||||
import {
|
||||
parseSchedulerRunSnapshot,
|
||||
@@ -585,6 +586,9 @@ function DataCollectionContent() {
|
||||
const [certRenewIntervalDraft, setCertRenewIntervalDraft] = useState("21600")
|
||||
const [renewBeforeDaysDraft, setRenewBeforeDaysDraft] = useState("30")
|
||||
const [schedulerRuns, setSchedulerRuns] = useState<SchedulerRunRowDto[]>([])
|
||||
const [runsKnown, setRunsKnown] = useState(false)
|
||||
const [runsError, setRunsError] = useState<string | null>(null)
|
||||
const [runsReceivedAt, setRunsReceivedAt] = useState<string | null>(null)
|
||||
const [runFilterJobKey, setRunFilterJobKey] = useState<string>("")
|
||||
const [runNowJobKey, setRunNowJobKey] = useState<string | null>(null)
|
||||
const [schedulerSaveBusy, setSchedulerSaveBusy] = useState(false)
|
||||
@@ -623,17 +627,14 @@ function DataCollectionContent() {
|
||||
setCollectorError(null)
|
||||
setPageBusy(true)
|
||||
try {
|
||||
const runsQuery =
|
||||
runFilterJobKey && SCHEDULER_JOB_KEYS.includes(runFilterJobKey as (typeof SCHEDULER_JOB_KEYS)[number])
|
||||
? `?limit=80&jobKey=${encodeURIComponent(runFilterJobKey)}`
|
||||
: "?limit=80"
|
||||
const runsFilter = runFilterJobKey && SCHEDULER_JOB_KEYS.includes(runFilterJobKey as (typeof SCHEDULER_JOB_KEYS)[number]) ? runFilterJobKey as (typeof SCHEDULER_JOB_KEYS)[number] : undefined
|
||||
const [trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes] = await Promise.allSettled([
|
||||
apiFetch<CollectorSettingsDto>("/api/traffic/settings", { signal }),
|
||||
apiFetch<CollectorSettingsDto>("/api/servers-api-ping/settings", { signal }),
|
||||
apiFetch<UptimeSettingsDto>("/api/uptime/settings", { signal }),
|
||||
apiFetch<CollectorSettingsDto>("/api/internet-path/settings", { signal }),
|
||||
apiFetch<{ enabled: boolean; intervalSec: number; renewBeforeDays: number }>("/api/certificates/renew-settings", { signal }),
|
||||
apiFetch<{ runs: SchedulerRunRowDto[] }>(`/api/scheduler/runs${runsQuery}`, { signal }),
|
||||
listSchedulerRuns(backendUrl, { limit: 80, jobKey: runsFilter }, { signal }),
|
||||
])
|
||||
|
||||
if (!current()) return
|
||||
@@ -699,14 +700,17 @@ function DataCollectionContent() {
|
||||
|
||||
const runsPayload = readSettled(runsRes)
|
||||
if (runsPayload) {
|
||||
setSchedulerRuns(runsPayload.runs ?? [])
|
||||
setSchedulerRuns(runsPayload.runs)
|
||||
setRunsKnown(true); setRunsError(null); setRunsReceivedAt(new Date().toISOString())
|
||||
} else if (runsRes.status === "rejected") {
|
||||
setRunsError(extractApiError(runsRes.reason))
|
||||
}
|
||||
} catch (e) {
|
||||
if (current()) setCollectorError(e instanceof Error ? e.message : "Не удалось загрузить данные")
|
||||
} finally {
|
||||
if (current()) setPageBusy(false)
|
||||
}
|
||||
}, [apiFetch, isLive, runFilterJobKey])
|
||||
}, [apiFetch, backendUrl, isLive, runFilterJobKey])
|
||||
|
||||
const persistSchedulerDrafts = useCallback(async (overrides: SchedulerToggleOverrides = {}) => {
|
||||
const trafficEnabled = overrides.trafficEnabled ?? draftTrafficEnabled
|
||||
@@ -1041,8 +1045,8 @@ function DataCollectionContent() {
|
||||
},
|
||||
{
|
||||
label: "Журнал (в списке)",
|
||||
value: String(schedulerRuns.length),
|
||||
sub: errorRunsInView ? `${errorRunsInView} с ошибкой` : "ошибок в показанных — нет",
|
||||
value: runsKnown ? String(schedulerRuns.length) : "—",
|
||||
sub: runsError ? (runsKnown ? "Показана последняя подтверждённая история" : "История не получена") : runsKnown ? (errorRunsInView ? `${errorRunsInView} с ошибкой` : "ошибок в показанных — нет") : "Ожидание истории",
|
||||
icon: <DatabaseIcon className="size-4" />,
|
||||
},
|
||||
]
|
||||
@@ -1260,7 +1264,7 @@ function DataCollectionContent() {
|
||||
|
||||
<OpsPanel
|
||||
title="Журнал прогонов"
|
||||
description="Таблица `scheduler_runs` — до 80 записей; раскройте строку для полей и текста ошибки."
|
||||
description="Последние 80 запусков. Раскройте запись для результатов и объяснения ошибок."
|
||||
headerRight={
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<label className="text-xs text-muted-foreground whitespace-nowrap" htmlFor="run-filter">
|
||||
@@ -1270,7 +1274,7 @@ function DataCollectionContent() {
|
||||
id="run-filter"
|
||||
className="h-8 rounded-md border border-input bg-background px-2 text-xs min-w-[180px]"
|
||||
value={runFilterJobKey}
|
||||
onChange={(e) => { setRunFilterJobKey(e.target.value); setOpenRunIds(new Set()) }}
|
||||
onChange={(e) => { setRunFilterJobKey(e.target.value); setOpenRunIds(new Set()); setSchedulerRuns([]); setRunsKnown(false); setRunsError(null); setRunsReceivedAt(null) }}
|
||||
>
|
||||
<option value="">Все</option>
|
||||
{SCHEDULER_JOB_KEYS.map((k) => (
|
||||
@@ -1283,7 +1287,19 @@ function DataCollectionContent() {
|
||||
}
|
||||
contentClassName="px-0 pb-0"
|
||||
>
|
||||
{schedulerRuns.length === 0 ? (
|
||||
{runsError ? (
|
||||
<div className="mx-4 mb-3">
|
||||
<Alert variant="warning">
|
||||
<AlertCircleIcon aria-hidden="true" />
|
||||
<AlertTitle>{runsKnown ? "История не обновлена" : "История не получена"}</AlertTitle>
|
||||
<AlertDescription>{runsError}</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
) : null}
|
||||
{runsReceivedAt ? <p className="px-4 pb-3 text-xs text-muted-foreground">История получена: {new Date(runsReceivedAt).toLocaleString("ru-RU")}</p> : null}
|
||||
{!runsKnown ? (
|
||||
!runsError ? <p role="status" className="px-4 py-10 text-center text-sm text-muted-foreground">Загрузка журнала…</p> : null
|
||||
) : schedulerRuns.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-10 px-4">Пока нет прогонов</p>
|
||||
) : (
|
||||
<div className="divide-y divide-border">
|
||||
|
||||
Reference in new issue
Block a user