fix(ui): выровнять chrome Frame и убрать Card-оболочки
Docker images / prepare-release (push) Successful in 4s
Docker images / backend-image (push) Failing after 2m36s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 55s
Docker images / publish-release (push) Skipped
Docker images / prepare-release (push) Successful in 4s
Docker images / backend-image (push) Failing after 2m36s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 55s
Docker images / publish-release (push) Skipped
Подключить App Switcher, NavUser, OpsPanel и AlertDialog вместо Card-shell. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+71
-50
@@ -4,11 +4,21 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { FormToggle } from "@/components/form-kit"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { OpsPanel } from "@/components/ops-panel"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import {
|
||||
Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter,
|
||||
} from "@/components/ui/sheet"
|
||||
@@ -841,12 +851,9 @@ function TelegramCard({ cfg, onChange, tokenConfigured, liveSaveBusy, onSaveTele
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3 pt-4 px-4">
|
||||
<CardTitle className="text-sm flex items-center justify-between">
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="text-base">✈️</span> Telegram
|
||||
</span>
|
||||
<OpsPanel
|
||||
title={<span className="flex items-center gap-2"><span className="text-base">✈️</span> Telegram</span>}
|
||||
headerRight={
|
||||
<span className={cn(
|
||||
"flex items-center gap-1.5 text-[11px] font-normal px-2 py-0.5 rounded-full",
|
||||
cfg.connected
|
||||
@@ -856,9 +863,9 @@ function TelegramCard({ cfg, onChange, tokenConfigured, liveSaveBusy, onSaveTele
|
||||
<span className={cn("size-1.5 rounded-full", cfg.connected ? "bg-emerald-500" : "bg-muted-foreground")} />
|
||||
{cfg.connected ? "Подключён" : "Не настроен"}
|
||||
</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 space-y-3">
|
||||
}
|
||||
contentClassName="px-4 pb-4 flex flex-col gap-3"
|
||||
>
|
||||
|
||||
{/* bot token */}
|
||||
<div>
|
||||
@@ -943,8 +950,7 @@ function TelegramCard({ cfg, onChange, tokenConfigured, liveSaveBusy, onSaveTele
|
||||
: testResult === "fail" ? "Ошибка отправки"
|
||||
: "Отправить тестовое сообщение"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</OpsPanel>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -952,11 +958,7 @@ function TelegramCard({ cfg, onChange, tokenConfigured, liveSaveBusy, onSaveTele
|
||||
|
||||
function HistoryCard({ entries }: { entries: HistoryEntry[] }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
<CardTitle className="text-sm">Журнал срабатываний</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<OpsPanel title="Журнал срабатываний" contentClassName="px-4 pb-4">
|
||||
{entries.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground py-2">Нет срабатываний</p>
|
||||
) : (
|
||||
@@ -983,8 +985,7 @@ function HistoryCard({ entries }: { entries: HistoryEntry[] }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</OpsPanel>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2363,6 +2364,7 @@ export default function AlertsPage() {
|
||||
const [meta, setMeta] = useState<AlertsMeta | null>(null)
|
||||
|
||||
const [sheetOpen, setSheetOpen] = useState(false)
|
||||
const [pendingPresetReplaceId, setPendingPresetReplaceId] = useState<string | null>(null)
|
||||
const [sheetSnap, setSheetSnap] = useState<RuleFormSnapshot | null>(null)
|
||||
const [sheetEditId, setSheetEditId] = useState<string | null>(null)
|
||||
const [sheetKey, setSheetKey] = useState(0)
|
||||
@@ -2661,9 +2663,6 @@ export default function AlertsPage() {
|
||||
|
||||
const applyPresetReplace = useCallback(
|
||||
(presetId: string) => {
|
||||
if (typeof window !== "undefined" && !window.confirm("Заменить все текущие правила выбранным пресетом?")) {
|
||||
return
|
||||
}
|
||||
const def = ALERT_PRESETS.find((p) => p.id === presetId)
|
||||
if (!def) return
|
||||
const built = def.build(serverNamesForPresets)
|
||||
@@ -2947,11 +2946,7 @@ export default function AlertsPage() {
|
||||
<CollapsibleContent>
|
||||
<div className="border-t border-border/60 px-4 pb-4 pt-3">
|
||||
<div className="grid gap-5 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
<CardTitle className="text-sm">Пресеты</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 flex flex-col gap-3">
|
||||
<OpsPanel title="Пресеты" contentClassName="px-4 pb-4 flex flex-col gap-3">
|
||||
<p className="text-[10px] text-muted-foreground leading-snug">
|
||||
Быстро добавить набор правил из каталога серверов (демо или live meta).
|
||||
</p>
|
||||
@@ -2979,19 +2974,18 @@ export default function AlertsPage() {
|
||||
variant="outline"
|
||||
className="h-7 text-xs"
|
||||
disabled={rulesSaveBusy || (isLive && backendStatus === false)}
|
||||
onClick={() => applyPresetReplace(p.id)}
|
||||
onClick={() => setPendingPresetReplaceId(p.id)}
|
||||
>
|
||||
Заменить все
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</OpsPanel>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between gap-2 pb-2 pt-4 px-4">
|
||||
<CardTitle className="text-sm">Группы ANY / ALL</CardTitle>
|
||||
<OpsPanel
|
||||
title="Группы ANY / ALL"
|
||||
headerRight={
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -3002,8 +2996,9 @@ export default function AlertsPage() {
|
||||
>
|
||||
<PlusIcon className="size-3.5" />Группа
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 flex flex-col gap-3">
|
||||
}
|
||||
contentClassName="px-4 pb-4 flex flex-col gap-3"
|
||||
>
|
||||
{groups.length === 0 ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Групп пока нет. Создайте группу и назначьте правилам её в форме правила — движок отправит одно сообщение по логике ANY или ALL.
|
||||
@@ -3074,8 +3069,7 @@ export default function AlertsPage() {
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</OpsPanel>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
@@ -3085,13 +3079,14 @@ export default function AlertsPage() {
|
||||
<div className="grid grid-cols-1 xl:grid-cols-[1fr_300px] gap-5 items-start">
|
||||
|
||||
{/* ── rules card ── */}
|
||||
<Card>
|
||||
<CardHeader className="pb-0 pt-4 px-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
Правила оповещения
|
||||
{rulesSaveBusy && <LoaderCircleIcon className="size-3.5 animate-spin text-muted-foreground" aria-hidden />}
|
||||
</CardTitle>
|
||||
<OpsPanel
|
||||
title={
|
||||
<span className="flex items-center gap-2">
|
||||
Правила оповещения
|
||||
{rulesSaveBusy && <LoaderCircleIcon className="size-3.5 animate-spin text-muted-foreground" aria-hidden />}
|
||||
</span>
|
||||
}
|
||||
headerRight={
|
||||
<Input
|
||||
placeholder="Поиск…"
|
||||
value={search}
|
||||
@@ -3100,10 +3095,9 @@ export default function AlertsPage() {
|
||||
name="alert-rules-search"
|
||||
className="h-7 text-xs max-w-[200px]"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-0 pb-0 pt-2">
|
||||
}
|
||||
contentClassName="px-0 pb-0 pt-2"
|
||||
>
|
||||
{filteredRules.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground/40">
|
||||
<BellOffIcon className="size-8 mb-2 opacity-40" />
|
||||
@@ -3138,8 +3132,7 @@ export default function AlertsPage() {
|
||||
<PlusIcon className="size-3" />Добавить правило
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</OpsPanel>
|
||||
|
||||
{/* ── right sidebar ── */}
|
||||
<div className="flex flex-col gap-5">
|
||||
@@ -3172,6 +3165,34 @@ export default function AlertsPage() {
|
||||
onTestRuleTelegram={isLive ? testRuleTelegramFromForm : undefined}
|
||||
testTelegramDisabled={isLive && backendStatus === false}
|
||||
/>
|
||||
|
||||
<AlertDialog
|
||||
open={pendingPresetReplaceId != null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setPendingPresetReplaceId(null)
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Заменить все правила?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Текущие правила будут заменены выбранным пресетом. Это нельзя отменить.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Отмена</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
if (pendingPresetReplaceId) applyPresetReplace(pendingPresetReplaceId)
|
||||
setPendingPresetReplaceId(null)
|
||||
}}
|
||||
>
|
||||
Заменить
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user