Files
MikrotikManager/components/mcp/database-restore.tsx
T

46 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import type { McpApi } from "@/shared/api/mcp"
export function DatabaseRestore({ api }: { api: McpApi }) {
const [connections, setConnections] = useState<{ id: string; name: string }[]>([])
const [connectionId, setConnectionId] = useState("")
const [file, setFile] = useState<File | null>(null)
const [archive, setArchive] = useState<Awaited<ReturnType<McpApi["uploadRestoreArchive"]>> | null>(null)
const [operationId, setOperationId] = useState("")
const [result, setResult] = useState<unknown>(null)
const [busy, setBusy] = useState(false)
const [error, setError] = useState("")
useEffect(() => {
const abort = new AbortController()
void api.connections(abort.signal).then(data => setConnections(data.connections.filter(item => item.policy.scope.allowGlobal && item.policy.scope.operationIds.includes("database.restore"))), failure => { if (!abort.signal.aborted) setError(String(failure)) })
return () => abort.abort()
}, [api])
const run = async (work: () => Promise<unknown>) => {
setBusy(true); setError("")
try { setResult(await work()) } catch (failure) { setError(failure instanceof Error ? failure.message : String(failure)) } finally { setBusy(false) }
}
return <section className="flex flex-col gap-3 rounded-xl border p-4" aria-labelledby="mcp-database-restore-title">
<h2 id="mcp-database-restore-title" className="font-semibold">Восстановление БД через MCP</h2>
<p className="text-sm text-muted-foreground">Архив PostgreSQL загружает администратор. Модель получает ссылку и SHA-256. Выполнение требует отдельного подтверждения в планах. После восстановления прежний MCP-доступ отзывается, а результат хранится во внешней квитанции.</p>
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
<label className="flex flex-col gap-1 text-sm">Подключение для восстановления
<select className="h-9 rounded-md border bg-background px-3" value={connectionId} onChange={event => setConnectionId(event.target.value)}><option value="">Выберите свой глобальный допуск</option>{connections.map(item => <option key={item.id} value={item.id}>{item.name}</option>)}</select>
</label>
<label className="flex flex-col gap-1 text-sm">Архив PostgreSQL, до 64 МиБ
<Input type="file" accept=".dump,.backup" onChange={event => { setFile(event.target.files?.[0] ?? null); setArchive(null); setResult(null) }} />
</label>
<div className="flex flex-wrap gap-2">
<Button variant="outline" disabled={busy || !file || file.size > 64 * 1024 * 1024} onClick={() => { if (file) void run(async () => { const data = await api.uploadRestoreArchive(file); setArchive(data); return data }) }}>Загрузить архив</Button>
<Button disabled={busy || !archive || !connectionId} onClick={() => { if (archive) void run(() => api.restorePlan(connectionId, archive.archiveRef, archive.sha256)) }}>Подготовить план восстановления БД</Button>
</div>
{archive && <p className="break-all text-xs text-muted-foreground">SHA-256: {archive.sha256} · Допуск до {new Date(archive.expiresAt).toLocaleString()}</p>}
<label className="flex flex-col gap-1 text-sm">ID операции восстановления БД<Input value={operationId} onChange={event => setOperationId(event.target.value)} /></label>
<Button className="self-start" variant="outline" disabled={busy || !operationId} onClick={() => { void run(() => api.restoreReceipt(operationId)) }}>Прочитать внешнюю квитанцию</Button>
{result !== null && <pre role="status" className="max-h-60 overflow-auto whitespace-pre-wrap break-all text-xs">{JSON.stringify(result, null, 2)}</pre>}
</section>
}