fix(ui): подтверждать удаление проб и изменение звезды
This commit is contained in:
1 parent
4f235058be
commit
ce14f5de3e
4 files changed
+136
-47
No files matched your search
+57
-45
@@ -13,6 +13,7 @@ import { FormField, FormToggle, SegmentedControl } from "@/components/form-kit"
|
||||
import { Frame, FramePanel } from "@/components/reui/frame"
|
||||
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter } from "@/components/ui/alert-dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { Flag } from "@/components/flag"
|
||||
@@ -1056,6 +1057,7 @@ function ResourcesTab({ resources, serversList, liveApi }: { resources: ServerRe
|
||||
|
||||
function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId: string | null; allProbesHref: string }) {
|
||||
const { mode, backendUrl, backendStatus, checkBackend } = useDataSource()
|
||||
const [deleteTarget, setDeleteTarget] = useState<{ kind: "ping" | "speed"; id: string; label: string } | null>(null)
|
||||
const draftProbeId = useRef("")
|
||||
const [probeSaving, setProbeSaving] = useState(false)
|
||||
const [probeSaveError, setProbeSaveError] = useState<string | null>(null)
|
||||
@@ -1498,40 +1500,26 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
})
|
||||
}, [apiFetch, liveApi])
|
||||
|
||||
const toggleDashboardStar = useCallback((id: string) => {
|
||||
const toggleDashboardStar = (id: string) => {
|
||||
if (!requireWrite()) return
|
||||
const cur = probes.find((p) => p.id === id)
|
||||
const cur = probes.find(p => p.id === id)
|
||||
if (!cur) return
|
||||
const nextVal = !cur.showOnDashboard
|
||||
if (liveApi) {
|
||||
overviewReqRef.current += 1
|
||||
}
|
||||
setProbes((prev) => prev.map((p) => (p.id === id ? { ...p, showOnDashboard: nextVal } : p)))
|
||||
if (liveApi) {
|
||||
void apiFetch(`/api/uptime/probes/${encodeURIComponent(id)}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
void saveProbeForm(async () => {
|
||||
if (liveApi) await apiFetch("/api/uptime/probes/" + encodeURIComponent(id), {
|
||||
method: "PATCH", signal: AbortSignal.timeout(30_000),
|
||||
body: JSON.stringify({ showOnDashboard: nextVal }),
|
||||
})
|
||||
.then(() => loadLiveOverview())
|
||||
.then(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new Event(UPTIME_PROBES_CHANGED))
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setProbes((prev) => prev.map((p) => (p.id === id ? { ...p, showOnDashboard: cur.showOnDashboard } : p)))
|
||||
})
|
||||
} else {
|
||||
const s = readMockDashboardStarIds()
|
||||
if (nextVal) s.add(id)
|
||||
else s.delete(id)
|
||||
writeMockDashboardStarIds(s)
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new Event(UPTIME_PROBES_CHANGED))
|
||||
}, () => {
|
||||
setProbes(prev => prev.map(p => p.id === id ? { ...p, showOnDashboard: nextVal } : p))
|
||||
if (!liveApi) {
|
||||
const ids = readMockDashboardStarIds()
|
||||
if (nextVal) ids.add(id); else ids.delete(id)
|
||||
writeMockDashboardStarIds(ids)
|
||||
}
|
||||
}
|
||||
}, [probes, liveApi, apiFetch, loadLiveOverview, requireWrite])
|
||||
window.dispatchEvent(new Event(UPTIME_PROBES_CHANGED))
|
||||
})
|
||||
}
|
||||
|
||||
const persistSpeedProbes = useCallback(async (rows: SpeedProbeRow[]) => {
|
||||
if (!liveApi) return
|
||||
@@ -1584,18 +1572,12 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
void saveProbeForm(() => persistProbes(next), () => setProbes(next))
|
||||
}
|
||||
|
||||
const deleteProbe = (id: string) => {
|
||||
const deleteProbe = (id: string) => {
|
||||
if (!requireWrite()) return
|
||||
if (!liveApi) {
|
||||
const s = readMockDashboardStarIds()
|
||||
s.delete(id)
|
||||
writeMockDashboardStarIds(s)
|
||||
}
|
||||
setProbes((prev) => {
|
||||
const next = prev.filter((x) => x.id !== id)
|
||||
void persistProbes(next).catch(error => setOpError(serverOperationError(error)))
|
||||
return next
|
||||
})
|
||||
const probe = probes.find(p => p.id === id)
|
||||
if (!probe) return
|
||||
setProbeSaveError(null)
|
||||
setDeleteTarget({ kind: "ping", id, label: probe.name + " → " + probe.target })
|
||||
}
|
||||
|
||||
const toggleCollapse = (serverId: string) =>
|
||||
@@ -1943,10 +1925,27 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
|
||||
const deleteSpeedProbe = (id: string) => {
|
||||
if (!requireWrite()) return
|
||||
setSpeedProbes((prev) => {
|
||||
const next = prev.filter((p) => p.id !== id)
|
||||
void persistSpeedProbes(next).catch(error => setSpeedError(serverOperationError(error)))
|
||||
return next
|
||||
const probe = speedProbes.find(p => p.id === id)
|
||||
if (!probe) return
|
||||
const name = (id: string) => allServers.find(s => s.id === id)?.name ?? id
|
||||
setProbeSaveError(null)
|
||||
setDeleteTarget({ kind: "speed", id, label: name(probe.srcServerId) + " → " + name(probe.dstServerId) })
|
||||
}
|
||||
|
||||
const confirmProbeDelete = () => {
|
||||
if (!deleteTarget || !requireWrite()) return
|
||||
const { kind, id } = deleteTarget
|
||||
const nextPing = probes.filter(p => p.id !== id)
|
||||
const nextSpeed = speedProbes.filter(p => p.id !== id)
|
||||
void saveProbeForm(() => kind === "ping" ? persistProbes(nextPing) : persistSpeedProbes(nextSpeed), () => {
|
||||
if (kind === "ping") {
|
||||
setProbes(nextPing)
|
||||
if (!liveApi) {
|
||||
const ids = readMockDashboardStarIds(); ids.delete(id); writeMockDashboardStarIds(ids)
|
||||
}
|
||||
window.dispatchEvent(new Event(UPTIME_PROBES_CHANGED))
|
||||
} else setSpeedProbes(nextSpeed)
|
||||
setDeleteTarget(null)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2002,9 +2001,9 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
/>
|
||||
|
||||
{!canWrite && <ContextAlert variant="info" role="status"><ContextTitle>Только просмотр</ContextTitle><ContextDescription>Изменение проб и запуск измерений требуют разрешения на запись. Обновление данных загружает сохранённые результаты.</ContextDescription></ContextAlert>}
|
||||
{permissionError && !sheetOpen && !speedSheetOpen && <ContextAlert variant="destructive" role="alert"><ContextDescription>{permissionError}</ContextDescription></ContextAlert>}
|
||||
{permissionError && !sheetOpen && !speedSheetOpen && !deleteTarget && <ContextAlert variant="destructive" role="alert"><ContextDescription>{permissionError}</ContextDescription></ContextAlert>}
|
||||
|
||||
{probeSaveError && !sheetOpen && !speedSheetOpen && <ContextAlert variant="destructive" role="alert"><ContextDescription>{probeSaveError}</ContextDescription></ContextAlert>}
|
||||
{probeSaveError && !sheetOpen && !speedSheetOpen && !deleteTarget && <ContextAlert variant="destructive" role="alert"><ContextDescription>{probeSaveError}</ContextDescription></ContextAlert>}
|
||||
|
||||
{/* ── Live indicator ── */}
|
||||
{!isPaused && (
|
||||
@@ -2957,6 +2956,19 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
<AlertDialog open={deleteTarget !== null} onOpenChange={open => { if (!open && !savingRef.current) setDeleteTarget(null) }}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Удалить пробу?</AlertDialogTitle>
|
||||
<AlertDialogDescription>Проба «{deleteTarget?.label}» будет удалена из мониторинга.</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
{(probeSaveError || permissionError) && <p role="alert" className="text-sm text-destructive">{probeSaveError || permissionError}</p>}
|
||||
<AlertDialogFooter>
|
||||
<Button type="button" variant="outline" disabled={probeSaving} onClick={() => setDeleteTarget(null)}>Отмена</Button>
|
||||
<Button type="button" variant="destructive" disabled={probeSaving || !canWrite} onClick={confirmProbeDelete}>{probeSaving ? "Удаление…" : "Удалить"}</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user