"use client" import { useCallback, useEffect, useState } from "react" import Link from "next/link" import { OpsPanel } from "@/components/ops-panel" import { FormField, FormToggle } from "@/components/form-kit" import { Alert, AlertDescription, AlertTitle } from "@/components/reui/alert" import { Badge } from "@/components/reui/badge" import { Button, buttonVariants } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { cn } from "@/lib/utils" import { getCertificateRenewSettings, putCertificateRenewSettings, } from "@/shared/api/certificates" import { toast } from "sonner" /** * Автообновление сертификатов через MM (ACME DNS-01). * Preview: https://reui.io/preview/base/settings-16 · https://reui.io/preview/base/settings-3 * Docs: https://reui.io/docs/components/base/frame · https://reui.io/docs/components/base/alert · https://reui.io/docs/components/base/badge */ export function CertificateRenewSettingsPanel({ backendUrl, liveReady, }: { backendUrl: string liveReady: boolean }) { const [enabled, setEnabled] = useState(true) const [intervalDraft, setIntervalDraft] = useState("21600") const [daysDraft, setDaysDraft] = useState("30") const [lastCollectedAt, setLastCollectedAt] = useState(null) const [lastError, setLastError] = useState(null) const [loaded, setLoaded] = useState(false) const [toggleBusy, setToggleBusy] = useState(false) const [saveBusy, setSaveBusy] = useState(false) const load = useCallback(async () => { if (!liveReady) return try { const s = await getCertificateRenewSettings(backendUrl) setEnabled(s.enabled) setIntervalDraft(String(s.intervalSec)) setDaysDraft(String(s.renewBeforeDays)) setLastCollectedAt(s.lastCollectedAt ?? null) setLastError(s.lastError ?? null) setLoaded(true) } catch (e) { setLoaded(true) toast.error(e instanceof Error ? e.message : "Не удалось загрузить настройки автообновления") } }, [backendUrl, liveReady]) useEffect(() => { queueMicrotask(() => { void load() }) }, [load]) async function handleEnabledChange(next: boolean) { if (!liveReady || toggleBusy) return const prev = enabled setEnabled(next) setToggleBusy(true) try { const saved = await putCertificateRenewSettings(backendUrl, { enabled: next }) setEnabled(saved.enabled) toast.success(next ? "Автообновление через MikrotikManager включено" : "Автообновление через MikrotikManager выключено") } catch (e) { setEnabled(prev) toast.error(e instanceof Error ? e.message : "Не удалось сохранить") } finally { setToggleBusy(false) } } async function handleSaveSchedule() { if (!liveReady || saveBusy) return const intervalSec = Math.max(300, Number.parseInt(intervalDraft, 10) || 21600) const renewBeforeDays = Math.max(1, Math.min(90, Number.parseInt(daysDraft, 10) || 30)) setSaveBusy(true) try { const saved = await putCertificateRenewSettings(backendUrl, { intervalSec, renewBeforeDays }) setIntervalDraft(String(saved.intervalSec)) setDaysDraft(String(saved.renewBeforeDays)) toast.success("Расписание автообновления сохранено") } catch (e) { toast.error(e instanceof Error ? e.message : "Не удалось сохранить расписание") } finally { setSaveBusy(false) } } const interactionsOff = !liveReady || toggleBusy || (liveReady && !loaded) return ( {!liveReady ? "нет backend" : enabled ? "Включено" : "Выключено"} } contentClassName="px-5 py-4 flex flex-col gap-4" >

Обновлять сертификаты из MM

Если ACME уже крутит RouterOS — выключите, чтобы не было двойного перевыпуска.

{!liveReady ? ( Нет подключения к API Переключатель станет активен, когда backend доступен. Планировщик читает тот же флаг, что и страница «Сбор данных». ) : enabled ? ( Не смешивайте с ACME RouterOS MM обновляет только сертификаты, выпущенные через эту страницу. Встроенный Let's Encrypt на устройстве для тех же имён лучше не включать одновременно. ) : ( Обновление отдано RouterOS Планировщик MM больше не проверяет срок и не перевыпускает сертификаты. Ручной выпуск и импорт остаются доступны. )}
setIntervalDraft(e.target.value)} disabled={!liveReady || saveBusy} /> setDaysDraft(e.target.value)} disabled={!liveReady || saveBusy} />
Журнал планировщика →
{lastCollectedAt || lastError ? (

Последний прогон:{" "} {lastCollectedAt ? new Date(lastCollectedAt).toLocaleString("ru-RU") : "ещё не было"} {lastError ? ` · ошибка: ${lastError}` : ""}

) : null}
) }