feat: database management - auto/USB backup toggles, folder browser, cron jobs

This commit is contained in:
ff
2026-04-11 00:32:39 -04:00
parent b9a7ddb6d7
commit 4025ca45e0
218 changed files with 1995 additions and 1381 deletions

View File

@@ -3,6 +3,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import {
AlertDialog,
AlertDialogAction,
@@ -16,11 +17,13 @@ import {
import { FolderOpen, Trash2 } from "lucide-react";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import { FolderBrowserModal } from "./folder-browser-modal";
export function BackupDestinationManager() {
const { toast } = useToast();
const [path, setPath] = useState("");
const [deleteId, setDeleteId] = useState<number | null>(null);
const [browserOpen, setBrowserOpen] = useState(false);
// ==============================
// Queries
@@ -36,6 +39,39 @@ export function BackupDestinationManager() {
},
});
const { data: usbSettingData } = useQuery({
queryKey: ["/db/usb-backup-setting"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/database-management/usb-backup-setting");
return res.json();
},
});
const usbBackupEnabled = usbSettingData?.usbBackupEnabled ?? false;
const usbToggleMutation = useMutation({
mutationFn: async (enabled: boolean) => {
const res = await apiRequest("PUT", "/api/database-management/usb-backup-setting", {
usbBackupEnabled: enabled,
});
return res.json();
},
onSuccess: (data) => {
queryClient.setQueryData(["/db/usb-backup-setting"], data);
toast({
title: "Setting Saved",
description: `USB backup ${data.usbBackupEnabled ? "enabled" : "disabled"}.`,
});
},
onError: () => {
toast({
title: "Error",
description: "Failed to update USB backup setting.",
variant: "destructive",
});
},
});
// ==============================
// Mutations
// ==============================
@@ -67,30 +103,10 @@ export function BackupDestinationManager() {
});
// ==============================
// Folder picker (browser limitation)
// Folder browser
// ==============================
const openFolderPicker = async () => {
// @ts-ignore
if (!window.showDirectoryPicker) {
toast({
title: "Not supported",
description: "Your browser does not support folder picking",
variant: "destructive",
});
return;
}
try {
// @ts-ignore
const dirHandle = await window.showDirectoryPicker();
toast({
title: "Folder selected",
description: `Selected folder: ${dirHandle.name}. Please enter the full path manually.`,
});
} catch {
// user cancelled
}
const handleFolderSelect = (selectedPath: string) => {
setPath(selectedPath);
};
// ==============================
@@ -102,17 +118,46 @@ export function BackupDestinationManager() {
<CardTitle>External Backup Destination</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center space-x-3">
<Switch
id="usb-backup-toggle"
checked={usbBackupEnabled}
onCheckedChange={(checked) => usbToggleMutation.mutate(checked)}
disabled={usbToggleMutation.isPending}
/>
<label
htmlFor="usb-backup-toggle"
className="text-sm font-medium text-gray-700 cursor-pointer select-none"
>
USB Backup
</label>
<span className="text-xs text-gray-400">
(daily at 9 PM saves to the &quot;USB Backup&quot; folder on your drive)
</span>
</div>
<p className="text-sm text-gray-500">
Enter the root path of your USB drive below. The app will automatically back up to the{" "}
<span className="font-medium text-gray-700">USB Backup</span> folder inside it every night at 9 PM when the toggle is on.
</p>
<div className="flex gap-2">
<Input
placeholder="/media/usb-drive or D:\\Backups"
value={path}
onChange={(e) => setPath(e.target.value)}
/>
<Button variant="outline" onClick={openFolderPicker}>
<Button variant="outline" onClick={() => setBrowserOpen(true)} title="Browse folders">
<FolderOpen className="h-4 w-4" />
</Button>
</div>
<FolderBrowserModal
open={browserOpen}
onClose={() => setBrowserOpen(false)}
onSelect={handleFolderSelect}
/>
<Button
onClick={() => saveMutation.mutate()}
disabled={!path || saveMutation.isPending}