fix(scheduler): показывать подтверждённый статус независимо от настроек uptime

This commit is contained in:
Denozordec committed 2026-10-01 21:55:03 +07:00
1 parent 1f75bc5cea
commit 5d5ea680aa
5 files changed
+87 -51

No files matched your search

+34 -49
View File
@@ -22,10 +22,11 @@ import {
SCHEDULER_JOB_LABELS,
type CollectorSettingsDto,
type SchedulerJobStatusDto,
type SchedulerStatusDto,
type SchedulerRunRowDto,
type UptimeSettingsDto,
} from "@/lib/scheduler-settings"
import { listSchedulerRuns } from "@/shared/api/scheduler"
import { getSchedulerStatus, listSchedulerRuns } from "@/shared/api/scheduler"
import { requestJson, ApiClientError } from "@/shared/api/http-client"
import {
parseSchedulerRunSnapshot,
@@ -586,6 +587,9 @@ function DataCollectionContent() {
const [certRenewIntervalDraft, setCertRenewIntervalDraft] = useState("21600")
const [renewBeforeDaysDraft, setRenewBeforeDaysDraft] = useState("30")
const [schedulerRuns, setSchedulerRuns] = useState<SchedulerRunRowDto[]>([])
const [schedulerStatus, setSchedulerStatus] = useState<SchedulerStatusDto | null>(null)
const [schedulerStatusError, setSchedulerStatusError] = useState<string | null>(null)
const [schedulerStatusReceivedAt, setSchedulerStatusReceivedAt] = useState<string | null>(null)
const [runsKnown, setRunsKnown] = useState(false)
const [runsError, setRunsError] = useState<string | null>(null)
const [runsReceivedAt, setRunsReceivedAt] = useState<string | null>(null)
@@ -613,9 +617,9 @@ function DataCollectionContent() {
}, [])
const schedulerJobsByKey = useMemo(() => {
const jobs = uptimeCollector?.scheduler?.jobs ?? []
const jobs = schedulerStatus?.jobs ?? []
return Object.fromEntries(jobs.map((j) => [j.jobKey, j])) as Record<string, SchedulerJobStatusDto>
}, [uptimeCollector?.scheduler?.jobs])
}, [schedulerStatus?.jobs])
const loadCollectors = useCallback(async () => {
if (!isLive || !reader.current.active) return
@@ -628,19 +632,27 @@ function DataCollectionContent() {
setPageBusy(true)
try {
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([
const runsRequest = listSchedulerRuns(backendUrl, { limit: 80, jobKey: runsFilter }, { signal }).then(data => {
if (current()) { setSchedulerRuns(data.runs); setRunsKnown(true); setRunsError(null); setRunsReceivedAt(new Date().toISOString()) }
return data
}, error => { if (current()) setRunsError(extractApiError(error)); throw error })
const statusRequest = getSchedulerStatus(backendUrl, { signal }).then(data => {
if (current()) { setSchedulerStatus(data); setSchedulerStatusError(null); setSchedulerStatusReceivedAt(new Date().toISOString()) }
return data
}, error => { if (current()) setSchedulerStatusError(extractApiError(error)); throw error })
const [trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes, statusRes] = 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 }),
listSchedulerRuns(backendUrl, { limit: 80, jobKey: runsFilter }, { signal }),
runsRequest, statusRequest,
])
if (!current()) return
const loadErrors = collectSettledErrors(
[trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes],
["трафик", "серверы REST API", "uptime", "internet path", "сертификаты", "журнал планировщика"],
[trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes, statusRes],
["трафик", "серверы REST API", "uptime", "internet path", "сертификаты", "журнал планировщика", "статус планировщика"],
)
if (loadErrors.length > 0) {
setCollectorError(loadErrors.join("; "))
@@ -698,13 +710,6 @@ function DataCollectionContent() {
setRenewBeforeDaysDraft(String(certRenew.renewBeforeDays ?? 30))
}
const runsPayload = readSettled(runsRes)
if (runsPayload) {
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 {
@@ -984,51 +989,23 @@ function DataCollectionContent() {
uptimeSpeedIntervalDraft,
])
const enabledJobsCount = useMemo(() => {
const jobs = uptimeCollector?.scheduler?.jobs
if (jobs?.length) return jobs.filter((job) => job.enabled).length
let n = draftTrafficEnabled ? 1 : 0
if (draftServersApiEnabled) n += 1
if (draftResourcesEnabled) n += 1
if (draftPingEnabled) n += 1
if (draftSpeedEnabled) n += 1
if (draftInternetPathEnabled) n += 1
if (draftCertRenewEnabled) n += 1
return n
}, [
draftCertRenewEnabled,
draftInternetPathEnabled,
draftPingEnabled,
draftResourcesEnabled,
draftServersApiEnabled,
draftSpeedEnabled,
draftTrafficEnabled,
uptimeCollector?.scheduler?.jobs,
])
const schedulerJobCount = SCHEDULER_JOB_KEYS.length
const runningJobsCount = useMemo(
() => (uptimeCollector?.scheduler?.jobs ?? []).filter((j) => j.running).length,
[uptimeCollector?.scheduler?.jobs],
)
const enabledJobsCount = schedulerStatus?.jobs.filter(job => job.enabled).length ?? 0
const schedulerJobCount = schedulerStatus?.jobs.length ?? 0
const runningJobsCount = schedulerStatus?.jobs.filter(job => job.running).length ?? 0
const errorRunsInView = useMemo(() => schedulerRuns.filter((r) => r.status === "error").length, [schedulerRuns])
const stats = [
{
label: "Включено задач",
value: `${enabledJobsCount} / ${schedulerJobCount}`,
sub: uptimeCollector?.scheduler?.jobs?.length
? "По сохранённым задачам планировщика"
: "По переключателям на этой странице",
value: schedulerStatus ? `${enabledJobsCount} / ${schedulerJobCount}` : "—",
sub: schedulerStatusError ? (schedulerStatus ? "Последний подтверждённый статус" : "Статус не получен") : schedulerStatus ? "По сохранённым задачам планировщика" : "Ожидание статуса",
icon: <CalendarClockIcon className="size-4" />,
},
{
label: "Сейчас выполняется",
value: String(runningJobsCount),
sub: "Фоновые прогоны планировщика",
value: schedulerStatus ? String(runningJobsCount) : "—",
sub: schedulerStatusError ? "Статус требует обновления" : "Фоновые прогоны планировщика",
icon: <LoaderCircleIcon className="size-4" />,
},
{
@@ -1099,6 +1076,14 @@ function DataCollectionContent() {
</OpsPanel>
)}
{isLive && schedulerStatusError ? (
<Alert variant="warning">
<AlertCircleIcon aria-hidden="true" />
<AlertTitle>{schedulerStatus ? "Статус планировщика не обновлён" : "Статус планировщика не получен"}</AlertTitle>
<AlertDescription>{schedulerStatusError}</AlertDescription>
</Alert>
) : null}
{isLive && schedulerStatusReceivedAt ? <p className="text-xs text-muted-foreground">Статус получен: {new Date(schedulerStatusReceivedAt).toLocaleString("ru-RU")}</p> : null}
{isLive && collectorError && (
<Alert variant="destructive">
<AlertCircleIcon />