"use client" import { Suspense, useMemo, useState } from "react" import { useSearchParams } from "next/navigation" import { readCertificatesUrl, patchCertificatesUrl, type CertificateStatusFilter } from "@/lib/certificates-url-state" import { PageHeader } from "@/components/page-header" import { FormField, FormToggle, SectionTitle } from "@/components/form-kit" import { routerCertificates, servers as mockServers } from "@/lib/data" import type { Server } from "@/lib/data" import type { CertificateDto } from "@mmapp/contracts/certificates" import { OpsPanel } from "@/components/ops-panel" import { DataPageCard } from "@/components/data-page-card" import { DataPageToolbar } from "@/components/data-page-toolbar" import { CertificatesDataGrid } from "@/components/data-grids/certificates-data-grid" import { CertificateAcmeSettings } from "@/components/certificates/certificate-acme-settings" import { CertificateRenewSettingsPanel } from "@/components/certificates/certificate-renew-settings" import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid" import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout" import { ALL_SERVERS_ID, type ServerTileItem } from "@/components/server-tile-rail" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, SheetClose, } from "@/components/ui/sheet" import { cn } from "@/lib/utils" import { useDataSource } from "@/lib/data-source" import { can } from "@/lib/auth" import { useCertificateCatalog } from "@/lib/use-certificate-catalog" import type { CertificateView } from "@/lib/certificate-view" import { toFrontendServer } from "@/entities/server/model/mappers" import { useCertificateIssue } from "@/lib/use-certificate-issue" import { toast } from "sonner" import { ShieldCheckIcon, BadgeCheckIcon, AlertTriangleIcon, AlertCircleIcon, PlusIcon, RefreshCwIcon, UploadIcon, } from "lucide-react" import { Stepper, StepperContent, StepperIndicator, StepperItem, StepperNav, StepperPanel, StepperSeparator, StepperTitle, StepperTrigger, } from "@/components/reui/stepper" function mockToDto(cert: (typeof routerCertificates)[number]): CertificateDto { return { id: cert.id, name: cert.name, serverId: cert.serverId, commonName: cert.commonName, sans: cert.sans, issuedBy: cert.issuedBy, validFrom: cert.validFrom, validUntil: cert.validUntil, daysLeft: cert.daysLeft, keySize: cert.keySize, usage: cert.usage, trusted: cert.trusted, status: cert.status, } } function CertPartAlertExpired({ expired }: { expired: CertificateView[] }) { return (
{expired.length} {expired.length === 1 ? "истёкший сертификат" : "истёкших сертификата"}
{expired.map((c) => c.name).join(", ")} — требуют обновления
{expiring.length} {expiring.length === 1 ? "сертификат истекает" : "сертификата истекают"} в течение 30 дней
{expiring.map((c) => `${c.name} (${c.daysLeft}д)`).join(", ")}
{b.title}
{b.lines.join("\n")}
Let's Encrypt · DNS-01 (Cloudflare)
TXT-запись создаётся в Cloudflare, сертификат импортируется на выбранный RouterOS.
Trust store · www
Веб-интерфейс и HTTPS-сервисы
Trust store · api
REST API и управление
Проверьте параметры
Загрузка настроек источника…
returnМоковые данные
} {urlError &&Выбранный сервер недоступен в каталоге сертификатов. Выберите другой сервер.
} {isLive && backendStatus === false && ({catalog.hasServers ? "Последние серверы сохранены. " : "Каталог серверов пока неизвестен. "}{serverError}
} {isLive && !canRead &&Нет права mm:certificates:read.
} {isLive && !canReadServers &&Нет права mm:servers:read. Выбор устройства для выпуска недоступен.
} {isLive && catalog.collectedAt &&Последнее чтение каталога: {new Date(catalog.collectedAt).toLocaleString("ru-RU")}
} {isLive && Object.values(snapshots).filter(snapshot => selectedServerId === ALL_SERVERS_ID || snapshot.latest.serverId === selectedServerId).map(snapshot =>{snapshot.latest.serverName}: {snapshot.latest.error}. {snapshot.confirmed ? "Последние сертификаты сохранены." : "Сертификаты пока неизвестны."}
}{snapshot.latest.serverName}: {snapshot.confirmed ? "Подтверждённые данные от " + new Date(snapshot.confirmed.collectedAt).toLocaleString("ru-RU") : "Успешного чтения нет"}
Сводка неполная: часть устройств, статусов или сроков неизвестна. Таблица показывает доступные снимки.
}Исправьте контекст ссылки
: isLive && scopedCerts.length === 0 && !scopeKnown ?{loading ? "Загрузка сертификатов…" : "Список сертификатов пока неизвестен"}
:{issue.error || issue.storageError}
} {issue.checkpoint &&Закрытие формы не отменяет серверную задачу. Её можно проверить повторно.
} {issue.checkpoint &&Задача {issue.checkpoint.request.requestId}: {issue.checkpoint.job ? ({ queued: "В очереди", running: "Выполняется", done: "Завершена", failed: "Ошибка" }[issue.checkpoint.job.status]) + " · " + issue.checkpoint.job.step : issue.checkpoint.accepted ? "Принята; результат пока неизвестен" : "Принятие запроса пока не подтверждено"}. Сервер {issue.checkpoint.request.serverId}, сертификат {issue.checkpoint.request.certName}.
}