Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m36s
Docker images / frontend-image (push) Successful in 1m39s
Docker images / updater-image (push) Successful in 50s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
import { ApiClientError } from "@/shared/api/http-client"
|
|
import { configuredBackendUrl } from "@/lib/backend-url"
|
|
|
|
const MAX_RESTORE_BYTES = 512 * 1024 * 1024
|
|
|
|
function trimBaseUrl(baseUrl: string): string {
|
|
return baseUrl.replace(/\/$/, "")
|
|
}
|
|
|
|
function resolveDatabaseApiUrl(baseUrl: string, path: string): string {
|
|
if (configuredBackendUrl().kind === "same-origin") {
|
|
return path
|
|
}
|
|
return `${trimBaseUrl(baseUrl)}${path}`
|
|
}
|
|
|
|
function parseFilename(contentDisposition: string | null, fallback: string): string {
|
|
if (!contentDisposition) return fallback
|
|
const utfMatch = /filename\*=UTF-8''([^;]+)/i.exec(contentDisposition)
|
|
if (utfMatch?.[1]) {
|
|
try {
|
|
return decodeURIComponent(utfMatch[1])
|
|
} catch {
|
|
return utfMatch[1]
|
|
}
|
|
}
|
|
const plainMatch = /filename="([^"]+)"/i.exec(contentDisposition)
|
|
if (plainMatch?.[1]) return plainMatch[1]
|
|
return fallback
|
|
}
|
|
|
|
export async function downloadSystemDatabaseBackup(
|
|
baseUrl: string,
|
|
): Promise<{ blob: Blob; filename: string }> {
|
|
const res = await fetch(resolveDatabaseApiUrl(baseUrl, "/api/system/database/backup"))
|
|
if (!res.ok) {
|
|
const payload = await res.json().catch(() => undefined)
|
|
const msg =
|
|
typeof payload === "object" &&
|
|
payload !== null &&
|
|
"error" in payload &&
|
|
typeof (payload as { error?: unknown }).error === "string"
|
|
? (payload as { error: string }).error
|
|
: res.statusText
|
|
throw new ApiClientError(msg, res.status, payload)
|
|
}
|
|
const blob = await res.blob()
|
|
const filename = parseFilename(res.headers.get("Content-Disposition"), "mikrotik-manager.db")
|
|
return { blob, filename }
|
|
}
|
|
|
|
export async function restoreSystemDatabaseBackup(baseUrl: string, file: File): Promise<void> {
|
|
if (file.size > MAX_RESTORE_BYTES) {
|
|
throw new ApiClientError(
|
|
`Файл больше ${MAX_RESTORE_BYTES / (1024 * 1024)} МБ — уменьшите бэкап или обратитесь к администратору`,
|
|
413,
|
|
)
|
|
}
|
|
const res = await fetch(resolveDatabaseApiUrl(baseUrl, "/api/system/database/restore"), {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/octet-stream" },
|
|
body: file,
|
|
})
|
|
if (!res.ok) {
|
|
const payload = await res.json().catch(() => undefined)
|
|
const msg =
|
|
typeof payload === "object" &&
|
|
payload !== null &&
|
|
"error" in payload &&
|
|
typeof (payload as { error?: unknown }).error === "string"
|
|
? (payload as { error: string }).error
|
|
: res.statusText
|
|
throw new ApiClientError(msg, res.status, payload)
|
|
}
|
|
}
|