fix(ui): выровнять confirms и токены под ReUI PRO
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Successful in 1m31s
Docker images / frontend-image (push) Successful in 2m11s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 40s
Docker images / publish-release (push) Successful in 10s

Добавить ui-design-contract, AlertDialog вместо hand-roll, CodeExportSheet для preview фильтров, ReUI Badge и gap вместо space-y.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-06 00:12:57 +07:00
co-authored by Cursor
parent 66509b26bd
commit 5e0c16e808
7 changed files with 212 additions and 180 deletions
+28 -28
View File
@@ -4,7 +4,7 @@ import Link from "next/link"
import { useCallback, useEffect, useMemo, useState } from "react"
import { PageHeader } from "@/components/page-header"
import { FormToggle } from "@/components/form-kit"
import { Badge } from "@/components/ui/badge"
import { Badge } from "@/components/reui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { Frame, FramePanel } from "@/components/reui/frame"
import { IconTile } from "@/components/reui/icon-tile"
@@ -104,7 +104,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "traffic") {
const t = snap as TrafficRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{t.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущий сбор трафика ещё выполнялся.</p>
) : null}
@@ -126,7 +126,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "uptime_resources") {
const u = snap as ResourcesRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{u.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: сбор ресурсов уже выполняется.</p>
) : null}
@@ -148,7 +148,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "servers_rest_ping") {
const s = snap as ServersRestPingRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{s.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущая проверка API ещё выполнялась.</p>
) : null}
@@ -171,7 +171,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "uptime_ping") {
const p = snap as PingRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{p.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: сбор ping уже выполняется.</p>
) : null}
@@ -196,7 +196,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "uptime_speed") {
const s = snap as SpeedScheduledRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
<p className="text-xs text-muted-foreground">
Прогоны speed на <span className="font-mono tabular-nums">{new Date(s.sampledAt).toLocaleString("ru-RU")}</span> — по очереди для каждой включённой пробы
</p>
@@ -209,7 +209,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "gre_bgp") {
const g = snap as GreBgpSnapshotRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{g.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущий сбор GRE/BGP ещё выполнялся.</p>
) : null}
@@ -235,7 +235,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
</div>
</dl>
{g.errors?.length ? (
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 space-y-1">
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 flex flex-col gap-1">
{g.errors.map((e, i) => (
<p key={i} className="break-words">
{e}
@@ -249,7 +249,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "internet_path") {
const p = snap as InternetPathRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
<p className="text-xs text-muted-foreground">
Снимок internet-path на{" "}
<span className="font-mono tabular-nums">{new Date(p.sampledAt).toLocaleString("ru-RU")}</span>
@@ -276,7 +276,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "certificates_renew") {
const c = snap as CertificatesRenewRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
{c.skipped ? (
<p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущая проверка ещё выполнялась или задача отключена.</p>
) : null}
@@ -299,7 +299,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
</div>
</dl>
{c.errors.length ? (
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 space-y-1">
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 flex flex-col gap-1">
{c.errors.map((e, i) => (
<p key={i} className="break-words">{e}</p>
))}
@@ -311,7 +311,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "backups") {
const b = snap as BackupsRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
<dl className="grid grid-cols-2 gap-3 text-xs sm:grid-cols-4">
<div>
<dt className="text-muted-foreground">Слот расписания</dt>
@@ -331,7 +331,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
</div>
</dl>
{b.errors?.length ? (
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 space-y-1">
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 flex flex-col gap-1">
{b.errors.map((e, i) => (
<p key={i} className="break-words">{e}</p>
))}
@@ -343,7 +343,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
if (snap.job === "alert_engine") {
const a = snap as AlertEngineRunSnapshot
return (
<div className="space-y-3">
<div className="flex flex-col gap-3">
<p className="text-xs text-muted-foreground">
Снимок на{" "}
<span className="font-mono tabular-nums">{new Date(a.sampledAt).toLocaleString("ru-RU")}</span>
@@ -370,7 +370,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
{a.errors?.length ? (
<Alert variant="destructive" className="py-2">
<AlertCircleIcon />
<AlertDescription className="space-y-1 text-xs">
<AlertDescription className="flex flex-col gap-1 text-xs">
{a.errors.map((e, i) => (
<p key={i} className="break-words">
{e}
@@ -380,7 +380,7 @@ function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
</Alert>
) : null}
{a.ruleDiag && a.ruleDiag.length > 0 ? (
<div className="rounded-md border border-border bg-muted/20 px-3 py-2 space-y-2">
<div className="rounded-md border border-border bg-muted/20 px-3 py-2 flex flex-col gap-2">
<p className="text-[11px] font-medium text-muted-foreground">По правилам (почему не ушло в Telegram)</p>
<DataPageCard className="border-0 shadow-none bg-transparent">
<AlertEngineRuleDiagGrid ruleDiag={a.ruleDiag} />
@@ -398,39 +398,39 @@ function RunRowDetail({ r }: { r: SchedulerRunRowDto }) {
const jobDesc = SCHEDULER_JOB_DESCRIPTIONS[r.jobKey] ?? "—"
const snapshot = useMemo(() => parseSchedulerRunSnapshot(r.resultJson ?? null), [r.resultJson])
return (
<div className="space-y-4 text-sm">
<div className="flex flex-col gap-4 text-sm">
<dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-3">
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">ID записи</dt>
<dd className="font-mono text-xs break-all bg-muted/60 rounded-md px-2 py-1.5 border border-border">{r.id}</dd>
</div>
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Ключ задачи</dt>
<dd className="font-mono text-xs">{r.jobKey}</dd>
</div>
<div className="sm:col-span-2 space-y-1">
<div className="sm:col-span-2 flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Название и назначение</dt>
<dd>
<span className="font-medium">{jobTitle}</span>
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">{jobDesc}</p>
</dd>
</div>
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Старт</dt>
<dd className="tabular-nums text-xs">{new Date(r.startedAt).toLocaleString("ru-RU")}</dd>
</div>
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Завершение</dt>
<dd className="tabular-nums text-xs">{new Date(r.finishedAt).toLocaleString("ru-RU")}</dd>
</div>
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Длительность</dt>
<dd className="tabular-nums">
<span className="font-mono">{r.durationMs}</span> мс
<span className="text-muted-foreground text-xs ml-2">({fmtMs(r.durationMs)})</span>
</dd>
</div>
<div className="space-y-1">
<div className="flex flex-col gap-1">
<dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Результат</dt>
<dd>
<Badge
@@ -447,7 +447,7 @@ function RunRowDetail({ r }: { r: SchedulerRunRowDto }) {
</div>
</dl>
{r.error ? (
<div className="space-y-1.5">
<div className="flex flex-col gap-1.5">
<p className="text-xs font-medium text-destructive">Текст ошибки</p>
<pre
className="text-xs font-mono whitespace-pre-wrap break-words rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 max-h-48 overflow-y-auto"
@@ -461,7 +461,7 @@ function RunRowDetail({ r }: { r: SchedulerRunRowDto }) {
<Separator />
<div className="space-y-2">
<div className="flex flex-col gap-2">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Результаты измерений</p>
{snapshot ? (
<SnapshotTables snap={snapshot} />
@@ -1090,7 +1090,7 @@ export default function DataCollectionPage() {
</div>
<DataCollectionSchedulerDataGrid rows={schedulerGridRows} />
<Separator />
<div className="space-y-3 px-5 py-4">
<div className="flex flex-col gap-3 px-5 py-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<p className="text-xs text-muted-foreground mb-1.5">Хранение сэмплов трафика (дней)</p>
@@ -1142,7 +1142,7 @@ export default function DataCollectionPage() {
{schedulerSaveBusy ? <LoaderCircleIcon className="size-4 animate-spin mr-2" /> : null}
Сохранить настройки планировщика
</Button>
<div className="text-xs text-muted-foreground space-y-0.5 pt-1 border-t border-border/60">
<div className="text-xs text-muted-foreground flex flex-col gap-0.5 pt-1 border-t border-border/60">
<p>
Трафик — последний сбор:{" "}
{trafficCollector?.lastCollectedAt