feat: database management - auto/USB backup toggles, folder browser, cron jobs
This commit is contained in:
@@ -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 "USB Backup" 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}
|
||||
|
||||
Reference in New Issue
Block a user