mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-14 06:12:19 +00:00
Lint and reformat.
This commit is contained in:
committed by
Elisiário Couto
parent
22ec0e36b1
commit
222bb2ec64
@@ -58,9 +58,11 @@ export default function S3BackupConfigDrawer({
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
toast.success("S3 backup configuration saved successfully");
|
toast.success("S3 backup configuration saved successfully");
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: Error & { response?: { data?: { detail?: string } } }) => {
|
||||||
console.error("Failed to update S3 backup configuration:", error);
|
console.error("Failed to update S3 backup configuration:", error);
|
||||||
const message = error?.response?.data?.detail || "Failed to save S3 configuration. Please check your settings and try again.";
|
const message =
|
||||||
|
error?.response?.data?.detail ||
|
||||||
|
"Failed to save S3 configuration. Please check your settings and try again.";
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -73,11 +75,15 @@ export default function S3BackupConfigDrawer({
|
|||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
console.log("S3 connection test successful");
|
console.log("S3 connection test successful");
|
||||||
toast.success("S3 connection test successful! Your configuration is working correctly.");
|
toast.success(
|
||||||
|
"S3 connection test successful! Your configuration is working correctly.",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: Error & { response?: { data?: { detail?: string } } }) => {
|
||||||
console.error("Failed to test S3 connection:", error);
|
console.error("Failed to test S3 connection:", error);
|
||||||
const message = error?.response?.data?.detail || "S3 connection test failed. Please verify your credentials and settings.";
|
const message =
|
||||||
|
error?.response?.data?.detail ||
|
||||||
|
"S3 connection test failed. Please verify your credentials and settings.";
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -98,9 +104,7 @@ export default function S3BackupConfigDrawer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerTrigger asChild>
|
<DrawerTrigger asChild>{trigger || <EditButton />}</DrawerTrigger>
|
||||||
{trigger || <EditButton />}
|
|
||||||
</DrawerTrigger>
|
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<div className="mx-auto w-full max-w-sm">
|
<div className="mx-auto w-full max-w-sm">
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
@@ -148,7 +152,10 @@ export default function S3BackupConfigDrawer({
|
|||||||
type="password"
|
type="password"
|
||||||
value={config.secret_access_key}
|
value={config.secret_access_key}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setConfig({ ...config, secret_access_key: e.target.value })
|
setConfig({
|
||||||
|
...config,
|
||||||
|
secret_access_key: e.target.value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
placeholder="Your AWS Secret Access Key"
|
placeholder="Your AWS Secret Access Key"
|
||||||
required
|
required
|
||||||
@@ -212,15 +219,21 @@ export default function S3BackupConfigDrawer({
|
|||||||
<Label htmlFor="path_style">Use path-style addressing</Label>
|
<Label htmlFor="path_style">Use path-style addressing</Label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Enable for older S3 implementations or certain S3-compatible services
|
Enable for older S3 implementations or certain S3-compatible
|
||||||
|
services
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DrawerFooter className="px-0">
|
<DrawerFooter className="px-0">
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button type="submit" disabled={updateMutation.isPending || !config.enabled}>
|
<Button
|
||||||
{updateMutation.isPending ? "Saving..." : "Save Configuration"}
|
type="submit"
|
||||||
|
disabled={updateMutation.isPending || !config.enabled}
|
||||||
|
>
|
||||||
|
{updateMutation.isPending
|
||||||
|
? "Saving..."
|
||||||
|
: "Save Configuration"}
|
||||||
</Button>
|
</Button>
|
||||||
{config.enabled && isConfigValid && (
|
{config.enabled && isConfigValid && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -203,8 +203,10 @@ export default function Settings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = accountsLoading || settingsLoading || servicesLoading || backupLoading;
|
const isLoading =
|
||||||
const hasError = accountsError || settingsError || servicesError || backupError;
|
accountsLoading || settingsLoading || servicesLoading || backupLoading;
|
||||||
|
const hasError =
|
||||||
|
accountsError || settingsError || servicesError || backupError;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <AccountsSkeleton />;
|
return <AccountsSkeleton />;
|
||||||
@@ -757,7 +759,8 @@ export default function Settings() {
|
|||||||
<span>S3 Backup Configuration</span>
|
<span>S3 Backup Configuration</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Configure automatic database backups to Amazon S3 or S3-compatible storage
|
Configure automatic database backups to Amazon S3 or
|
||||||
|
S3-compatible storage
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@@ -769,7 +772,8 @@ export default function Settings() {
|
|||||||
No S3 backup configured
|
No S3 backup configured
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-muted-foreground mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
Set up S3 backup to automatically backup your database to the cloud.
|
Set up S3 backup to automatically backup your database to
|
||||||
|
the cloud.
|
||||||
</p>
|
</p>
|
||||||
<S3BackupConfigDrawer settings={backupSettings} />
|
<S3BackupConfigDrawer settings={backupSettings} />
|
||||||
</div>
|
</div>
|
||||||
@@ -786,26 +790,33 @@ export default function Settings() {
|
|||||||
S3 Backup
|
S3 Backup
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className={`w-2 h-2 rounded-full ${
|
<div
|
||||||
backupSettings.s3.enabled
|
className={`w-2 h-2 rounded-full ${
|
||||||
? 'bg-green-500'
|
backupSettings.s3.enabled
|
||||||
: 'bg-muted-foreground'
|
? "bg-green-500"
|
||||||
}`} />
|
: "bg-muted-foreground"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{backupSettings.s3.enabled ? 'Enabled' : 'Disabled'}
|
{backupSettings.s3.enabled
|
||||||
|
? "Enabled"
|
||||||
|
: "Disabled"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="font-medium">Bucket:</span> {backupSettings.s3.bucket_name}
|
<span className="font-medium">Bucket:</span>{" "}
|
||||||
|
{backupSettings.s3.bucket_name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="font-medium">Region:</span> {backupSettings.s3.region}
|
<span className="font-medium">Region:</span>{" "}
|
||||||
|
{backupSettings.s3.region}
|
||||||
</p>
|
</p>
|
||||||
{backupSettings.s3.endpoint_url && (
|
{backupSettings.s3.endpoint_url && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
<span className="font-medium">Endpoint:</span> {backupSettings.s3.endpoint_url}
|
<span className="font-medium">Endpoint:</span>{" "}
|
||||||
|
{backupSettings.s3.endpoint_url}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -813,16 +824,17 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
<S3BackupConfigDrawer settings={backupSettings} />
|
<S3BackupConfigDrawer settings={backupSettings} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-muted rounded-lg">
|
<div className="p-4 bg-muted rounded-lg">
|
||||||
<h5 className="font-medium mb-2">Backup Information</h5>
|
<h5 className="font-medium mb-2">Backup Information</h5>
|
||||||
<p className="text-sm text-muted-foreground mb-3">
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
Database backups are stored in the "leggen_backups/" folder in your S3 bucket.
|
Database backups are stored in the "leggen_backups/"
|
||||||
Backups include the complete SQLite database file.
|
folder in your S3 bucket. Backups include the complete
|
||||||
|
SQLite database file.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// TODO: Implement manual backup trigger
|
// TODO: Implement manual backup trigger
|
||||||
@@ -831,8 +843,8 @@ export default function Settings() {
|
|||||||
>
|
>
|
||||||
Create Backup Now
|
Create Backup Now
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// TODO: Implement backup list view
|
// TODO: Implement backup list view
|
||||||
|
|||||||
@@ -281,9 +281,8 @@ export const apiClient = {
|
|||||||
|
|
||||||
// Backup endpoints
|
// Backup endpoints
|
||||||
getBackupSettings: async (): Promise<BackupSettings> => {
|
getBackupSettings: async (): Promise<BackupSettings> => {
|
||||||
const response = await api.get<ApiResponse<BackupSettings>>(
|
const response =
|
||||||
"/backup/settings",
|
await api.get<ApiResponse<BackupSettings>>("/backup/settings");
|
||||||
);
|
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -8,170 +8,170 @@
|
|||||||
// You should NOT make any changes in this file as it will be overwritten.
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from "./routes/__root";
|
||||||
import { Route as TransactionsRouteImport } from './routes/transactions'
|
import { Route as TransactionsRouteImport } from "./routes/transactions";
|
||||||
import { Route as SystemRouteImport } from './routes/system'
|
import { Route as SystemRouteImport } from "./routes/system";
|
||||||
import { Route as SettingsRouteImport } from './routes/settings'
|
import { Route as SettingsRouteImport } from "./routes/settings";
|
||||||
import { Route as NotificationsRouteImport } from './routes/notifications'
|
import { Route as NotificationsRouteImport } from "./routes/notifications";
|
||||||
import { Route as BankConnectedRouteImport } from './routes/bank-connected'
|
import { Route as BankConnectedRouteImport } from "./routes/bank-connected";
|
||||||
import { Route as AnalyticsRouteImport } from './routes/analytics'
|
import { Route as AnalyticsRouteImport } from "./routes/analytics";
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from "./routes/index";
|
||||||
|
|
||||||
const TransactionsRoute = TransactionsRouteImport.update({
|
const TransactionsRoute = TransactionsRouteImport.update({
|
||||||
id: '/transactions',
|
id: "/transactions",
|
||||||
path: '/transactions',
|
path: "/transactions",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const SystemRoute = SystemRouteImport.update({
|
const SystemRoute = SystemRouteImport.update({
|
||||||
id: '/system',
|
id: "/system",
|
||||||
path: '/system',
|
path: "/system",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const SettingsRoute = SettingsRouteImport.update({
|
const SettingsRoute = SettingsRouteImport.update({
|
||||||
id: '/settings',
|
id: "/settings",
|
||||||
path: '/settings',
|
path: "/settings",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const NotificationsRoute = NotificationsRouteImport.update({
|
const NotificationsRoute = NotificationsRouteImport.update({
|
||||||
id: '/notifications',
|
id: "/notifications",
|
||||||
path: '/notifications',
|
path: "/notifications",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const BankConnectedRoute = BankConnectedRouteImport.update({
|
const BankConnectedRoute = BankConnectedRouteImport.update({
|
||||||
id: '/bank-connected',
|
id: "/bank-connected",
|
||||||
path: '/bank-connected',
|
path: "/bank-connected",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const AnalyticsRoute = AnalyticsRouteImport.update({
|
const AnalyticsRoute = AnalyticsRouteImport.update({
|
||||||
id: '/analytics',
|
id: "/analytics",
|
||||||
path: '/analytics',
|
path: "/analytics",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
const IndexRoute = IndexRouteImport.update({
|
const IndexRoute = IndexRouteImport.update({
|
||||||
id: '/',
|
id: "/",
|
||||||
path: '/',
|
path: "/",
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any);
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
"/": typeof IndexRoute;
|
||||||
'/analytics': typeof AnalyticsRoute
|
"/analytics": typeof AnalyticsRoute;
|
||||||
'/bank-connected': typeof BankConnectedRoute
|
"/bank-connected": typeof BankConnectedRoute;
|
||||||
'/notifications': typeof NotificationsRoute
|
"/notifications": typeof NotificationsRoute;
|
||||||
'/settings': typeof SettingsRoute
|
"/settings": typeof SettingsRoute;
|
||||||
'/system': typeof SystemRoute
|
"/system": typeof SystemRoute;
|
||||||
'/transactions': typeof TransactionsRoute
|
"/transactions": typeof TransactionsRoute;
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
"/": typeof IndexRoute;
|
||||||
'/analytics': typeof AnalyticsRoute
|
"/analytics": typeof AnalyticsRoute;
|
||||||
'/bank-connected': typeof BankConnectedRoute
|
"/bank-connected": typeof BankConnectedRoute;
|
||||||
'/notifications': typeof NotificationsRoute
|
"/notifications": typeof NotificationsRoute;
|
||||||
'/settings': typeof SettingsRoute
|
"/settings": typeof SettingsRoute;
|
||||||
'/system': typeof SystemRoute
|
"/system": typeof SystemRoute;
|
||||||
'/transactions': typeof TransactionsRoute
|
"/transactions": typeof TransactionsRoute;
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport;
|
||||||
'/': typeof IndexRoute
|
"/": typeof IndexRoute;
|
||||||
'/analytics': typeof AnalyticsRoute
|
"/analytics": typeof AnalyticsRoute;
|
||||||
'/bank-connected': typeof BankConnectedRoute
|
"/bank-connected": typeof BankConnectedRoute;
|
||||||
'/notifications': typeof NotificationsRoute
|
"/notifications": typeof NotificationsRoute;
|
||||||
'/settings': typeof SettingsRoute
|
"/settings": typeof SettingsRoute;
|
||||||
'/system': typeof SystemRoute
|
"/system": typeof SystemRoute;
|
||||||
'/transactions': typeof TransactionsRoute
|
"/transactions": typeof TransactionsRoute;
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| "/"
|
||||||
| '/analytics'
|
| "/analytics"
|
||||||
| '/bank-connected'
|
| "/bank-connected"
|
||||||
| '/notifications'
|
| "/notifications"
|
||||||
| '/settings'
|
| "/settings"
|
||||||
| '/system'
|
| "/system"
|
||||||
| '/transactions'
|
| "/transactions";
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo;
|
||||||
to:
|
to:
|
||||||
| '/'
|
| "/"
|
||||||
| '/analytics'
|
| "/analytics"
|
||||||
| '/bank-connected'
|
| "/bank-connected"
|
||||||
| '/notifications'
|
| "/notifications"
|
||||||
| '/settings'
|
| "/settings"
|
||||||
| '/system'
|
| "/system"
|
||||||
| '/transactions'
|
| "/transactions";
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| "__root__"
|
||||||
| '/'
|
| "/"
|
||||||
| '/analytics'
|
| "/analytics"
|
||||||
| '/bank-connected'
|
| "/bank-connected"
|
||||||
| '/notifications'
|
| "/notifications"
|
||||||
| '/settings'
|
| "/settings"
|
||||||
| '/system'
|
| "/system"
|
||||||
| '/transactions'
|
| "/transactions";
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById;
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute;
|
||||||
AnalyticsRoute: typeof AnalyticsRoute
|
AnalyticsRoute: typeof AnalyticsRoute;
|
||||||
BankConnectedRoute: typeof BankConnectedRoute
|
BankConnectedRoute: typeof BankConnectedRoute;
|
||||||
NotificationsRoute: typeof NotificationsRoute
|
NotificationsRoute: typeof NotificationsRoute;
|
||||||
SettingsRoute: typeof SettingsRoute
|
SettingsRoute: typeof SettingsRoute;
|
||||||
SystemRoute: typeof SystemRoute
|
SystemRoute: typeof SystemRoute;
|
||||||
TransactionsRoute: typeof TransactionsRoute
|
TransactionsRoute: typeof TransactionsRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module "@tanstack/react-router" {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
'/transactions': {
|
"/transactions": {
|
||||||
id: '/transactions'
|
id: "/transactions";
|
||||||
path: '/transactions'
|
path: "/transactions";
|
||||||
fullPath: '/transactions'
|
fullPath: "/transactions";
|
||||||
preLoaderRoute: typeof TransactionsRouteImport
|
preLoaderRoute: typeof TransactionsRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/system': {
|
"/system": {
|
||||||
id: '/system'
|
id: "/system";
|
||||||
path: '/system'
|
path: "/system";
|
||||||
fullPath: '/system'
|
fullPath: "/system";
|
||||||
preLoaderRoute: typeof SystemRouteImport
|
preLoaderRoute: typeof SystemRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/settings': {
|
"/settings": {
|
||||||
id: '/settings'
|
id: "/settings";
|
||||||
path: '/settings'
|
path: "/settings";
|
||||||
fullPath: '/settings'
|
fullPath: "/settings";
|
||||||
preLoaderRoute: typeof SettingsRouteImport
|
preLoaderRoute: typeof SettingsRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/notifications': {
|
"/notifications": {
|
||||||
id: '/notifications'
|
id: "/notifications";
|
||||||
path: '/notifications'
|
path: "/notifications";
|
||||||
fullPath: '/notifications'
|
fullPath: "/notifications";
|
||||||
preLoaderRoute: typeof NotificationsRouteImport
|
preLoaderRoute: typeof NotificationsRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/bank-connected': {
|
"/bank-connected": {
|
||||||
id: '/bank-connected'
|
id: "/bank-connected";
|
||||||
path: '/bank-connected'
|
path: "/bank-connected";
|
||||||
fullPath: '/bank-connected'
|
fullPath: "/bank-connected";
|
||||||
preLoaderRoute: typeof BankConnectedRouteImport
|
preLoaderRoute: typeof BankConnectedRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/analytics': {
|
"/analytics": {
|
||||||
id: '/analytics'
|
id: "/analytics";
|
||||||
path: '/analytics'
|
path: "/analytics";
|
||||||
fullPath: '/analytics'
|
fullPath: "/analytics";
|
||||||
preLoaderRoute: typeof AnalyticsRouteImport
|
preLoaderRoute: typeof AnalyticsRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
'/': {
|
"/": {
|
||||||
id: '/'
|
id: "/";
|
||||||
path: '/'
|
path: "/";
|
||||||
fullPath: '/'
|
fullPath: "/";
|
||||||
preLoaderRoute: typeof IndexRouteImport
|
preLoaderRoute: typeof IndexRouteImport;
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
SettingsRoute: SettingsRoute,
|
SettingsRoute: SettingsRoute,
|
||||||
SystemRoute: SystemRoute,
|
SystemRoute: SystemRoute,
|
||||||
TransactionsRoute: TransactionsRoute,
|
TransactionsRoute: TransactionsRoute,
|
||||||
}
|
};
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>()
|
._addFileTypes<FileRouteTypes>();
|
||||||
|
|||||||
@@ -7,32 +7,34 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
class S3Config(BaseModel):
|
class S3Config(BaseModel):
|
||||||
"""S3 backup configuration model for API."""
|
"""S3 backup configuration model for API."""
|
||||||
|
|
||||||
access_key_id: str = Field(..., description="AWS S3 access key ID")
|
access_key_id: str = Field(..., description="AWS S3 access key ID")
|
||||||
secret_access_key: str = Field(..., description="AWS S3 secret access key")
|
secret_access_key: str = Field(..., description="AWS S3 secret access key")
|
||||||
bucket_name: str = Field(..., description="S3 bucket name")
|
bucket_name: str = Field(..., description="S3 bucket name")
|
||||||
region: str = Field(default="us-east-1", description="AWS S3 region")
|
region: str = Field(default="us-east-1", description="AWS S3 region")
|
||||||
endpoint_url: Optional[str] = Field(default=None, description="Custom S3 endpoint URL")
|
endpoint_url: Optional[str] = Field(
|
||||||
|
default=None, description="Custom S3 endpoint URL"
|
||||||
|
)
|
||||||
path_style: bool = Field(default=False, description="Use path-style addressing")
|
path_style: bool = Field(default=False, description="Use path-style addressing")
|
||||||
enabled: bool = Field(default=True, description="Enable S3 backups")
|
enabled: bool = Field(default=True, description="Enable S3 backups")
|
||||||
|
|
||||||
|
|
||||||
class BackupSettings(BaseModel):
|
class BackupSettings(BaseModel):
|
||||||
"""Backup settings model for API."""
|
"""Backup settings model for API."""
|
||||||
|
|
||||||
s3: Optional[S3Config] = None
|
s3: Optional[S3Config] = None
|
||||||
|
|
||||||
|
|
||||||
class BackupTest(BaseModel):
|
class BackupTest(BaseModel):
|
||||||
"""Backup connection test request model."""
|
"""Backup connection test request model."""
|
||||||
|
|
||||||
service: str = Field(..., description="Backup service type (s3)")
|
service: str = Field(..., description="Backup service type (s3)")
|
||||||
config: S3Config = Field(..., description="S3 configuration to test")
|
config: S3Config = Field(..., description="S3 configuration to test")
|
||||||
|
|
||||||
|
|
||||||
class BackupInfo(BaseModel):
|
class BackupInfo(BaseModel):
|
||||||
"""Backup file information model."""
|
"""Backup file information model."""
|
||||||
|
|
||||||
key: str = Field(..., description="S3 object key")
|
key: str = Field(..., description="S3 object key")
|
||||||
last_modified: str = Field(..., description="Last modified timestamp (ISO format)")
|
last_modified: str = Field(..., description="Last modified timestamp (ISO format)")
|
||||||
size: int = Field(..., description="File size in bytes")
|
size: int = Field(..., description="File size in bytes")
|
||||||
@@ -40,6 +42,8 @@ class BackupInfo(BaseModel):
|
|||||||
|
|
||||||
class BackupOperation(BaseModel):
|
class BackupOperation(BaseModel):
|
||||||
"""Backup operation request model."""
|
"""Backup operation request model."""
|
||||||
|
|
||||||
operation: str = Field(..., description="Operation type (backup, restore)")
|
operation: str = Field(..., description="Operation type (backup, restore)")
|
||||||
backup_key: Optional[str] = Field(default=None, description="Backup key for restore operations")
|
backup_key: Optional[str] = Field(
|
||||||
|
default=None, description="Backup key for restore operations"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""API routes for backup management."""
|
"""API routes for backup management."""
|
||||||
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -24,10 +23,10 @@ async def get_backup_settings() -> APIResponse:
|
|||||||
"""Get current backup settings."""
|
"""Get current backup settings."""
|
||||||
try:
|
try:
|
||||||
backup_config = config.backup_config
|
backup_config = config.backup_config
|
||||||
|
|
||||||
# Build response safely without exposing secrets
|
# Build response safely without exposing secrets
|
||||||
s3_config = backup_config.get("s3", {})
|
s3_config = backup_config.get("s3", {})
|
||||||
|
|
||||||
settings = BackupSettings(
|
settings = BackupSettings(
|
||||||
s3=S3Config(
|
s3=S3Config(
|
||||||
access_key_id="***" if s3_config.get("access_key_id") else "",
|
access_key_id="***" if s3_config.get("access_key_id") else "",
|
||||||
@@ -41,13 +40,13 @@ async def get_backup_settings() -> APIResponse:
|
|||||||
if s3_config.get("bucket_name")
|
if s3_config.get("bucket_name")
|
||||||
else None,
|
else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
data=settings,
|
data=settings,
|
||||||
message="Backup settings retrieved successfully",
|
message="Backup settings retrieved successfully",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get backup settings: {e}")
|
logger.error(f"Failed to get backup settings: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -71,20 +70,20 @@ async def update_backup_settings(settings: BackupSettings) -> APIResponse:
|
|||||||
path_style=settings.s3.path_style,
|
path_style=settings.s3.path_style,
|
||||||
enabled=settings.s3.enabled,
|
enabled=settings.s3.enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test connection
|
# Test connection
|
||||||
backup_service = BackupService()
|
backup_service = BackupService()
|
||||||
connection_success = await backup_service.test_connection(s3_config)
|
connection_success = await backup_service.test_connection(s3_config)
|
||||||
|
|
||||||
if not connection_success:
|
if not connection_success:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail="S3 connection test failed. Please check your configuration."
|
detail="S3 connection test failed. Please check your configuration.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update backup config
|
# Update backup config
|
||||||
backup_config = {}
|
backup_config = {}
|
||||||
|
|
||||||
if settings.s3:
|
if settings.s3:
|
||||||
backup_config["s3"] = {
|
backup_config["s3"] = {
|
||||||
"access_key_id": settings.s3.access_key_id,
|
"access_key_id": settings.s3.access_key_id,
|
||||||
@@ -95,17 +94,17 @@ async def update_backup_settings(settings: BackupSettings) -> APIResponse:
|
|||||||
"path_style": settings.s3.path_style,
|
"path_style": settings.s3.path_style,
|
||||||
"enabled": settings.s3.enabled,
|
"enabled": settings.s3.enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save to config
|
# Save to config
|
||||||
if backup_config:
|
if backup_config:
|
||||||
config.update_section("backup", backup_config)
|
config.update_section("backup", backup_config)
|
||||||
|
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
data={"updated": True},
|
data={"updated": True},
|
||||||
message="Backup settings updated successfully",
|
message="Backup settings updated successfully",
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -123,7 +122,7 @@ async def test_backup_connection(test_request: BackupTest) -> APIResponse:
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="Only 's3' service is supported"
|
status_code=400, detail="Only 's3' service is supported"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert API model to config model
|
# Convert API model to config model
|
||||||
s3_config = S3BackupConfig(
|
s3_config = S3BackupConfig(
|
||||||
access_key_id=test_request.config.access_key_id,
|
access_key_id=test_request.config.access_key_id,
|
||||||
@@ -134,10 +133,10 @@ async def test_backup_connection(test_request: BackupTest) -> APIResponse:
|
|||||||
path_style=test_request.config.path_style,
|
path_style=test_request.config.path_style,
|
||||||
enabled=test_request.config.enabled,
|
enabled=test_request.config.enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
backup_service = BackupService()
|
backup_service = BackupService()
|
||||||
success = await backup_service.test_connection(s3_config)
|
success = await backup_service.test_connection(s3_config)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
@@ -149,7 +148,7 @@ async def test_backup_connection(test_request: BackupTest) -> APIResponse:
|
|||||||
success=False,
|
success=False,
|
||||||
message="S3 connection test failed",
|
message="S3 connection test failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -164,26 +163,26 @@ async def list_backups() -> APIResponse:
|
|||||||
"""List available backups."""
|
"""List available backups."""
|
||||||
try:
|
try:
|
||||||
backup_config = config.backup_config.get("s3", {})
|
backup_config = config.backup_config.get("s3", {})
|
||||||
|
|
||||||
if not backup_config.get("bucket_name"):
|
if not backup_config.get("bucket_name"):
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
data=[],
|
data=[],
|
||||||
message="No S3 backup configuration found",
|
message="No S3 backup configuration found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert config to model
|
# Convert config to model
|
||||||
s3_config = S3BackupConfig(**backup_config)
|
s3_config = S3BackupConfig(**backup_config)
|
||||||
backup_service = BackupService(s3_config)
|
backup_service = BackupService(s3_config)
|
||||||
|
|
||||||
backups = await backup_service.list_backups()
|
backups = await backup_service.list_backups()
|
||||||
|
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
data=backups,
|
data=backups,
|
||||||
message=f"Found {len(backups)} backups",
|
message=f"Found {len(backups)} backups",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to list backups: {e}")
|
logger.error(f"Failed to list backups: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -196,12 +195,10 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
|
|||||||
"""Perform backup operation (backup or restore)."""
|
"""Perform backup operation (backup or restore)."""
|
||||||
try:
|
try:
|
||||||
backup_config = config.backup_config.get("s3", {})
|
backup_config = config.backup_config.get("s3", {})
|
||||||
|
|
||||||
if not backup_config.get("bucket_name"):
|
if not backup_config.get("bucket_name"):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="S3 backup is not configured")
|
||||||
status_code=400, detail="S3 backup is not configured"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert config to model with validation
|
# Convert config to model with validation
|
||||||
try:
|
try:
|
||||||
s3_config = S3BackupConfig(**backup_config)
|
s3_config = S3BackupConfig(**backup_config)
|
||||||
@@ -209,14 +206,14 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail=f"Invalid S3 configuration: {str(e)}"
|
status_code=400, detail=f"Invalid S3 configuration: {str(e)}"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
backup_service = BackupService(s3_config)
|
backup_service = BackupService(s3_config)
|
||||||
|
|
||||||
if operation_request.operation == "backup":
|
if operation_request.operation == "backup":
|
||||||
# Backup database
|
# Backup database
|
||||||
database_path = path_manager.get_database_path()
|
database_path = path_manager.get_database_path()
|
||||||
success = await backup_service.backup_database(database_path)
|
success = await backup_service.backup_database(database_path)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
@@ -228,19 +225,20 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
|
|||||||
success=False,
|
success=False,
|
||||||
message="Database backup failed",
|
message="Database backup failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
elif operation_request.operation == "restore":
|
elif operation_request.operation == "restore":
|
||||||
if not operation_request.backup_key:
|
if not operation_request.backup_key:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="backup_key is required for restore operation"
|
status_code=400,
|
||||||
|
detail="backup_key is required for restore operation",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
database_path = path_manager.get_database_path()
|
database_path = path_manager.get_database_path()
|
||||||
success = await backup_service.restore_database(
|
success = await backup_service.restore_database(
|
||||||
operation_request.backup_key, database_path
|
operation_request.backup_key, database_path
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
return APIResponse(
|
return APIResponse(
|
||||||
success=True,
|
success=True,
|
||||||
@@ -256,11 +254,11 @@ async def backup_operation(operation_request: BackupOperation) -> APIResponse:
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="Invalid operation. Use 'backup' or 'restore'"
|
status_code=400, detail="Invalid operation. Use 'backup' or 'restore'"
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to perform backup operation: {e}")
|
logger.error(f"Failed to perform backup operation: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500, detail=f"Failed to perform backup operation: {str(e)}"
|
status_code=500, detail=f"Failed to perform backup operation: {str(e)}"
|
||||||
) from e
|
) from e
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ class S3BackupConfig(BaseModel):
|
|||||||
secret_access_key: str = Field(..., description="AWS S3 secret access key")
|
secret_access_key: str = Field(..., description="AWS S3 secret access key")
|
||||||
bucket_name: str = Field(..., description="S3 bucket name")
|
bucket_name: str = Field(..., description="S3 bucket name")
|
||||||
region: str = Field(default="us-east-1", description="AWS S3 region")
|
region: str = Field(default="us-east-1", description="AWS S3 region")
|
||||||
endpoint_url: Optional[str] = Field(default=None, description="Custom S3 endpoint URL")
|
endpoint_url: Optional[str] = Field(
|
||||||
|
default=None, description="Custom S3 endpoint URL"
|
||||||
|
)
|
||||||
path_style: bool = Field(default=False, description="Use path-style addressing")
|
path_style: bool = Field(default=False, description="Use path-style addressing")
|
||||||
enabled: bool = Field(default=True, description="Enable S3 backups")
|
enabled: bool = Field(default=True, description="Enable S3 backups")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user