PageSourceSearch

https://www.hamienet.com/assets/BackupManager-C0vSVQLp.js

js hamienet.com collected 2026-10-02 05:01:31 UTC 79,767 bytes, 1,331 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { d$ as Archive, b0 as FileText, cM as RefreshCw, ci as Database, fI as Cloud, b9 as Settings, dO as Calendar, cd as Save, a_ as CircleCheck, ea as CircleX, b5 as Trash2, c4 as RotateCcw, cB as ChevronUp, cC as ChevronDown, bf as Search } from "./vendor-D0P4XgNj.js";
3import "./vendor-i18n-BC7ZMXWQ.js";
4const BackupManager = () => {
5  var _a;
6  const [backups, setBackups] = reactExports.useState([]);
7  const [selectedBackup, setSelectedBackup] = reactExports.useState(null);
8  const [files, setFiles] = reactExports.useState([]);
9  const [selectedFile, setSelectedFile] = reactExports.useState(null);
10  const [fileContent, setFileContent] = reactExports.useState("");
11  const [loading, setLoading] = reactExports.useState(false);
12  const [creating, setCreating] = reactExports.useState(false);
13  const [backupProgress, setBackupProgress] = reactExports.useState(null);
14  const [savingSchedule, setSavingSchedule] = reactExports.useState(false);
15  const [scheduleExpanded, setScheduleExpanded] = reactExports.useState(false);
16  const [dbStats, setDbStats] = reactExports.useState(null);
17  const [statsTitle, setStatsTitle] = reactExports.useState("Live Database Statistics");
18  const [showStatsModal, setShowStatsModal] = reactExports.useState(false);
19  const [sortConfig, setSortConfig] = reactExports.useState(null);
20  const [activeTab, setActiveTab] = reactExports.useState("database");
21  const [compareModalOpen, setCompareModalOpen] = reactExports.useState(false);
22  const [diffData, setDiffData] = reactExports.useState(null);
23  const [settingsRestoreModalOpen, setSettingsRestoreModalOpen] = reactExports.useState(false);
24  const [settingsRestoreData, setSettingsRestoreData] = reactExports.useState(null);
25  const [selectedSettingsSections, setSelectedSettingsSections] = reactExports.useState([]);
26  const [viewingTable, setViewingTable] = reactExports.useState(null);
27  const [tableData, setTableData] = reactExports.useState(null);
28  const [isTableLoading, setIsTableLoading] = reactExports.useState(false);
29  const [tableSearchQuery, setTableSearchQuery] = reactExports.useState("");
30  const [tableSearchField, setTableSearchField] = reactExports.useState("ALL");
31  const [tableCurrentPage, setTableCurrentPage] = reactExports.useState(1);
32  const rowsPerPage = 50;
33  const [schedule, setSchedule] = reactExports.useState({
34    enabled: true,
35    cronExpression: "0 2 * * *",
36    keepDaily: 7,
37    keepWeekly: 4,
38    keepMonthly: 12
39  });
40  const [settingsSchedule, setSettingsSchedule] = reactExports.useState({
41    enabled: true,
42    cronExpression: "0 2 * * *",
43    keepDaily: 7,
44    keepWeekly: 4,
45    keepMonthly: 12
46  });
47  const [cfTokens, setCfTokens] = reactExports.useState({ dnsToken: "", tunnelToken: "" });
48  const [savingCfTokens, setSavingCfTokens] = reactExports.useState(false);
49  const [cfConfigExpanded, setCfConfigExpanded] = reactExports.useState(true);
50  reactExports.useEffect(() => {
51    loadBackups();
52    loadSchedule();
53    loadSettingsSchedule();
54    loadCfTokens();
55  }, []);
56  const loadCfTokens = async () => {
57    try {
58      const res = await fetch("/api/admin/backups/cloudflare/config");
59      if (res.ok) {
60        const data = await res.json();
61        setCfTokens({ dnsToken: data.dnsToken || "", tunnelToken: data.tunnelToken || "" });
62      }
63    } catch (e) {
64      console.error("Failed to restore Cloudflare config:", e);
65      alert(`Failed: ${e.message}`);
66    }
67  };
68  const handleBrowseTable = async (tableName) => {
69    if (!selectedBackup || !selectedBackup.id) return;
70    setViewingTable(tableName);
71    setIsTableLoading(true);
72    setTableData(null);
73    setTableSearchQuery("");
74    setTableSearchField("ALL");
75    setTableCurrentPage(1);
76    try {
77      const res = await fetch(`/api/admin/backups/${selectedBackup.id}/read-table?table=${tableName}`);
78      if (!res.ok) throw new Error(await res.text());
79      const data = await res.json();
80      if (data.success) {
81        setTableData({ columns: data.columns, rows: data.rows, limitReached: data.limitReached });
82      } else {
83        throw new Error(data.error);
84      }
85    } catch (e) {
86      console.error("Failed to read table:", e);
87      alert(`Failed to extract table data: ${e.message || e}`);
88      setViewingTable(null);
89    } finally {
90      setIsTableLoading(false);
91    }
92  };
93  const handleBrowseSettingsTable = (tableName, records) => {
94    setViewingTable(tableName);
95    let columns = [];
96    if (records && records.length > 0) {
97      columns = Object.keys(records[0]);
98    }
99    setTableData({
100      columns,
101      rows: records,
102      limitReached: false
103    });
104    setTableSearchQuery("");
105    setTableSearchField("ALL");
106    setTableCurrentPage(1);
107  };
108  const saveCfTokens = async () => {
109    setSavingCfTokens(true);
110    try {
111      const res = await fetch("/api/admin/backups/cloudflare/config", {
112        method: "POST",
113        headers: { "Content-Type": "application/json" },
114        body: JSON.stringify(cfTokens)
115      });
116      const data = await res.json();
117      if (data.success) {
118        alert("Cloudflare API tokens saved successfully.");
119      } else {
120        alert("Failed to save tokens: " + (data.error || "Unknown error"));
121      }
122    } catch (e) {
123      alert("Failed to save tokens: " + e.message);
124    } finally {
125      setSavingCfTokens(false);
126    }
127  };
128  reactExports.useEffect(() => {
129    if (selectedBackup && selectedBackup.type === "snapshot") {
130      loadFiles(selectedBackup.id);
131    } else {
132      setFiles([]);
133      setSelectedFile(null);
134    }
135  }, [selectedBackup]);
136  reactExports.useEffect(() => {
137    if (selectedFile && selectedBackup) {
138      loadFileContent(selectedBackup.id, selectedFile.backupFilename);
139    } else {
140      setFileContent("");
141    }
142  }, [selectedFile, selectedBackup]);
143  const loadSchedule = async () => {
144    try {
145      const res = await fetch("/api/admin/backups/schedule");
146      if (res.ok) {
147        const data = await res.json();
148        setSchedule(data);
149      }
150    } catch (e) {
151      console.error("Failed to load backup schedule:", e);
152    }
153  };
154  const saveSchedule = async () => {
155    setSavingSchedule(true);
156    try {
157      const res = await fetch("/api/admin/backups/schedule", {
158        method: "POST",
159        headers: { "Content-Type": "application/json" },
160        body: JSON.stringify(schedule)
161      });
162      const data = await res.json();
163      if (data.success) {
164        alert("Backup schedule & retention rules saved successfully.");
165        setSchedule(data.schedule);
166      } else {
167        alert("Failed to save schedule: " + (data.error || "Unknown error"));
168      }
169    } catch (e) {
170      alert("Failed to save schedule: " + e.message);
171    } finally {
172      setSavingSchedule(false);
173    }
174  };
175  const loadSettingsSchedule = async () => {
176    try {
177      const res = await fetch("/api/admin/backups/settings/schedule");
178      if (res.ok) {
179        const data = await res.json();
180        setSettingsSchedule(data);
181      }
182    } catch (e) {
183      console.error("Failed to load settings backup schedule:", e);
184    }
185  };
186  const saveSettingsSchedule = async () => {
187    setSavingSchedule(true);
188    try {
189      const res = await fetch("/api/admin/backups/settings/schedule", {
190        method: "POST",
191        headers: { "Content-Type": "application/json" },
192        body: JSON.stringify(settingsSchedule)
193      });
194      const data = await res.json();
195      if (data.success) {
196        alert("Settings backup schedule & retention rules saved successfully.");
197        setSettingsSchedule(data.schedule);
198      } else {
199        alert("Failed to save settings schedule: " + (data.error || "Unknown error"));
200      }
201    } catch (e) {
202      alert("Failed to save settings schedule: " + e.message);
203    } finally {
204      setSavingSchedule(false);
205    }
206  };
207  const loadBackups = async () => {
208    setLoading(true);
209    try {
210      const res = await fetch("/api/admin/backups");
211      if (res.ok) {
212        const data = await res.json();
213        setBackups(data);
214      }
215    } catch (e) {
216      console.error(e);
217    } finally {
218      setLoading(false);
219    }
220  };
221  const loadFiles = async (id) => {
222    try {
223      const res = await fetch(`/api/admin/backups/${id}/files`);
224      if (res.ok) setFiles(await res.json());
225    } catch (e) {
226      console.error(e);
227    }
228  };
229  const loadFileContent = async (backupId, filename) => {
230    try {
231      const res = await fetch(`/api/admin/backups/${backupId}/read?file=${filename}`);
232      if (res.ok) {
233        const data = await res.json();
234        setFileContent(data.content);
235      }
236    } catch (e) {
237      console.error(e);
238    }
239  };
240  const loadDbStats = async () => {
241    try {
242      const res = await fetch("/api/admin/backups/stats");
243      const data = await res.json();
244      if (data.success) {
245        setDbStats(data.stats);
246        setStatsTitle("Live Database Statistics");
247        setShowStatsModal(true);
248      } else {
249        alert("Failed to load stats: " + data.error);
250      }
251    } catch (e) {
252      console.error(e);
253    }
254  };
255  const checkBackupStatus = async (filename) => {
256    try {
257      const res = await fetch(`/api/admin/backups/status/${filename}`);
258      const data = await res.json();
259      if (data.status === "running") {
260        setBackupProgress({ filename, size: data.size });
261        setTimeout(() => checkBackupStatus(filename), 1500);
262      } else if (data.status === "success") {
263        alert(`Database backup ${filename} created successfully.`);
264        setCreating(false);
265        setBackupProgress(null);
266        loadBackups();
267        loadSchedule();
268      } else if (data.status === "error") {
269        alert("Backup failed: " + data.error);
270        setCreating(false);
271        setBackupProgress(null);
272      } else {
273        setCreating(false);
274        setBackupProgress(null);
275      }
276    } catch (e) {
277      console.error("Failed to poll status:", e);
278      setTimeout(() => checkBackupStatus(filename), 2e3);
279    }
280  };
281  const createDbBackup = async () => {
282    if (!confirm("Trigger on-demand PostgreSQL database backup? This will execute pg_dump and save to backups storage.")) return;
283    setCreating(true);
284    setBackupProgress(null);
285    try {
286      const res = await fetch("/api/admin/backups/create", { method: "POST" });
287      const data = await res.json();
288      if (data.success) {
289        setBackupProgress({ filename: data.filename, size: 0 });
290        checkBackupStatus(data.filename);
291      } else {
292        alert("Backup failed: " + data.error);
293        setCreating(false);
294      }
295    } catch (e) {
296      alert("Backup failed: " + e.message);
297      setCreating(false);
298    }
299  };
300  const createSettingsBackup = async () => {
301    if (!confirm("Create a new manual settings backup?")) return;
302    setCreating(true);
303    try {
304      const res = await fetch("/api/admin/backups/settings/create", { method: "POST" });
305      const data = await res.json();
306      if (data.success) {
307        alert(`Settings backup ${data.filename} created successfully.`);
308        loadBackups();
309        loadSettingsSchedule();
310      } else {
311        alert("Backup failed: " + data.error);
312      }
313    } catch (e) {
314      alert("Backup failed: " + e.message);
315    } finally {
316      setCreating(false);
317    }
318  };
319  const createCfBackup = async () => {
320    if (!confirm("Trigger Cloudflare backup? This will fetch all DNS records and Tunnel configurations.")) return;
321    setCreating(true);
322    setBackupProgress(null);
323    try {
324      const res = await fetch("/api/admin/backups/cloudflare/create", { method: "POST" });
325      const data = await res.json();
326      if (data.success) {
327        setBackupProgress({ filename: data.filename, size: 0 });
328        checkBackupStatus(data.filename);
329      } else {
330        alert("Backup failed: " + data.error);
331        setCreating(false);
332      }
333    } catch (e) {
334      alert("Backup failed: " + e.message);
335      setCreating(false);
336    }
337  };
338  const deleteBackup = async (id, e) => {
339    e.stopPropagation();
340    if (!confirm(`Are you sure you want to delete ${id}? This action cannot be undone.`)) return;
341    try {
342      const res = await fetch(`/api/admin/backups/${id}`, { method: "DELETE" });
343      const data = await res.json();
344      if (data.success) {
345        if ((selectedBackup == null ? void 0 : selectedBackup.id) === id) setSelectedBackup(null);
346        loadBackups();
347      } else {
348        alert("Delete failed: " + data.error);
349      }
350    } catch (err) {
351      alert("Delete failed: " + err.message);
352    }
353  };
354  const openCompareModal = async () => {
355    if (!selectedFile || !selectedBackup) return;
356    setLoading(true);
357    try {
358      const res = await fetch(`/api/admin/backups/cloudflare/diff?backupId=${selectedBackup.id}&file=${selectedFile.backupFilename}`);
359      const data = await res.json();
360      if (data.success) {
361        setDiffData({ backup: data.backup, live: data.live });
362        setCompareModalOpen(true);
363      } else {
364        alert("Diff failed: " + data.error);
365      }
366    } catch (e) {
367      alert("Diff failed: " + e.message);
368    } finally {
369      setLoading(false);
370    }
371  };
372  const handleRestoreCloudflare = async () => {
373    if (!selectedFile || !selectedBackup || !diffData) return;
374    let payload = {};
375    if (selectedFile.backupFilename === "dns_records.json") {
376      const recordsToRestore = [];
377      const backupRecs = diffData.backup.result || [];
378      const liveRecs = diffData.live.result || [];
379      for (const b of backupRecs) {
380        const live = liveRecs.find((l) => l.name === b.name && l.type === b.type);
381        if (!live) {
382          recordsToRestore.push({ action: "create", data: b });
383        } else if (live.content !== b.content || live.proxied !== b.proxied) {
384          recordsToRestore.push({ action: "update", data: { ...b, id: live.id } });
385        }
386      }
387      if (recordsToRestore.length === 0) {
388        alert("No changes to restore for DNS.");
389        return;
390      }
391      if (!confirm(`Restore ${recordsToRestore.length} DNS record changes?`)) return;
392      payload = { type: "dns", items: recordsToRestore };
393    } else if (selectedFile.backupFilename.startsWith("tunnel_config_")) {
394      if (!confirm(`Restore Tunnel Configuration from backup? This will overwrite the live configuration for this tunnel.`)) return;
395      payload = {
396        type: "tunnel_config",
397        items: diffData.backup.result,
398        tunnelName: selectedFile.backupFilename.replace("tunnel_config_", "").replace(".json", "")
399      };
400    }
401    try {
402      const res = await fetch("/api/admin/backups/cloudflare/restore", {
403        method: "POST",
404        headers: { "Content-Type": "application/json" },
405        body: JSON.stringify(payload)
406      });
407      const data = await res.json();
408      if (data.success) {
409        alert(`Successfully restored configuration.`);
410        setCompareModalOpen(false);
411      } else {
412        alert("Restore failed: " + data.error);
413      }
414    } catch (e) {
415      alert("Restore failed: " + e.message);
416    }
417  };
418  const handleBrowseSettings = async () => {
419    if (!selectedBackup || selectedBackup.type !== "settings") return;
420    setLoading(true);
421    try {
422      const res = await fetch(`/api/admin/backups/settings/${selectedBackup.id}/browse`);
423      const data = await res.json();
424      if (data.success) {
425        setSettingsRestoreData(data.data);
426        setSelectedSettingsSections([]);
427        setSettingsRestoreModalOpen(true);
428      } else {
429        alert("Browse failed: " + data.error);
430      }
431    } catch (e) {
432      alert("Browse failed: " + e.message);
433    } finally {
434      setLoading(false);
435    }
436  };
437  const handleRestoreSettings = async () => {
438    if (!selectedBackup || selectedBackup.type !== "settings") return;
439    if (selectedSettingsSections.length === 0) {
440      alert("Please select at least one section to restore.");
441      return;
442    }
443    if (!confirm(`Are you sure you want to restore ${selectedSettingsSections.length} sections? This will overwrite the live settings in the database for these sections.`)) return;
444    setLoading(true);
445    try {
446      const res = await fetch(`/api/admin/backups/settings/${selectedBackup.id}/restore`, {
447        method: "POST",
448        headers: { "Content-Type": "application/json" },
449        body: JSON.stringify({ sections: selectedSettingsSections })
450      });
451      const data = await res.json();
452      if (data.success) {
453        alert(`Successfully restored settings: ${data.message}`);
454        setSettingsRestoreModalOpen(false);
455      } else {
456        alert("Restore failed: " + data.error);
457      }
458    } catch (e) {
459      alert("Restore failed: " + e.message);
460    } finally {
461      setLoading(false);
462    }
463  };
464  const toggleSettingsSection = (sectionName) => {
465    if (selectedSettingsSections.includes(sectionName)) {
466      setSelectedSettingsSections(selectedSettingsSections.filter((s) => s !== sectionName));
467    } else {
468      setSelectedSettingsSections([...selectedSettingsSections, sectionName]);
469    }
470  };
471  const handleRestore = async () => {
472    if (!selectedFile || !selectedBackup) return;
473    if (!confirm(`Restore ${selectedFile.originalPath} from backup? This overwrites the current live file.`)) return;
474    try {
475      const res = await fetch("/api/dev-agent/restore", {
476        method: "POST",
477        headers: { "Content-Type": "application/json" },
478        body: JSON.stringify({
479          file: {
480            backupPath: `backup/${selectedBackup.id}/${selectedFile.backupFilename}`,
481            originalPath: selectedFile.originalPath
482          }
483        })
484      });
485      const data = await res.json();
486      if (data.success) {
487        alert("File restored successfully.");
488      } else {
489        alert("Restore failed: " + data.error);
490      }
491    } catch (e) {
492      alert("Restore error: " + e.message);
493    }
494  };
495  const formatBytes = (bytes) => {
496    if (!bytes) return "0 B";
497    const k = 1024;
498    const sizes = ["B", "KB", "MB", "GB"];
499    const i = Math.floor(Math.log(bytes) / Math.log(k));
500    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
501  };
502  const filteredTableRows = reactExports.useMemo(() => {
503    if (!tableData) return [];
504    if (!tableSearchQuery.trim()) return tableData.rows;
505    const query = tableSearchQuery.toLowerCase();
506    return tableData.rows.filter((row) => {
507      if (tableSearchField === "ALL") {
508        return Object.values(row).some((val) => String(val).toLowerCase().includes(query));
509      }
510      return String(row[tableSearchField] || "").toLowerCase().includes(query);
511    });
512  }, [tableData, tableSearchQuery, tableSearchField]);
513  const filteredBackups = reactExports.useMemo(() => {
514    return backups.filter((bk) => {
515      if (activeTab === "cloudflare") return bk.type === "cloudflare";
516      if (activeTab === "settings") return bk.type === "settings";
517      return bk.type === "database" || bk.type === "snapshot";
518    });
519  }, [backups, activeTab]);
520  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full space-y-4 animate-in fade-in", children: [
521    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center bg-white dark:bg-zinc-900 p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-sm shrink-0", children: [
522      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
523        /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
524          /* @__PURE__ */ jsxRuntimeExports.jsx(Archive, { className: "text-indigo-500" }),
525          " Database & System Backup Manager"
526        ] }),
527        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Configure backup rotation schedules, retention limits, and manage DB dumps & snapshots." })
528      ] }),
529      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
530        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: loadDbStats, className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg text-zinc-500 transition-colors", title: "View Live DB Stats", children: /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 20 }) }),
531        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => {
532          loadBackups();
533          loadSchedule();
534        }, className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg text-zinc-500 transition-colors", title: "Refresh", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 20, className: loading ? "animate-spin" : "" }) }),
535        activeTab === "database" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
536          "button",
537          {
538            onClick: createDbBackup,
539            disabled: creating,
540            className: "flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-bold text-sm shadow-md transition-colors disabled:opacity-50",
541            children: [
542              creating ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 16 }),
543              creating ? backupProgress ? `Dumping... ${formatBytes(backupProgress.size)}` : "Starting Backup..." : "Create DB Dump"
544            ]
545          }
546        ) : activeTab === "cloudflare" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
547          "button",
548          {
549            onClick: createCfBackup,
550            disabled: creating,
551            className: "flex items-center gap-2 px-4 py-2 bg-amber-500 hover:bg-amber-600 text-white rounded-lg font-bold text-sm shadow-md transition-colors disabled:opacity-50",
552            children: [
553              creating ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Cloud, { size: 16 }),
554              creating ? "Fetching..." : "Create CF Backup"
555            ]
556          }
557        ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
558          "button",
559          {
560            onClick: createSettingsBackup,
561            disabled: creating,
562            className: "flex items-center gap-2 px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white rounded-lg font-bold text-sm shadow-md transition-colors disabled:opacity-50",
563            children: [
564              creating ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 16 }),
565              creating ? "Fetching..." : "Create Settings Backup"
566            ]
567          }
568        )
569      ] })
570    ] }),
571    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 p-1 bg-zinc-100 dark:bg-zinc-800 rounded-lg w-max shrink-0", children: [
572      /* @__PURE__ */ jsxRuntimeExports.jsx(
573        "button",
574        {
575          onClick: () => {
576            setActiveTab("database");
577            setSelectedBackup(null);
578          },
579          className: `px-4 py-2 rounded-md font-bold text-sm transition-colors ${activeTab === "database" ? "bg-white dark:bg-zinc-700 shadow-sm text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"}`,
580          children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
581            /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 16 }),
582            " Database & Code"
583          ] })
584        }
585      ),
586      /* @__PURE__ */ jsxRuntimeExports.jsx(
587        "button",
588        {
589          onClick: () => {
590            setActiveTab("cloudflare");
591            setSelectedBackup(null);
592          },
593          className: `px-4 py-2 rounded-md font-bold text-sm transition-colors ${activeTab === "cloudflare" ? "bg-white dark:bg-zinc-700 shadow-sm text-amber-600 dark:text-amber-400" : "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"}`,
594          children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
595            /* @__PURE__ */ jsxRuntimeExports.jsx(Cloud, { size: 16 }),
596            " Cloudflare Configs"
597          ] })
598        }
599      ),
600      /* @__PURE__ */ jsxRuntimeExports.jsx(
601        "button",
602        {
603          onClick: () => {
604            setActiveTab("settings");
605            setSelectedBackup(null);
606          },
607          className: `px-4 py-2 rounded-md font-bold text-sm transition-colors ${activeTab === "settings" ? "bg-white dark:bg-zinc-700 shadow-sm text-teal-600 dark:text-teal-400" : "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"}`,
608          children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
609            /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 16 }),
610            " Site Settings"
611          ] })
612        }
613      )
614    ] }),
615    (activeTab === "database" || activeTab === "settings") && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-sm shrink-0 overflow-hidden", children: [
616      /* @__PURE__ */ jsxRuntimeExports.jsxs(
617        "div",
618        {
619          className: "flex items-center justify-between p-4 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors",
620          onClick: () => setScheduleExpanded(!scheduleExpanded),
621          children: [
622            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
623              /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "text-indigo-500", size: 18 }),
624              /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-base text-zinc-900 dark:text-white", children: "Backup Schedule & Retention Rules" })
625            ] }),
626            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-500 font-semibold", children: [
627              scheduleExpanded ? "Hide" : "Show",
628              " Settings"
629            ] }) })
630          ]
631        }
632      ),
633      scheduleExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 pt-0 border-t border-zinc-100 dark:border-zinc-800 mt-2", children: [
634        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-4 pb-3 border-b border-zinc-100 dark:border-zinc-800", children: [
635          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: [
636            /* @__PURE__ */ jsxRuntimeExports.jsx(
637              "input",
638              {
639                type: "checkbox",
640                checked: activeTab === "database" ? schedule.enabled : settingsSchedule.enabled,
641                onChange: (e) => {
642                  if (activeTab === "database") setSchedule({ ...schedule, enabled: e.target.checked });
643                  else setSettingsSchedule({ ...settingsSchedule, enabled: e.target.checked });
644                },
645                className: "w-4 h-4 text-indigo-600 rounded focus:ring-indigo-500"
646              }
647            ),
648            "Automated Rotation Active"
649          ] }) }),
650          /* @__PURE__ */ jsxRuntimeExports.jsxs(
651            "button",
652            {
653              onClick: activeTab === "database" ? saveSchedule : saveSettingsSchedule,
654              disabled: savingSchedule,
655              className: "flex items-center gap-1.5 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-bold text-xs shadow transition-colors disabled:opacity-50",
656              children: [
657                savingSchedule ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 14 }),
658                "Save Settings"
659              ]
660            }
661          )
662        ] }),
663        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 text-sm", children: [
664          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
665            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Cron Schedule Expression" }),
666            /* @__PURE__ */ jsxRuntimeExports.jsx(
667              "input",
668              {
669                type: "text",
670                value: activeTab === "database" ? schedule.cronExpression : settingsSchedule.cronExpression,
671                onChange: (e) => {
672                  if (activeTab === "database") setSchedule({ ...schedule, cronExpression: e.target.value });
673                  else setSettingsSchedule({ ...settingsSchedule, cronExpression: e.target.value });
674                },
675                disabled: activeTab === "database",
676                placeholder: activeTab === "database" ? "Managed by crontab" : "e.g. 0 2 * * *",
677                className: "w-full px-3 py-2 bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-mono text-xs text-zinc-500 focus:outline-none disabled:cursor-not-allowed"
678              }
679            ),
680            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: activeTab === "database" ? "Schedule is configured via system crontab and runs in the node's local time zone." : "Cron format (e.g. '0 * * * *' for every hour). Checked hourly by background worker." })
681          ] }),
682          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
683            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Keep Daily Backups" }),
684            /* @__PURE__ */ jsxRuntimeExports.jsx(
685              "input",
686              {
687                type: "number",
688                min: "1",
689                max: "365",
690                value: activeTab === "database" ? schedule.keepDaily : settingsSchedule.keepDaily,
691                onChange: (e) => {
692                  const val = parseInt(e.target.value) || 7;
693                  if (activeTab === "database") setSchedule({ ...schedule, keepDaily: val });
694                  else setSettingsSchedule({ ...settingsSchedule, keepDaily: val });
695                },
696                className: "w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-bold text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-indigo-500"
697              }
698            ),
699            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Rotation retention limit for daily dumps" })
700          ] }),
701          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
702            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Keep Weekly Backups" }),
703            /* @__PURE__ */ jsxRuntimeExports.jsx(
704              "input",
705              {
706                type: "number",
707                min: "1",
708                max: "104",
709                value: activeTab === "database" ? schedule.keepWeekly : settingsSchedule.keepWeekly,
710                onChange: (e) => {
711                  const val = parseInt(e.target.value) || 4;
712                  if (activeTab === "database") setSchedule({ ...schedule, keepWeekly: val });
713                  else setSettingsSchedule({ ...settingsSchedule, keepWeekly: val });
714                },
715                className: "w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-bold text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-indigo-500"
716              }
717            ),
718            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Rotation retention limit for weekly dumps" })
719          ] }),
720          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
721            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Keep Monthly Backups" }),
722            /* @__PURE__ */ jsxRuntimeExports.jsx(
723              "input",
724              {
725                type: "number",
726                min: "1",
727                max: "120",
728                value: activeTab === "database" ? schedule.keepMonthly : settingsSchedule.keepMonthly,
729                onChange: (e) => {
730                  const val = parseInt(e.target.value) || 12;
731                  if (activeTab === "database") setSchedule({ ...schedule, keepMonthly: val });
732                  else setSettingsSchedule({ ...settingsSchedule, keepMonthly: val });
733                },
734                className: "w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-bold text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-indigo-500"
735              }
736            ),
737            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Rotation retention limit for monthly dumps" })
738          ] })
739        ] }),
740        (activeTab === "database" ? schedule.lastBackupAt : settingsSchedule.lastBackupAt) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 pt-3 border-t border-zinc-100 dark:border-zinc-800 flex items-center justify-between text-xs text-zinc-500", children: [
741          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
742            (activeTab === "database" ? schedule.lastBackupStatus : settingsSchedule.lastBackupStatus) === "success" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 16, className: "text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { size: 16, className: "text-red-500" }),
743            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
744              "Last Execution: ",
745              /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: new Date(activeTab === "database" ? schedule.lastBackupAt : settingsSchedule.lastBackupAt).toLocaleString(void 0, { timeZoneName: "short" }) })
746            ] }),
747            (activeTab === "database" ? schedule.lastBackupFile : settingsSchedule.lastBackupFile) && /* @__P
747URE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-zinc-400", children: [
748              "(",
749              activeTab === "database" ? schedule.lastBackupFile : settingsSchedule.lastBackupFile,
750              ")"
751            ] })
752          ] }),
753          (activeTab === "database" ? schedule.lastBackupSize : settingsSchedule.lastBackupSize) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold bg-zinc-100 dark:bg-zinc-800 px-2 py-0.5 rounded text-zinc-700 dark:text-zinc-300", children: [
754            "Size: ",
755            activeTab === "database" ? schedule.lastBackupSize : settingsSchedule.lastBackupSize
756          ] })
757        ] })
758      ] })
759    ] }),
760    activeTab === "cloudflare" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 shadow-sm shrink-0 overflow-hidden", children: [
761      /* @__PURE__ */ jsxRuntimeExports.jsxs(
762        "div",
763        {
764          className: "flex items-center justify-between p-4 cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors",
765          onClick: () => setCfConfigExpanded(!cfConfigExpanded),
766          children: [
767            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
768              /* @__PURE__ */ jsxRuntimeExports.jsx(Cloud, { className: "text-amber-500", size: 18 }),
769              /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-base text-zinc-900 dark:text-white", children: "Cloudflare API Tokens & Credentials" })
770            ] }),
771            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-500 font-semibold", children: [
772              cfConfigExpanded ? "Hide" : "Show",
773              " Configuration"
774            ] }) })
775          ]
776        }
777      ),
778      cfConfigExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 pt-0 border-t border-zinc-100 dark:border-zinc-800 mt-2", children: [
779        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-4 pb-3 border-b border-zinc-100 dark:border-zinc-800", children: [
780          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500", children: "Configure Cloudflare API credentials stored in System Settings for DNS & Tunnel backups." }),
781          /* @__PURE__ */ jsxRuntimeExports.jsxs(
782            "button",
783            {
784              onClick: saveCfTokens,
785              disabled: savingCfTokens,
786              className: "flex items-center gap-1.5 px-3 py-1.5 bg-amber-600 hover:bg-amber-700 text-white rounded-lg font-bold text-xs shadow transition-colors disabled:opacity-50",
787              children: [
788                savingCfTokens ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 14 }),
789                "Save Credentials"
790              ]
791            }
792          )
793        ] }),
794        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [
795          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
796            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "DNS Zone Token" }),
797            /* @__PURE__ */ jsxRuntimeExports.jsx(
798              "input",
799              {
800                type: "password",
801                value: cfTokens.dnsToken,
802                onChange: (e) => setCfTokens({ ...cfTokens, dnsToken: e.target.value }),
803                placeholder: "cfat_...",
804                className: "w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-mono text-xs text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-amber-500"
805              }
806            ),
807            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Token for reading Cloudflare Zone DNS records" })
808          ] }),
809          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
810            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Zero Trust / Tunnel Token" }),
811            /* @__PURE__ */ jsxRuntimeExports.jsx(
812              "input",
813              {
814                type: "password",
815                value: cfTokens.tunnelToken,
816                onChange: (e) => setCfTokens({ ...cfTokens, tunnelToken: e.target.value }),
817                placeholder: "cfut_...",
818                className: "w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg font-mono text-xs text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-amber-500"
819              }
820            ),
821            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Token for reading Zero Trust Tunnel & Access settings" })
822          ] })
823        ] })
824      ] })
825    ] }),
826    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-4 min-h-0 overflow-hidden", children: [
827      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-80 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden flex flex-col shrink-0", children: [
828        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 font-bold text-xs uppercase text-zinc-500 flex justify-between items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
829          "Stored Backups (",
830          filteredBackups.length,
831          ")"
832        ] }) }),
833        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-2", children: [
834          filteredBackups.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-center text-sm text-zinc-500", children: "No backup files found on disk." }),
835          filteredBackups.map((bk) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
836            "div",
837            {
838              onClick: () => setSelectedBackup(bk),
839              className: `p-3 rounded-lg cursor-pointer border transition-all ${(selectedBackup == null ? void 0 : selectedBackup.id) === bk.id ? "bg-indigo-50 dark:bg-indigo-900/20 border-indigo-500" : "bg-white dark:bg-zinc-800/50 border-zinc-200 dark:border-zinc-700 hover:border-zinc-400"}`,
840              children: [
841                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
842                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
843                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold uppercase px-1.5 py-0.5 rounded ${bk.type === "database" ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300" : bk.type === "settings" ? "bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300" : "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"}`, children: bk.type }),
844                    bk.category && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold uppercase px-1.5 py-0.5 rounded bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400", children: bk.category })
845                  ] }),
846                  /* @__PURE__ */ jsxRuntimeExports.jsx(
847                    "button",
848                    {
849                      onClick: (e) => deleteBackup(bk.id, e),
850                      className: "text-zinc-400 hover:text-red-500 p-1 rounded transition-colors",
851                      title: "Delete Backup",
852                      children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 })
853                    }
854                  )
855                ] }),
856                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-xs text-zinc-900 dark:text-white truncate font-mono", children: bk.id }),
857                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[11px] text-zinc-500 flex items-center justify-between mt-1", children: [
858                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: new Date(bk.timestamp).toLocaleString(void 0, { timeZoneName: "short" }) }),
859                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: bk.size ? formatBytes(bk.size) : `${bk.fileCount || 0} files` })
860                ] })
861              ]
862            },
863            bk.id
864          ))
865        ] })
866      ] }),
867      (selectedBackup == null ? void 0 : selectedBackup.type) === "snapshot" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-80 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden flex flex-col shrink-0", children: [
868        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 font-bold text-xs uppercase text-zinc-500", children: "Files in Snapshot" }),
869        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-2 space-y-1", children: files.length > 0 ? files.map((file, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
870          "div",
871          {
872            onClick: () => setSelectedFile(file),
873            className: `flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer text-xs font-mono transition-colors ${(selectedFile == null ? void 0 : selectedFile.originalPath) === file.originalPath ? "bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white font-bold" : "text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`,
874            children: [
875              /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 14, className: "shrink-0" }),
876              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: file.originalPath, children: file.originalPath })
877            ]
878          },
879          i
880        )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-center text-sm text-zinc-500", children: "Loading snapshot files..." }) })
881      ] }),
882      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden flex flex-col min-w-0", children: [
883        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 flex justify-between items-center h-12", children: [
884          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-xs uppercase text-zinc-500 truncate pr-4", children: selectedFile ? `Preview: ${selectedFile.originalPath}` : selectedBackup ? `Details: ${selectedBackup.id}` : "Backup Details & Preview" }),
885          selectedFile && activeTab === "database" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
886            "button",
887            {
888              onClick: handleRestore,
889              className: "flex items-center gap-2 px-3 py-1 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/40 rounded text-xs font-bold transition-colors",
890              children: [
891                /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }),
892                " Restore File"
893              ]
894            }
895          ),
896          selectedBackup && activeTab === "settings" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
897            "button",
898            {
899              onClick: handleBrowseSettings,
900              disabled: loading,
901              className: "flex items-center gap-2 px-3 py-1 bg-teal-50 dark:bg-teal-900/20 text-teal-600 dark:text-teal-400 hover:bg-teal-100 dark:hover:bg-teal-900/40 rounded text-xs font-bold transition-colors",
902              children: [
903                loading ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 12 }),
904                " Browse & Restore"
905              ]
906            }
907          ),
908          selectedFile && activeTab === "cloudflare" && (selectedFile.backupFilename === "dns_records.json" || selectedFile.backupFilename.startsWith("tunnel_config_")) && /* @__PURE__ */ jsxRuntimeExports.jsxs(
909            "button",
910            {
911              onClick: openCompareModal,
912              disabled: loading,
913              className: "flex items-center gap-2 px-3 py-1 bg-amber-50 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-900/40 rounded text-xs font-bold transition-colors",
914              children: [
915                loading ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }),
916                " Compare & Restore"
917              ]
918            }
919          )
920        ] }),
921        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative bg-[#1e1e1e] overflow-hidden", children: selectedFile ? /* @__PURE__ */ jsxRuntimeExports.jsx(
922          "textarea",
923          {
924            readOnly: true,
925            value: fileContent,
926            className: "absolute inset-0 w-full h-full bg-transparent text-zinc-300 font-mono text-xs p-4 resize-none outline-none leading-relaxed",
927            spellCheck: false
928          }
929        ) : selectedBackup ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 text-zinc-300 space-y-4", children: [
930          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
931            /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 32, className: "text-indigo-400" }),
932            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
933              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-sm text-zinc-700 dark:text-zinc-300 mb-1", children: selectedBackup.filename }),
934              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-400", children: [
935                "Created: ",
936                new Date(selectedBackup.timestamp).toLocaleString(void 0, { timeZoneName: "short" })
937              ] })
938            ] })
939          ] }),
940          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 bg-zinc-800/60 p-4 rounded-lg border border-zinc-700 text-xs", children: [
941            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
942              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block", children: "Backup Type" }),
943              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-white uppercase", children: selectedBackup.type })
944            ] }),
945            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
946              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block", children: "Retention Category" }),
947              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-indigo-400 uppercase", children: selectedBackup.category || "N/A" })
948            ] }),
949            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
950              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block", children: "File Size" }),
951              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-white", children: [
952                selectedBackup.size ? formatBytes(selectedBackup.size) : "N/A",
953                selectedBackup.fileCount ? ` (${selectedBackup.fileCount} files)` : ""
954              ] })
955            ] }),
956            selectedBackup.metadata && selectedBackup.type === "database" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
957              /* @__P
957URE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block", children: "Snapshot Data" }),
958              /* @__PURE__ */ jsxRuntimeExports.jsx(
959                "button",
960                {
961                  onClick: () => {
962                    setDbStats(selectedBackup.metadata.tables);
963                    setStatsTitle(`Snapshot: ${selectedBackup.filename}`);
964                    setShowStatsModal(true);
965                  },
966                  className: "mt-1 text-[11px] px-2.5 py-1 bg-indigo-500/20 text-indigo-300 font-bold rounded hover:bg-indigo-500/40 transition-colors",
967                  children: "View Table Stats"
968                }
969              )
970            ] }),
971            selectedBackup.metadata && selectedBackup.type === "settings" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 mt-2 pt-2 border-t border-zinc-700/50", children: [
972              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block mb-1", children: "Sections" }),
973              /* @__P
973URE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1", children: (_a = selectedBackup.metadata.sections) == null ? void 0 : _a.map((sec) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] bg-zinc-700 text-zinc-300 px-1.5 py-0.5 rounded", children: [
974                sec.tableName,
975                " (",
976                sec.rowCount,
977                ")"
978              ] }, sec.tableName)) })
979            ] }),
980            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
981              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 block", children: "Status" }),
982              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-emerald-400", children: "Verified On Disk" })
983            ] })
984          ] })
985        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-zinc-500", children: [
986          /* @__PURE__ */ jsxRuntimeExports.jsx(Archive, { size: 48, className: "opacity-20 mb-2" }),
987          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Select a backup file to view details or preview contents." })
988        ] }) })
989      ] })
990    ] }),
991    settingsRestoreModalOpen && settingsRestoreData && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl w-full max-w-4xl max-h-[90vh] flex flex-col shadow-2xl border border-zinc-200 dark:border-zinc-800", children: [
992      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800 shrink-0", children: [
993        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
994          /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "text-teal-500", size: 20 }),
995          "Browse & Restore Settings: ",
996          selectedBackup == null ? void 0 : selectedBackup.id
997        ] }),
998        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setSettingsRestoreModalOpen(false), className: "text-zinc-500 hover:text-red-500 p-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { size: 20 }) })
999      ] }),
1000      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
1001        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-900/50 rounded-lg p-3 text-amber-700 dark:text-amber-400 text-sm", children: [
1002          /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Warning:" }),
1003          " Restoring settings will overwrite the current live configuration in the database for the selected sections. Select the sections you wish to restore below."
1004        ] }),
1005        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: Object.keys(settingsRestoreData).map((section) => {
1006          const records = settingsRestoreData[section];
1007          const isSelected = selectedSettingsSections.includes(section);
1008          if (!records || records.length === 0) return null;
1009          return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1010            "div",
1011            {
1012              onClick: () => toggleSettingsSection(section),
1013              className: `p-3 rounded-xl border-2 cursor-pointer transition-colors flex items-center justify-between ${isSelected ? "border-teal-500 bg-teal-50 dark:bg-teal-900/20" : "border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 hover:border-zinc-300 dark:hover:border-zinc-700"}`,
1014              children: [
1015                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1016                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `font-bold text-sm ${isSelected ? "text-teal-700 dark:text-teal-300" : "text-zinc-900 dark:text-white"}`, children: section }),
1017                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500", children: [
1018                    records.length,
1019                    " records"
1020                  ] })
1021                ] }),
1022                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
1023                  /* @__PURE__ */ jsxRuntimeExports.jsx(
1024                    "button",
1025                    {
1026                      onClick: (e) => {
1027                        e.stopPropagation();
1028                        handleBrowseSettingsTable(section, records);
1029                      },
1030                      className: "px-2 py-1 bg-teal-500/20 text-teal-600 dark:text-teal-400 hover:bg-teal-500/40 text-[11px] font-bold rounded transition-colors",
1031                      children: "Browse Data"
1032                    }
1033                  ),
1034                  isSelected ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { className: "text-teal-500", size: 20 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 h-5 rounded-full border-2 border-zinc-300 dark:border-zinc-600" })
1035                ] })
1036              ]
1037            },
1038            section
1039          );
1040        }) })
1041      ] }),
1042      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 flex justify-end gap-3 shrink-0", children: [
1043        /* @__PURE__ */ jsxRuntimeExports.jsx(
1044          "button",
1045          {
1046            onClick: () => setSettingsRestoreModalOpen(false),
1047            className: "px-4 py-2 font-bold text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors",
1048            children: "Cancel"
1049          }
1050        ),
1051        /* @__PURE__ */ jsxRuntimeExports.jsxs(
1052          "button",
1053          {
1054            onClick: handleRestoreSettings,
1055            disabled: selectedSettingsSections.length === 0 || loading,
1056            className: "flex items-center gap-2 px-4 py-2 font-bold text-sm text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors disabled:opacity-50",
1057            children: [
1058              loading ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 }),
1059              "Restore ",
1060              selectedSettingsSections.length,
1061              " Sections"
1062            ]
1063          }
1064        )
1065      ] })
1066    ] }) }),
1067    compareModalOpen && diffData && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl w-full max-w-5xl h-[80vh] flex flex-col shadow-2xl border border-zinc-200 dark:border-zinc-800", children: [
1068      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800 shrink-0", children: [
1069        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
1070          /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "text-amber-500", size: 20 }),
1071          "Compare & Restore: ",
1072          selectedFile == null ? void 0 : selectedFile.backupFilename
1073        ] }),
1074        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCompareModalOpen(false), className: "text-zinc-500 hover:text-red-500 p-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { size: 20 }) })
1075      ] }),
1076      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
1077        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col border-r border-zinc-200 dark:border-zinc-800 min-w-0", children: [
1078          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 bg-red-50 dark:bg-red-900/20 border-b border-zinc-200 dark:border-zinc-800 font-bold text-sm text-red-600 dark:text-red-400 flex items-center gap-2", children: [
1079            /* @__PURE__ */ jsxRuntimeExports.jsx(Archive, { size: 16 }),
1080            " Backup Version (To Restore)"
1081          ] }),
1082          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-auto bg-[#1e1e1e] p-4 text-xs font-mono text-zinc-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: JSON.stringify(diffData.backup, null, 2) }) })
1083        ] }),
1084        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
1085          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 bg-emerald-50 dark:bg-emerald-900/20 border-b border-zinc-200 dark:border-zinc-800 font-bold text-sm text-emerald-600 dark:text-emerald-400 flex items-center gap-2", children: [
1086            /* @__PURE__ */ jsxRuntimeExports.jsx(Cloud, { size: 16 }),
1087            " Live Version (Cloudflare)"
1088          ] }),
1089          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-auto bg-[#1e1e1e] p-4 text-xs font-mono text-zinc-300", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { children: JSON.stringify(diffData.live, null, 2) }) })
1090        ] })
1091      ] }),
1092      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 shrink-0 flex justify-between items-center bg-zinc-50 dark:bg-zinc-900/50", children: [
1093        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-zinc-500", children: "Review the differences. Clicking Restore will apply the backup changes to Cloudflare." }),
1094        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3", children: [
1095          /* @__PURE__ */ jsxRuntimeExports.jsx(
1096            "button",
1097            {
1098              onClick: () => setCompareModalOpen(false),
1099              className: "px-4 py-2 bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 text-zinc-800 dark:text-zinc-200 rounded-lg font-bold text-sm transition-colors",
1100              children: "Cancel"
1101            }
1102          ),
1103          /* @__PURE__ */ jsxRuntimeExports.jsxs(
1104            "button",
1105            {
1106              onClick: handleRestoreCloudflare,
1107              className: "px-4 py-2 bg-amber-500 hover:bg-amber-600 text-white rounded-lg font-bold text-sm shadow transition-colors flex items-center gap-2",
1108              children: [
1109                /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 }),
1110                " Restore Selected Changes"
1111              ]
1112            }
1113          )
1114        ] })
1115      ] })
1116    ] }) }),
1117    showStatsModal && dbStats && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl w-full max-w-2xl max-h-[80vh] flex flex-col shadow-2xl border border-zinc-200 dark:border-zinc-800", children: [
1118      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800 shrink-0", children: [
1119        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
1120          /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { className: "text-indigo-500", size: 20 }),
1121          statsTitle
1122        ] }),
1123        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowStatsModal(false), className: "text-zinc-500 hover:text-red-500 p-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { size: 20 }) })
1124      ] }),
1125      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 overflow-y-auto flex-1", children: [
1126        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 mb-4", children: statsTitle === "Live Database Statistics" ? "This table shows the current contents of the PostgreSQL database, representing exactly what gets packed into the DB Dump backups." : "This table shows the snapshot of the database contents at the time this backup was created." }),
1127        /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-sm border-collapse", children: [
1128          /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "bg-zinc-100 dark:bg-zinc-800", children: [
1129            /* @__PURE__ */ jsxRuntimeExports.jsx(
1130              "th",
1131              {
1132                className: "p-2 border border-zinc-200 dark:border-zinc-700 font-bold text-zinc-700 dark:text-zinc-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors",
1133                onClick: () => {
1134                  if ((sortConfig == null ? void 0 : sortConfig.key) === "tableName") {
1135                    setSortConfig({ key: "tableName", direction: sortConfig.direction === "asc" ? "desc" : "asc" });
1136                  } else {
1137                    setSortConfig({ key: "tableName", direction: "asc" });
1138                  }
1139                },
1140                children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
1141                  "Table Name",
1142                  (sortConfig == null ? void 0 : sortConfig.key) === "tableName" && (sortConfig.direction === "asc" ? /* @__P
1142URE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }))
1143                ] })
1144              }
1145            ),
1146            /* @__PURE__ */ jsxRuntimeExports.jsx(
1147              "th",
1148              {
1149                className: "p-2 border border-zinc-200 dark:border-zinc-700 font-bold text-zinc-700 dark:text-zinc-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors",
1150                onClick: () => {
1151                  if ((sortConfig == null ? void 0 : sortConfig.key) === "size") {
1152                    setSortConfig({ key: "size", direction: sortConfig.direction === "asc" ? "desc" : "asc" });
1153                  } else {
1154                    setSortConfig({ key: "size", direction: "desc" });
1155                  }
1156                },
1157                children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
1158                  "Size on Disk",
1159                  (sortConfig == null ? void 0 : sortConfig.key) === "size" && (sortConfig.direction === "asc" ? /* @__P
1159URE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }))
1160                ] })
1161              }
1162            ),
1163            /* @__PURE__ */ jsxRuntimeExports.jsx(
1164              "th",
1165              {
1166                className: "p-2 border border-zinc-200 dark:border-zinc-700 font-bold text-zinc-700 dark:text-zinc-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors",
1167                onClick: () => {
1168                  if ((sortConfig == null ? void 0 : sortConfig.key) === "rowCount") {
1169                    setSortConfig({ key: "rowCount", direction: sortConfig.direction === "asc" ? "desc" : "asc" });
1170                  } else {
1171                    setSortConfig({ key: "rowCount", direction: "desc" });
1172                  }
1173                },
1174                children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
1175                  "Estimated Rows",
1176                  (sortConfig == null ? void 0 : sortConfig.key) === "rowCount" && (sortConfig.direction === "asc" ? /* @__P
1176URE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }))
1177                ] })
1178              }
1179            ),
1180            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-2 border border-zinc-200 dark:border-zinc-700 font-bold text-zinc-700 dark:text-zinc-300", children: "Action" })
1181          ] }) }),
1182          /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: [...dbStats].sort((a, b) => {
1183            if (!sortConfig) return 0;
1184            if (sortConfig.key === "tableName") {
1185              return sortConfig.direction === "asc" ? a.tableName.localeCompare(b.tableName) : b.tableName.localeCompare(a.tableName);
1186            }
1187            if (sortConfig.key === "rowCount") {
1188              const aVal = parseInt(a.rowCount) || 0;
1189              const bVal = parseInt(b.rowCount) || 0;
1190              return sortConfig.direction === "asc" ? aVal - bVal : bVal - aVal;
1191            }
1192            if (sortConfig.key === "size") {
1193              const parseSize = (size) => {
1194                const match = size.match(/([\d.]+)\s*([a-zA-Z]+)/);
1195                if (!match) return 0;
1196                const val = parseFloat(match[1]);
1197                const u = match[2].toUpperCase();
1198                const mult = u.includes("GB") ? 1024 * 1024 * 1024 : u.includes("MB") ? 1024 * 1024 : u.includes("KB") ? 1024 : 1;
1199                return val * mult;
1200              };
1201              const aVal = parseSize(a.size);
1202              const bVal = parseSize(b.size);
1203              return sortConfig.direction === "asc" ? aVal - bVal : bVal - aVal;
1204            }
1205            return 0;
1206          }).map((stat, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "hover:bg-zinc-50 dark:hover:bg-zinc-800/50", children: [
1207            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 border border-zinc-200 dark:border-zinc-700 font-mono text-zinc-800 dark:text-zinc-200", children: stat.tableName }),
1208            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 border border-zinc-200 dark:border-zinc-700 text-indigo-600 dark:text-indigo-400 font-semibold", children: stat.size }),
1209            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 border border-zinc-200 dark:border-zinc-700 text-zinc-600 dark:text-zinc-400", children: parseInt(stat.rowCount).toLocaleString() }),
1210            /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 border border-zinc-200 dark:border-zinc-700", children: statsTitle !== "Live Database Statistics" && selectedBackup && /* @__PURE__ */ jsxRuntimeExports.jsx(
1211              "button",
1212              {
1213                onClick: () => handleBrowseTable(stat.tableName),
1214                className: "px-2 py-1 bg-indigo-500/20 text-indigo-400 hover:bg-indigo-500/40 text-[11px] font-bold rounded transition-colors",
1215                children: "Browse Data"
1216              }
1217            ) })
1218          ] }, idx)) })
1219        ] })
1220      ] })
1221    ] }) }),
1222    viewingTable && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl w-full max-w-6xl h-[85vh] flex flex-col shadow-2xl border border-zinc-200 dark:border-zinc-800", children: [
1223      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800 shrink-0", children: [
1224        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
1225          /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { className: "text-emerald-500", size: 20 }),
1226          "Browsing Table: ",
1227          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-indigo-400", children: viewingTable })
1228        ] }),
1229        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-4 flex-1 justify-end mr-4", children: tableData && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 bg-zinc-100 dark:bg-zinc-800 rounded-lg p-1 w-full max-w-md", children: [
1230          /* @__PURE__ */ jsxRuntimeExports.jsxs(
1231            "select",
1232            {
1233              value: tableSearchField,
1234              onChange: (e) => {
1235                setTableSearchField(e.target.value);
1236                setTableCurrentPage(1);
1237              },
1238              className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200 border-none text-xs rounded py-1 px-2 focus:ring-1 focus:ring-indigo-500 max-w-[120px]",
1239              children: [
1240                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "ALL", children: "All Fields" }),
1241                tableData.columns.map((col) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: col, children: col }, col))
1242              ]
1243            }
1244          ),
1245          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1", children: [
1246            /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 text-zinc-400", size: 14 }),
1247            /* @__PURE__ */ jsxRuntimeExports.jsx(
1248              "input",
1249              {
1250                type: "text",
1251                value: tableSearchQuery,
1252                onChange: (e) => {
1253                  setTableSearchQuery(e.target.value);
1254                  setTableCurrentPage(1);
1255                },
1256                placeholder: "Search records...",
1257                className: "w-full bg-transparent border-none text-sm text-zinc-800 dark:text-zinc-200 py-1 pl-8 pr-2 focus:outline-none focus:ring-0"
1258              }
1259            )
1260          ] })
1261        ] }) }),
1262        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => {
1263          setViewingTable(null);
1264          setTableData(null);
1265        }, className: "text-zinc-500 hover:text-red-500 p-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { size: 20 }) })
1266      ] }),
1267      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-auto bg-zinc-50 dark:bg-zinc-900 p-0 relative", children: isTableLoading ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
1268        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin" }),
1269        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-4 text-sm text-zinc-500 animate-pulse", children: "Scanning backup dump on the fly..." })
1270      ] }) : tableData ? /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-xs border-collapse", children: [
1271        /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "sticky top-0 z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "bg-zinc-200 dark:bg-zinc-800 shadow-sm", children: tableData.columns.map((col, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-2 border border-zinc-300 dark:border-zinc-700 font-bold text-zinc-700 dark:text-zinc-300 whitespace-nowrap", children: col }, idx)) }) }),
1272        /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: filteredTableRows.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { colSpan: tableData.columns.length, className: "p-8 text-center text-zinc-500", children: [
1273          "No rows found ",
1274          tableSearchQuery && "matching your search ",
1275          "in this backup for table ",
1276          /* @__PURE__ */ jsxRuntimeExports.jsx("b", { children: viewingTable }),
1277          "."
1278        ] }) }) : filteredTableRows.slice((tableCurrentPage - 1) * rowsPerPage, tableCurrentPage * rowsPerPage).map((row, rowIdx) => /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "hover:bg-zinc-100 dark:hover:bg-zinc-800/40", children: tableData.columns.map((col, colIdx) => {
1279          const cellValue = row[col];
1280          const isNull = cellValue === "\\N" || cellValue === null || cellValue === void 0;
1281          const displayValue = isNull ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 italic", children: "null" }) : typeof cellValue === "object" ? JSON.stringify(cellValue) : String(cellValue);
1282          return /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 border border-zinc-200 dark:border-zinc-700 text-zinc-800 dark:text-zinc-300 max-w-[200px] truncate", title: typeof cellValue === "object" ? JSON.stringify(cellValue) : String(cellValue || ""), children: displayValue }, colIdx);
1283        }) }, rowIdx)) })
1284      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-zinc-500", children: "Failed to load table data." }) }),
1285      tableData && tableData.limitReached && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 bg-amber-50 dark:bg-amber-900/20 border-t border-amber-200 dark:border-amber-800 text-amber-700 dark:text-amber-400 text-xs text-center shrink-0 flex items-center justify-center gap-2 font-semibold", children: [
1286        /* @__PURE__ */ jsxRuntimeExports.jsx(Archive, { size: 14 }),
1287        " Showing first 500 rows to prevent browser performance issues."
1288      ] }),
1289      tableData && filteredTableRows.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 border-t border-zinc-200 dark:border-zinc-800 shrink-0 bg-white dark:bg-zinc-900 flex justify-between items-center text-xs", children: [
1290        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-500", children: [
1291          "Showing ",
1292          (tableCurrentPage - 1) * rowsPerPage + 1,
1293          " to ",
1294          Math.min(tableCurrentPage * rowsPerPage, filteredTableRows.length),
1295          " of ",
1296          filteredTableRows.length,
1297          " entries"
1298        ] }),
1299        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
1300          /* @__PURE__ */ jsxRuntimeExports.jsx(
1301            "button",
1302            {
1303              disabled: tableCurrentPage === 1,
1304              onClick: () => setTableCurrentPage(Math.max(1, tableCurrentPage - 1)),
1305              className: "px-3 py-1 bg-zinc-100 dark:bg-zinc-800 rounded font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700 disabled:opacity-50 transition-colors",
1306              children: "Previous"
1307            }
1308          ),
1309          /* @__PURE__ */ jsxRuntimeExports.jsx(
1310            "button",
1311            {
1312              disabled: tableCurrentPage * rowsPerPage >= filteredTableRows.length,
1313              onClick: () => setTableCurrentPage(tableCurrentPage + 1),
1314              className: "px-3 py-1 bg-zinc-100 dark:bg-zinc-800 rounded font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700 disabled:opacity-50 transition-colors",
1315              children: "Next"
1316            }
1317          )
1318        ] })
1319      ] }),
1320      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 shrink-0 bg-white dark:bg-zinc-900 flex justify-between items-center text-xs text-zinc-500", children: tableData && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1321        "Loaded ",
1322        tableData.rows.length,
1323        " rows directly from SQL dump. ",
1324        tableSearchQuery && `Showing ${filteredTableRows.length} matching rows.`
1325      ] }) })
1326    ] }) })
1327  ] });
1328};
1329export {
1330  BackupManager as default
1331};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.