fix(data-collection): добавить возможность отключения переключателей и улучшить обработку настроек
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m31s
Docker images / frontend-image (push) Successful in 1m41s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m31s
Docker images / frontend-image (push) Successful in 1m41s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s
This commit is contained in:
@@ -79,15 +79,25 @@ function collectSettledErrors(results: PromiseSettledResult<unknown>[], labels:
|
|||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
|
function Toggle({
|
||||||
|
checked,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
}: {
|
||||||
|
checked: boolean
|
||||||
|
onChange: (v: boolean) => void
|
||||||
|
disabled?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
onClick={() => onChange(!checked)}
|
onClick={() => onChange(!checked)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors cursor-pointer",
|
"relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
|
||||||
|
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||||
checked ? "bg-primary" : "bg-input",
|
checked ? "bg-primary" : "bg-input",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -750,6 +760,38 @@ function RunRowDetail({ r }: { r: SchedulerRunRowDto }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SchedulerToggleOverrides = {
|
||||||
|
trafficEnabled?: boolean
|
||||||
|
serversApiEnabled?: boolean
|
||||||
|
resourcesEnabled?: boolean
|
||||||
|
pingEnabled?: boolean
|
||||||
|
speedEnabled?: boolean
|
||||||
|
internetPathEnabled?: boolean
|
||||||
|
certRenewEnabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySchedulerJobsToDrafts(
|
||||||
|
jobs: SchedulerJobStatusDto[],
|
||||||
|
setters: {
|
||||||
|
setDraftTrafficEnabled: (value: boolean) => void
|
||||||
|
setDraftServersApiEnabled: (value: boolean) => void
|
||||||
|
setDraftResourcesEnabled: (value: boolean) => void
|
||||||
|
setDraftPingEnabled: (value: boolean) => void
|
||||||
|
setDraftSpeedEnabled: (value: boolean) => void
|
||||||
|
setDraftInternetPathEnabled: (value: boolean) => void
|
||||||
|
setDraftCertRenewEnabled: (value: boolean) => void
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const byKey = Object.fromEntries(jobs.map((job) => [job.jobKey, job])) as Record<string, SchedulerJobStatusDto>
|
||||||
|
if (byKey.traffic) setters.setDraftTrafficEnabled(!!byKey.traffic.enabled)
|
||||||
|
if (byKey.servers_rest_ping) setters.setDraftServersApiEnabled(!!byKey.servers_rest_ping.enabled)
|
||||||
|
if (byKey.uptime_resources) setters.setDraftResourcesEnabled(!!byKey.uptime_resources.enabled)
|
||||||
|
if (byKey.uptime_ping) setters.setDraftPingEnabled(!!byKey.uptime_ping.enabled)
|
||||||
|
if (byKey.uptime_speed) setters.setDraftSpeedEnabled(!!byKey.uptime_speed.enabled)
|
||||||
|
if (byKey.internet_path) setters.setDraftInternetPathEnabled(!!byKey.internet_path.enabled)
|
||||||
|
if (byKey.certificates_renew) setters.setDraftCertRenewEnabled(!!byKey.certificates_renew.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
export default function DataCollectionPage() {
|
export default function DataCollectionPage() {
|
||||||
const { mode, backendUrl, prefsHydrated } = useDataSource()
|
const { mode, backendUrl, prefsHydrated } = useDataSource()
|
||||||
const isLive = prefsHydrated && mode === "live"
|
const isLive = prefsHydrated && mode === "live"
|
||||||
@@ -849,6 +891,17 @@ export default function DataCollectionPage() {
|
|||||||
setDraftResourcesEnabled(!!(uptime.resourcesEnabled ?? uptime.enabled))
|
setDraftResourcesEnabled(!!(uptime.resourcesEnabled ?? uptime.enabled))
|
||||||
setDraftPingEnabled(!!(uptime.pingEnabled ?? uptime.enabled))
|
setDraftPingEnabled(!!(uptime.pingEnabled ?? uptime.enabled))
|
||||||
setDraftSpeedEnabled(!!(uptime.speedEnabled ?? uptime.enabled))
|
setDraftSpeedEnabled(!!(uptime.speedEnabled ?? uptime.enabled))
|
||||||
|
if (uptime.scheduler?.jobs?.length) {
|
||||||
|
applySchedulerJobsToDrafts(uptime.scheduler.jobs, {
|
||||||
|
setDraftTrafficEnabled,
|
||||||
|
setDraftServersApiEnabled,
|
||||||
|
setDraftResourcesEnabled,
|
||||||
|
setDraftPingEnabled,
|
||||||
|
setDraftSpeedEnabled,
|
||||||
|
setDraftInternetPathEnabled,
|
||||||
|
setDraftCertRenewEnabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const internetPath = readSettled(internetPathRes)
|
const internetPath = readSettled(internetPathRes)
|
||||||
@@ -876,6 +929,138 @@ export default function DataCollectionPage() {
|
|||||||
}
|
}
|
||||||
}, [apiFetch, isLive, runFilterJobKey])
|
}, [apiFetch, isLive, runFilterJobKey])
|
||||||
|
|
||||||
|
const persistSchedulerDrafts = useCallback(async (overrides: SchedulerToggleOverrides = {}) => {
|
||||||
|
const trafficEnabled = overrides.trafficEnabled ?? draftTrafficEnabled
|
||||||
|
const serversApiEnabled = overrides.serversApiEnabled ?? draftServersApiEnabled
|
||||||
|
const resourcesEnabled = overrides.resourcesEnabled ?? draftResourcesEnabled
|
||||||
|
const pingEnabled = overrides.pingEnabled ?? draftPingEnabled
|
||||||
|
const speedEnabled = overrides.speedEnabled ?? draftSpeedEnabled
|
||||||
|
const internetPathEnabled = overrides.internetPathEnabled ?? draftInternetPathEnabled
|
||||||
|
const certRenewEnabled = overrides.certRenewEnabled ?? draftCertRenewEnabled
|
||||||
|
|
||||||
|
const tInt = Math.max(5, Number.parseInt(trafficIntervalDraft, 10) || 30)
|
||||||
|
const tRet = Math.max(1, Number.parseInt(trafficRetentionDraft, 10) || 14)
|
||||||
|
const uRes = Math.max(5, Number.parseInt(uptimeResourceIntervalDraft, 10) || 300)
|
||||||
|
const uPing = Math.max(5, Number.parseInt(uptimeIntervalDraft, 10) || 15)
|
||||||
|
const uSpd = Math.max(10, Number.parseInt(uptimeSpeedIntervalDraft, 10) || 60)
|
||||||
|
const uRet = Math.max(1, Number.parseInt(uptimeRetentionDraft, 10) || 14)
|
||||||
|
const sApiInt = Math.max(10, Number.parseInt(serversApiIntervalDraft, 10) || 120)
|
||||||
|
const ipInt = Math.max(30, Number.parseInt(internetPathIntervalDraft, 10) || 300)
|
||||||
|
const certRenewInt = Math.max(300, Number.parseInt(certRenewIntervalDraft, 10) || 21600)
|
||||||
|
const renewBeforeDays = Math.max(1, Math.min(90, Number.parseInt(renewBeforeDaysDraft, 10) || 30))
|
||||||
|
|
||||||
|
const saveResults = await Promise.allSettled([
|
||||||
|
apiFetch("/api/traffic/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: trafficEnabled,
|
||||||
|
intervalSec: tInt,
|
||||||
|
retentionDays: tRet,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
apiFetch("/api/servers-api-ping/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: serversApiEnabled,
|
||||||
|
intervalSec: sApiInt,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
apiFetch("/api/uptime/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
resourcesEnabled,
|
||||||
|
pingEnabled,
|
||||||
|
speedEnabled,
|
||||||
|
intervalSec: uRes,
|
||||||
|
probeIntervalSec: uPing,
|
||||||
|
speedIntervalSec: uSpd,
|
||||||
|
retentionDays: uRet,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
apiFetch("/api/internet-path/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: internetPathEnabled,
|
||||||
|
intervalSec: ipInt,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
apiFetch("/api/certificates/renew-settings", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
enabled: certRenewEnabled,
|
||||||
|
intervalSec: certRenewInt,
|
||||||
|
renewBeforeDays,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
const saveErrors = collectSettledErrors(
|
||||||
|
saveResults,
|
||||||
|
["трафик", "серверы REST API", "uptime", "internet path", "сертификаты"],
|
||||||
|
)
|
||||||
|
if (saveErrors.length > 0) {
|
||||||
|
throw new Error(`Не все настройки сохранились: ${saveErrors.join("; ")}`)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
apiFetch,
|
||||||
|
certRenewIntervalDraft,
|
||||||
|
draftCertRenewEnabled,
|
||||||
|
draftInternetPathEnabled,
|
||||||
|
draftPingEnabled,
|
||||||
|
draftResourcesEnabled,
|
||||||
|
draftServersApiEnabled,
|
||||||
|
draftSpeedEnabled,
|
||||||
|
draftTrafficEnabled,
|
||||||
|
internetPathIntervalDraft,
|
||||||
|
renewBeforeDaysDraft,
|
||||||
|
serversApiIntervalDraft,
|
||||||
|
trafficIntervalDraft,
|
||||||
|
trafficRetentionDraft,
|
||||||
|
uptimeIntervalDraft,
|
||||||
|
uptimeResourceIntervalDraft,
|
||||||
|
uptimeRetentionDraft,
|
||||||
|
uptimeSpeedIntervalDraft,
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleJobEnabledChange = useCallback(async (jobKey: (typeof SCHEDULER_JOB_KEYS)[number], nextEnabled: boolean) => {
|
||||||
|
const overrides: SchedulerToggleOverrides = {}
|
||||||
|
if (jobKey === "traffic") {
|
||||||
|
setDraftTrafficEnabled(nextEnabled)
|
||||||
|
overrides.trafficEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "servers_rest_ping") {
|
||||||
|
setDraftServersApiEnabled(nextEnabled)
|
||||||
|
overrides.serversApiEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "uptime_resources") {
|
||||||
|
setDraftResourcesEnabled(nextEnabled)
|
||||||
|
overrides.resourcesEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "uptime_ping") {
|
||||||
|
setDraftPingEnabled(nextEnabled)
|
||||||
|
overrides.pingEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "uptime_speed") {
|
||||||
|
setDraftSpeedEnabled(nextEnabled)
|
||||||
|
overrides.speedEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "certificates_renew") {
|
||||||
|
setDraftCertRenewEnabled(nextEnabled)
|
||||||
|
overrides.certRenewEnabled = nextEnabled
|
||||||
|
} else if (jobKey === "internet_path") {
|
||||||
|
setDraftInternetPathEnabled(nextEnabled)
|
||||||
|
overrides.internetPathEnabled = nextEnabled
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCollectorError(null)
|
||||||
|
setSchedulerSaveBusy(true)
|
||||||
|
try {
|
||||||
|
await persistSchedulerDrafts(overrides)
|
||||||
|
await loadCollectors()
|
||||||
|
} catch (e) {
|
||||||
|
setCollectorError(e instanceof Error ? e.message : "Не удалось сохранить")
|
||||||
|
} finally {
|
||||||
|
setSchedulerSaveBusy(false)
|
||||||
|
}
|
||||||
|
}, [loadCollectors, persistSchedulerDrafts])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLive) {
|
if (!isLive) {
|
||||||
setTrafficCollector(null)
|
setTrafficCollector(null)
|
||||||
@@ -904,6 +1089,9 @@ export default function DataCollectionPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enabledJobsCount = useMemo(() => {
|
const enabledJobsCount = useMemo(() => {
|
||||||
|
const jobs = uptimeCollector?.scheduler?.jobs
|
||||||
|
if (jobs?.length) return jobs.filter((job) => job.enabled).length
|
||||||
|
|
||||||
let n = draftTrafficEnabled ? 1 : 0
|
let n = draftTrafficEnabled ? 1 : 0
|
||||||
if (draftServersApiEnabled) n += 1
|
if (draftServersApiEnabled) n += 1
|
||||||
if (draftResourcesEnabled) n += 1
|
if (draftResourcesEnabled) n += 1
|
||||||
@@ -920,6 +1108,7 @@ export default function DataCollectionPage() {
|
|||||||
draftServersApiEnabled,
|
draftServersApiEnabled,
|
||||||
draftSpeedEnabled,
|
draftSpeedEnabled,
|
||||||
draftTrafficEnabled,
|
draftTrafficEnabled,
|
||||||
|
uptimeCollector?.scheduler?.jobs,
|
||||||
])
|
])
|
||||||
|
|
||||||
const schedulerJobCount = SCHEDULER_JOB_KEYS.length
|
const schedulerJobCount = SCHEDULER_JOB_KEYS.length
|
||||||
@@ -936,7 +1125,9 @@ export default function DataCollectionPage() {
|
|||||||
{
|
{
|
||||||
label: "Включено задач",
|
label: "Включено задач",
|
||||||
value: `${enabledJobsCount} / ${schedulerJobCount}`,
|
value: `${enabledJobsCount} / ${schedulerJobCount}`,
|
||||||
sub: "По переключателям на этой странице (до сохранения)",
|
sub: uptimeCollector?.scheduler?.jobs?.length
|
||||||
|
? "По сохранённым задачам планировщика"
|
||||||
|
: "По переключателям на этой странице",
|
||||||
icon: <CalendarClockIcon className="size-4 text-muted-foreground" />,
|
icon: <CalendarClockIcon className="size-4 text-muted-foreground" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1059,7 +1250,7 @@ export default function DataCollectionPage() {
|
|||||||
<CardHeader className="border-b border-border pb-4">
|
<CardHeader className="border-b border-border pb-4">
|
||||||
<CardTitle className="text-base">Планировщик сбора данных</CardTitle>
|
<CardTitle className="text-base">Планировщик сбора данных</CardTitle>
|
||||||
<CardDescription className="text-xs">
|
<CardDescription className="text-xs">
|
||||||
Интервалы и вкл/выкл по задачам. Сохранение отправляет настройки на бекенд и перезапускает таймеры.
|
Интервалы и вкл/выкл по задачам. Переключатель сразу сохраняет задачу на бекенде; кнопка ниже — интервалы и срок хранения.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-0 pb-0">
|
<CardContent className="px-0 pb-0">
|
||||||
@@ -1085,15 +1276,15 @@ export default function DataCollectionPage() {
|
|||||||
? draftTrafficEnabled
|
? draftTrafficEnabled
|
||||||
: jobKey === "servers_rest_ping"
|
: jobKey === "servers_rest_ping"
|
||||||
? draftServersApiEnabled
|
? draftServersApiEnabled
|
||||||
: jobKey === "uptime_resources"
|
: jobKey === "uptime_resources"
|
||||||
? draftResourcesEnabled
|
? draftResourcesEnabled
|
||||||
: jobKey === "uptime_ping"
|
: jobKey === "uptime_ping"
|
||||||
? draftPingEnabled
|
? draftPingEnabled
|
||||||
: jobKey === "uptime_speed"
|
: jobKey === "uptime_speed"
|
||||||
? draftSpeedEnabled
|
? draftSpeedEnabled
|
||||||
: jobKey === "certificates_renew"
|
: jobKey === "certificates_renew"
|
||||||
? draftCertRenewEnabled
|
? draftCertRenewEnabled
|
||||||
: draftInternetPathEnabled
|
: draftInternetPathEnabled
|
||||||
const iv = fixedSchedule
|
const iv = fixedSchedule
|
||||||
? String(j?.intervalSec ?? (jobKey === "gre_bgp" ? 30 : 20))
|
? String(j?.intervalSec ?? (jobKey === "gre_bgp" ? 30 : 20))
|
||||||
: jobKey === "traffic"
|
: jobKey === "traffic"
|
||||||
@@ -1152,15 +1343,10 @@ export default function DataCollectionPage() {
|
|||||||
<span className={fixedSchedule ? "inline-flex pointer-events-none opacity-50" : "inline-flex"}>
|
<span className={fixedSchedule ? "inline-flex pointer-events-none opacity-50" : "inline-flex"}>
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={en}
|
checked={en}
|
||||||
|
disabled={fixedSchedule || schedulerSaveBusy}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
if (fixedSchedule) return
|
if (fixedSchedule || schedulerSaveBusy) return
|
||||||
if (jobKey === "traffic") setDraftTrafficEnabled(v)
|
void handleJobEnabledChange(jobKey, v)
|
||||||
else if (jobKey === "servers_rest_ping") setDraftServersApiEnabled(v)
|
|
||||||
else if (jobKey === "uptime_resources") setDraftResourcesEnabled(v)
|
|
||||||
else if (jobKey === "uptime_ping") setDraftPingEnabled(v)
|
|
||||||
else if (jobKey === "uptime_speed") setDraftSpeedEnabled(v)
|
|
||||||
else if (jobKey === "certificates_renew") setDraftCertRenewEnabled(v)
|
|
||||||
else setDraftInternetPathEnabled(v)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -1282,68 +1468,8 @@ export default function DataCollectionPage() {
|
|||||||
setSchedulerSaveBusy(true)
|
setSchedulerSaveBusy(true)
|
||||||
setCollectorError(null)
|
setCollectorError(null)
|
||||||
try {
|
try {
|
||||||
const tInt = Math.max(5, Number.parseInt(trafficIntervalDraft, 10) || 30)
|
await persistSchedulerDrafts()
|
||||||
const tRet = Math.max(1, Number.parseInt(trafficRetentionDraft, 10) || 14)
|
|
||||||
const uRes = Math.max(5, Number.parseInt(uptimeResourceIntervalDraft, 10) || 300)
|
|
||||||
const uPing = Math.max(5, Number.parseInt(uptimeIntervalDraft, 10) || 15)
|
|
||||||
const uSpd = Math.max(10, Number.parseInt(uptimeSpeedIntervalDraft, 10) || 60)
|
|
||||||
const uRet = Math.max(1, Number.parseInt(uptimeRetentionDraft, 10) || 14)
|
|
||||||
const sApiInt = Math.max(10, Number.parseInt(serversApiIntervalDraft, 10) || 120)
|
|
||||||
const ipInt = Math.max(30, Number.parseInt(internetPathIntervalDraft, 10) || 300)
|
|
||||||
const certRenewInt = Math.max(300, Number.parseInt(certRenewIntervalDraft, 10) || 21600)
|
|
||||||
const renewBeforeDays = Math.max(1, Math.min(90, Number.parseInt(renewBeforeDaysDraft, 10) || 30))
|
|
||||||
const saveResults = await Promise.allSettled([
|
|
||||||
apiFetch("/api/traffic/settings", {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({
|
|
||||||
enabled: draftTrafficEnabled,
|
|
||||||
intervalSec: tInt,
|
|
||||||
retentionDays: tRet,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
apiFetch("/api/servers-api-ping/settings", {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({
|
|
||||||
enabled: draftServersApiEnabled,
|
|
||||||
intervalSec: sApiInt,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
apiFetch("/api/uptime/settings", {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({
|
|
||||||
resourcesEnabled: draftResourcesEnabled,
|
|
||||||
pingEnabled: draftPingEnabled,
|
|
||||||
speedEnabled: draftSpeedEnabled,
|
|
||||||
intervalSec: uRes,
|
|
||||||
probeIntervalSec: uPing,
|
|
||||||
speedIntervalSec: uSpd,
|
|
||||||
retentionDays: uRet,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
apiFetch("/api/internet-path/settings", {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({
|
|
||||||
enabled: draftInternetPathEnabled,
|
|
||||||
intervalSec: ipInt,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
apiFetch("/api/certificates/renew-settings", {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({
|
|
||||||
enabled: draftCertRenewEnabled,
|
|
||||||
intervalSec: certRenewInt,
|
|
||||||
renewBeforeDays,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
const saveErrors = collectSettledErrors(
|
|
||||||
saveResults,
|
|
||||||
["трафик", "серверы REST API", "uptime", "internet path", "сертификаты"],
|
|
||||||
)
|
|
||||||
await loadCollectors()
|
await loadCollectors()
|
||||||
if (saveErrors.length > 0) {
|
|
||||||
setCollectorError(`Не все настройки сохранились: ${saveErrors.join("; ")}`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setCollectorError(e instanceof Error ? e.message : "Не удалось сохранить")
|
setCollectorError(e instanceof Error ? e.message : "Не удалось сохранить")
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user