@arch-cadre/backup-module 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.cjs +131 -0
- package/dist/actions.d.ts +24 -0
- package/dist/actions.mjs +101 -0
- package/dist/docs/getting-started.md +13 -0
- package/dist/index.cjs +35 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +28 -0
- package/dist/navigation.cjs +15 -0
- package/dist/navigation.d.ts +2 -0
- package/dist/navigation.mjs +11 -0
- package/dist/routes.cjs +13 -0
- package/dist/routes.d.ts +3 -0
- package/dist/routes.mjs +8 -0
- package/dist/schema.cjs +18 -0
- package/dist/schema.d.ts +82 -0
- package/dist/schema.mjs +10 -0
- package/dist/ui/components/backup-actions.cjs +24 -0
- package/dist/ui/components/backup-actions.d.ts +9 -0
- package/dist/ui/components/backup-actions.mjs +16 -0
- package/dist/ui/components/backup-client.cjs +100 -0
- package/dist/ui/components/backup-client.d.ts +7 -0
- package/dist/ui/components/backup-client.mjs +84 -0
- package/dist/ui/pages/backup-list.cjs +56 -0
- package/dist/ui/pages/backup-list.d.ts +1 -0
- package/dist/ui/pages/backup-list.mjs +51 -0
- package/locales/en/global.json +11 -0
- package/manifest.json +11 -0
- package/package.json +56 -0
package/dist/actions.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use server";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.createBackup = createBackup;
|
|
8
|
+
exports.deleteBackup = deleteBackup;
|
|
9
|
+
exports.getBackups = getBackups;
|
|
10
|
+
exports.restoreBackup = restoreBackup;
|
|
11
|
+
var _nodeChild_process = require("node:child_process");
|
|
12
|
+
var _promises = _interopRequireDefault(require("node:fs/promises"));
|
|
13
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
14
|
+
var _nodeUtil = require("node:util");
|
|
15
|
+
var _server = require("@arch-cadre/core/server");
|
|
16
|
+
var _drizzleOrm = require("drizzle-orm");
|
|
17
|
+
var _schema = require("./schema.cjs");
|
|
18
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
19
|
+
const execAsync = (0, _nodeUtil.promisify)(_nodeChild_process.exec);
|
|
20
|
+
const BACKUP_DIR = _nodePath.default.join(process.cwd(), "backups");
|
|
21
|
+
async function getBackups() {
|
|
22
|
+
return await _server.db.select().from(_schema.backupTable).orderBy((0, _drizzleOrm.desc)(_schema.backupTable.createdAt));
|
|
23
|
+
}
|
|
24
|
+
async function createBackup() {
|
|
25
|
+
const timestamp = (/* @__PURE__ */new Date()).toISOString().replace(/[:.]/g, "-");
|
|
26
|
+
const filename = `backup-${timestamp}.sql`;
|
|
27
|
+
const filePath = _nodePath.default.join(BACKUP_DIR, filename);
|
|
28
|
+
try {
|
|
29
|
+
await _promises.default.mkdir(BACKUP_DIR, {
|
|
30
|
+
recursive: true
|
|
31
|
+
});
|
|
32
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
33
|
+
if (!dbUrl) throw new Error("DATABASE_URL is not defined");
|
|
34
|
+
await execAsync(`pg_dump "${dbUrl}" -c --if-exists -x -O > "${filePath}"`);
|
|
35
|
+
const stats = await _promises.default.stat(filePath);
|
|
36
|
+
const [backup] = await _server.db.insert(_schema.backupTable).values({
|
|
37
|
+
filename,
|
|
38
|
+
size: Math.round(stats.size),
|
|
39
|
+
status: "success"
|
|
40
|
+
}).returning();
|
|
41
|
+
const {
|
|
42
|
+
user
|
|
43
|
+
} = await (0, _server.getCurrentSession)();
|
|
44
|
+
await _server.eventBus.publish("activity.create", {
|
|
45
|
+
action: "backup.create",
|
|
46
|
+
description: `Created database backup: ${filename}`,
|
|
47
|
+
userId: user?.id,
|
|
48
|
+
metadata: {
|
|
49
|
+
filename,
|
|
50
|
+
size: stats.size,
|
|
51
|
+
backupId: backup.id
|
|
52
|
+
}
|
|
53
|
+
}, "backup");
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
backup
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[Backup] Create backup failed:", error);
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: error.message
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function restoreBackup(id) {
|
|
67
|
+
try {
|
|
68
|
+
const [backup] = await _server.db.select().from(_schema.backupTable).where((0, _drizzleOrm.eq)(_schema.backupTable.id, id));
|
|
69
|
+
if (!backup) throw new Error("Backup not found");
|
|
70
|
+
const filePath = _nodePath.default.join(BACKUP_DIR, backup.filename);
|
|
71
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
72
|
+
if (!dbUrl) throw new Error("DATABASE_URL is not defined");
|
|
73
|
+
await _promises.default.access(filePath);
|
|
74
|
+
await execAsync(`psql "${dbUrl}" < "${filePath}"`);
|
|
75
|
+
const {
|
|
76
|
+
user
|
|
77
|
+
} = await (0, _server.getCurrentSession)();
|
|
78
|
+
await _server.eventBus.publish("activity.create", {
|
|
79
|
+
action: "backup.restore",
|
|
80
|
+
description: `Restored database from backup: ${backup.filename}`,
|
|
81
|
+
userId: user?.id,
|
|
82
|
+
metadata: {
|
|
83
|
+
filename: backup.filename,
|
|
84
|
+
backupId: backup.id
|
|
85
|
+
}
|
|
86
|
+
}, "backup");
|
|
87
|
+
return {
|
|
88
|
+
success: true
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("[Backup] Restore backup failed:", error);
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: error.message
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function deleteBackup(id) {
|
|
99
|
+
try {
|
|
100
|
+
const [backup] = await _server.db.select().from(_schema.backupTable).where((0, _drizzleOrm.eq)(_schema.backupTable.id, id));
|
|
101
|
+
if (!backup) throw new Error("Backup not found");
|
|
102
|
+
const filePath = _nodePath.default.join(BACKUP_DIR, backup.filename);
|
|
103
|
+
try {
|
|
104
|
+
await _promises.default.unlink(filePath);
|
|
105
|
+
} catch (_e) {
|
|
106
|
+
console.warn(`[Backup] File ${filePath} could not be deleted or doesn't exist`);
|
|
107
|
+
}
|
|
108
|
+
await _server.db.delete(_schema.backupTable).where((0, _drizzleOrm.eq)(_schema.backupTable.id, id));
|
|
109
|
+
const {
|
|
110
|
+
user
|
|
111
|
+
} = await (0, _server.getCurrentSession)();
|
|
112
|
+
await _server.eventBus.publish("activity.create", {
|
|
113
|
+
action: "backup.delete",
|
|
114
|
+
description: `Deleted database backup: ${backup.filename}`,
|
|
115
|
+
userId: user?.id,
|
|
116
|
+
metadata: {
|
|
117
|
+
filename: backup.filename,
|
|
118
|
+
backupId: id
|
|
119
|
+
}
|
|
120
|
+
}, "backup");
|
|
121
|
+
return {
|
|
122
|
+
success: true
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("[Backup] Delete backup failed:", error);
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: error.message
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function getBackups(): Promise<any>;
|
|
2
|
+
export declare function createBackup(): Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
backup: any;
|
|
5
|
+
error?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
success: boolean;
|
|
8
|
+
error: string;
|
|
9
|
+
backup?: undefined;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function restoreBackup(id: string): Promise<{
|
|
12
|
+
success: boolean;
|
|
13
|
+
error?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
success: boolean;
|
|
16
|
+
error: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function deleteBackup(id: string): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
error?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
success: boolean;
|
|
23
|
+
error: string;
|
|
24
|
+
}>;
|
package/dist/actions.mjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { db, eventBus, getCurrentSession } from "@arch-cadre/core/server";
|
|
7
|
+
import { desc, eq } from "drizzle-orm";
|
|
8
|
+
import { backupTable } from "./schema.mjs";
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const BACKUP_DIR = path.join(process.cwd(), "backups");
|
|
11
|
+
export async function getBackups() {
|
|
12
|
+
return await db.select().from(backupTable).orderBy(desc(backupTable.createdAt));
|
|
13
|
+
}
|
|
14
|
+
export async function createBackup() {
|
|
15
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
16
|
+
const filename = `backup-${timestamp}.sql`;
|
|
17
|
+
const filePath = path.join(BACKUP_DIR, filename);
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(BACKUP_DIR, { recursive: true });
|
|
20
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
21
|
+
if (!dbUrl) throw new Error("DATABASE_URL is not defined");
|
|
22
|
+
await execAsync(`pg_dump "${dbUrl}" -c --if-exists -x -O > "${filePath}"`);
|
|
23
|
+
const stats = await fs.stat(filePath);
|
|
24
|
+
const [backup] = await db.insert(backupTable).values({
|
|
25
|
+
filename,
|
|
26
|
+
size: Math.round(stats.size),
|
|
27
|
+
status: "success"
|
|
28
|
+
}).returning();
|
|
29
|
+
const { user } = await getCurrentSession();
|
|
30
|
+
await eventBus.publish(
|
|
31
|
+
"activity.create",
|
|
32
|
+
{
|
|
33
|
+
action: "backup.create",
|
|
34
|
+
description: `Created database backup: ${filename}`,
|
|
35
|
+
userId: user?.id,
|
|
36
|
+
metadata: { filename, size: stats.size, backupId: backup.id }
|
|
37
|
+
},
|
|
38
|
+
"backup"
|
|
39
|
+
);
|
|
40
|
+
return { success: true, backup };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("[Backup] Create backup failed:", error);
|
|
43
|
+
return { success: false, error: error.message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function restoreBackup(id) {
|
|
47
|
+
try {
|
|
48
|
+
const [backup] = await db.select().from(backupTable).where(eq(backupTable.id, id));
|
|
49
|
+
if (!backup) throw new Error("Backup not found");
|
|
50
|
+
const filePath = path.join(BACKUP_DIR, backup.filename);
|
|
51
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
52
|
+
if (!dbUrl) throw new Error("DATABASE_URL is not defined");
|
|
53
|
+
await fs.access(filePath);
|
|
54
|
+
await execAsync(`psql "${dbUrl}" < "${filePath}"`);
|
|
55
|
+
const { user } = await getCurrentSession();
|
|
56
|
+
await eventBus.publish(
|
|
57
|
+
"activity.create",
|
|
58
|
+
{
|
|
59
|
+
action: "backup.restore",
|
|
60
|
+
description: `Restored database from backup: ${backup.filename}`,
|
|
61
|
+
userId: user?.id,
|
|
62
|
+
metadata: { filename: backup.filename, backupId: backup.id }
|
|
63
|
+
},
|
|
64
|
+
"backup"
|
|
65
|
+
);
|
|
66
|
+
return { success: true };
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("[Backup] Restore backup failed:", error);
|
|
69
|
+
return { success: false, error: error.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function deleteBackup(id) {
|
|
73
|
+
try {
|
|
74
|
+
const [backup] = await db.select().from(backupTable).where(eq(backupTable.id, id));
|
|
75
|
+
if (!backup) throw new Error("Backup not found");
|
|
76
|
+
const filePath = path.join(BACKUP_DIR, backup.filename);
|
|
77
|
+
try {
|
|
78
|
+
await fs.unlink(filePath);
|
|
79
|
+
} catch (_e) {
|
|
80
|
+
console.warn(
|
|
81
|
+
`[Backup] File ${filePath} could not be deleted or doesn't exist`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
await db.delete(backupTable).where(eq(backupTable.id, id));
|
|
85
|
+
const { user } = await getCurrentSession();
|
|
86
|
+
await eventBus.publish(
|
|
87
|
+
"activity.create",
|
|
88
|
+
{
|
|
89
|
+
action: "backup.delete",
|
|
90
|
+
description: `Deleted database backup: ${backup.filename}`,
|
|
91
|
+
userId: user?.id,
|
|
92
|
+
metadata: { filename: backup.filename, backupId: id }
|
|
93
|
+
},
|
|
94
|
+
"backup"
|
|
95
|
+
);
|
|
96
|
+
return { success: true };
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[Backup] Delete backup failed:", error);
|
|
99
|
+
return { success: false, error: error.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Database Backup Module
|
|
2
|
+
|
|
3
|
+
This module allows you to create full SQL snapshots of your database using `pg_dump`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Create Backup**: Generates a portable `.sql` file with all tables and data.
|
|
7
|
+
- **Restore**: Easily revert your database to a previous state.
|
|
8
|
+
- **Activity Logging**: Every backup action is recorded in the system logs.
|
|
9
|
+
|
|
10
|
+
## Storage
|
|
11
|
+
All backups are stored in the root `/backups` directory of the project.
|
|
12
|
+
|
|
13
|
+
> **Warning**: Restoring a backup will overwrite all current data in the database. Always ensure you have a recent snapshot before performing a restore.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
var _server = require("@arch-cadre/core/server");
|
|
8
|
+
var _manifest = _interopRequireDefault(require("../manifest.json"));
|
|
9
|
+
var _navigation = require("./navigation.cjs");
|
|
10
|
+
var _routes = require("./routes.cjs");
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
const backupModule = {
|
|
13
|
+
manifest: _manifest.default,
|
|
14
|
+
navigation: _navigation.navigation,
|
|
15
|
+
routes: {
|
|
16
|
+
private: _routes.privateRoutes
|
|
17
|
+
},
|
|
18
|
+
onEnable: async () => {
|
|
19
|
+
console.log("[Backup] Module enabled.");
|
|
20
|
+
},
|
|
21
|
+
onDisable: async () => {
|
|
22
|
+
try {
|
|
23
|
+
console.log("[Backup] Cleaning up database tables...");
|
|
24
|
+
await _server.db.execute(`DROP TABLE IF EXISTS backups;`);
|
|
25
|
+
console.log("[Backup] Database tables removed.");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("[Backup] Failed to remove tables:", error);
|
|
28
|
+
}
|
|
29
|
+
console.log("[Backup] Module disabled.");
|
|
30
|
+
},
|
|
31
|
+
init: async () => {
|
|
32
|
+
console.log("[Backup] Module initialized.");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
module.exports = backupModule;
|
package/dist/index.d.ts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { db } from "@arch-cadre/core/server";
|
|
2
|
+
import manifest from "../manifest.json";
|
|
3
|
+
import { navigation } from "./navigation.mjs";
|
|
4
|
+
import { privateRoutes } from "./routes.mjs";
|
|
5
|
+
const backupModule = {
|
|
6
|
+
manifest,
|
|
7
|
+
navigation,
|
|
8
|
+
routes: {
|
|
9
|
+
private: privateRoutes
|
|
10
|
+
},
|
|
11
|
+
onEnable: async () => {
|
|
12
|
+
console.log("[Backup] Module enabled.");
|
|
13
|
+
},
|
|
14
|
+
onDisable: async () => {
|
|
15
|
+
try {
|
|
16
|
+
console.log("[Backup] Cleaning up database tables...");
|
|
17
|
+
await db.execute(`DROP TABLE IF EXISTS backups;`);
|
|
18
|
+
console.log("[Backup] Database tables removed.");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("[Backup] Failed to remove tables:", error);
|
|
21
|
+
}
|
|
22
|
+
console.log("[Backup] Module disabled.");
|
|
23
|
+
},
|
|
24
|
+
init: async () => {
|
|
25
|
+
console.log("[Backup] Module initialized.");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export default backupModule;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.navigation = void 0;
|
|
7
|
+
const navigation = exports.navigation = {
|
|
8
|
+
admin: {
|
|
9
|
+
System: [{
|
|
10
|
+
title: "Backups",
|
|
11
|
+
url: "/backups",
|
|
12
|
+
icon: "solar:database-bold-duotone"
|
|
13
|
+
}]
|
|
14
|
+
}
|
|
15
|
+
};
|
package/dist/routes.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.publicRoutes = exports.privateRoutes = void 0;
|
|
7
|
+
var _backupList = _interopRequireDefault(require("./ui/pages/backup-list.cjs"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
const publicRoutes = exports.publicRoutes = [];
|
|
10
|
+
const privateRoutes = exports.privateRoutes = [{
|
|
11
|
+
path: "/backups",
|
|
12
|
+
component: _backupList.default
|
|
13
|
+
}];
|
package/dist/routes.d.ts
ADDED
package/dist/routes.mjs
ADDED
package/dist/schema.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.backupTable = void 0;
|
|
7
|
+
var _pgCore = require("drizzle-orm/pg-core");
|
|
8
|
+
const backupTable = exports.backupTable = (0, _pgCore.pgTable)("backups", {
|
|
9
|
+
id: (0, _pgCore.text)("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
|
|
10
|
+
filename: (0, _pgCore.text)("filename").notNull(),
|
|
11
|
+
size: (0, _pgCore.integer)("size").notNull(),
|
|
12
|
+
// in bytes
|
|
13
|
+
createdAt: (0, _pgCore.timestamp)("created_at", {
|
|
14
|
+
precision: 3
|
|
15
|
+
}).notNull().defaultNow(),
|
|
16
|
+
status: (0, _pgCore.text)("status").notNull()
|
|
17
|
+
// 'success', 'failed'
|
|
18
|
+
});
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export declare const backupTable: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
2
|
+
name: "backups";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"backups", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").SetHasRuntimeDefault<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>>>, {
|
|
6
|
+
name: string;
|
|
7
|
+
tableName: "backups";
|
|
8
|
+
dataType: "string";
|
|
9
|
+
data: string;
|
|
10
|
+
driverParam: string;
|
|
11
|
+
notNull: true;
|
|
12
|
+
hasDefault: true;
|
|
13
|
+
isPrimaryKey: false;
|
|
14
|
+
isAutoincrement: false;
|
|
15
|
+
hasRuntimeDefault: false;
|
|
16
|
+
enumValues: undefined;
|
|
17
|
+
identity: undefined;
|
|
18
|
+
generated: undefined;
|
|
19
|
+
}>;
|
|
20
|
+
filename: import("drizzle-orm/pg-core").PgBuildColumn<"backups", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
21
|
+
name: string;
|
|
22
|
+
tableName: "backups";
|
|
23
|
+
dataType: "string";
|
|
24
|
+
data: string;
|
|
25
|
+
driverParam: string;
|
|
26
|
+
notNull: true;
|
|
27
|
+
hasDefault: false;
|
|
28
|
+
isPrimaryKey: false;
|
|
29
|
+
isAutoincrement: false;
|
|
30
|
+
hasRuntimeDefault: false;
|
|
31
|
+
enumValues: undefined;
|
|
32
|
+
identity: undefined;
|
|
33
|
+
generated: undefined;
|
|
34
|
+
}>;
|
|
35
|
+
size: import("drizzle-orm/pg-core").PgBuildColumn<"backups", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgIntegerBuilder>, {
|
|
36
|
+
name: string;
|
|
37
|
+
tableName: "backups";
|
|
38
|
+
dataType: "number int32";
|
|
39
|
+
data: number;
|
|
40
|
+
driverParam: string | number;
|
|
41
|
+
notNull: true;
|
|
42
|
+
hasDefault: false;
|
|
43
|
+
isPrimaryKey: false;
|
|
44
|
+
isAutoincrement: false;
|
|
45
|
+
hasRuntimeDefault: false;
|
|
46
|
+
enumValues: undefined;
|
|
47
|
+
identity: undefined;
|
|
48
|
+
generated: undefined;
|
|
49
|
+
}>;
|
|
50
|
+
createdAt: import("drizzle-orm/pg-core").PgBuildColumn<"backups", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>, {
|
|
51
|
+
name: string;
|
|
52
|
+
tableName: "backups";
|
|
53
|
+
dataType: "object date";
|
|
54
|
+
data: Date;
|
|
55
|
+
driverParam: string;
|
|
56
|
+
notNull: true;
|
|
57
|
+
hasDefault: true;
|
|
58
|
+
isPrimaryKey: false;
|
|
59
|
+
isAutoincrement: false;
|
|
60
|
+
hasRuntimeDefault: false;
|
|
61
|
+
enumValues: undefined;
|
|
62
|
+
identity: undefined;
|
|
63
|
+
generated: undefined;
|
|
64
|
+
}>;
|
|
65
|
+
status: import("drizzle-orm/pg-core").PgBuildColumn<"backups", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
66
|
+
name: string;
|
|
67
|
+
tableName: "backups";
|
|
68
|
+
dataType: "string";
|
|
69
|
+
data: string;
|
|
70
|
+
driverParam: string;
|
|
71
|
+
notNull: true;
|
|
72
|
+
hasDefault: false;
|
|
73
|
+
isPrimaryKey: false;
|
|
74
|
+
isAutoincrement: false;
|
|
75
|
+
hasRuntimeDefault: false;
|
|
76
|
+
enumValues: undefined;
|
|
77
|
+
identity: undefined;
|
|
78
|
+
generated: undefined;
|
|
79
|
+
}>;
|
|
80
|
+
};
|
|
81
|
+
dialect: "pg";
|
|
82
|
+
}>;
|
package/dist/schema.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
+
export const backupTable = pgTable("backups", {
|
|
3
|
+
id: text("id").$defaultFn(() => crypto.randomUUID()).notNull().primaryKey(),
|
|
4
|
+
filename: text("filename").notNull(),
|
|
5
|
+
size: integer("size").notNull(),
|
|
6
|
+
// in bytes
|
|
7
|
+
createdAt: timestamp("created_at", { precision: 3 }).notNull().defaultNow(),
|
|
8
|
+
status: text("status").notNull()
|
|
9
|
+
// 'success', 'failed'
|
|
10
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use server";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.createBackupAction = createBackupAction;
|
|
8
|
+
exports.deleteBackupAction = deleteBackupAction;
|
|
9
|
+
exports.restoreBackupAction = restoreBackupAction;
|
|
10
|
+
var _cache = require("next/cache");
|
|
11
|
+
var _actions = require("../../actions.cjs");
|
|
12
|
+
async function createBackupAction() {
|
|
13
|
+
await (0, _actions.createBackup)();
|
|
14
|
+
(0, _cache.revalidatePath)("/module/backup");
|
|
15
|
+
}
|
|
16
|
+
async function restoreBackupAction(id) {
|
|
17
|
+
const result = await (0, _actions.restoreBackup)(id);
|
|
18
|
+
(0, _cache.revalidatePath)("/module/backup");
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
async function deleteBackupAction(id) {
|
|
22
|
+
await (0, _actions.deleteBackup)(id);
|
|
23
|
+
(0, _cache.revalidatePath)("/module/backup");
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function createBackupAction(): Promise<void>;
|
|
2
|
+
export declare function restoreBackupAction(id: string): Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
error?: undefined;
|
|
5
|
+
} | {
|
|
6
|
+
success: boolean;
|
|
7
|
+
error: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function deleteBackupAction(id: string): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { revalidatePath } from "next/cache";
|
|
3
|
+
import { createBackup, deleteBackup, restoreBackup } from "../../actions.mjs";
|
|
4
|
+
export async function createBackupAction() {
|
|
5
|
+
await createBackup();
|
|
6
|
+
revalidatePath("/module/backup");
|
|
7
|
+
}
|
|
8
|
+
export async function restoreBackupAction(id) {
|
|
9
|
+
const result = await restoreBackup(id);
|
|
10
|
+
revalidatePath("/module/backup");
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export async function deleteBackupAction(id) {
|
|
14
|
+
await deleteBackup(id);
|
|
15
|
+
revalidatePath("/module/backup");
|
|
16
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.CreateBackupButton = CreateBackupButton;
|
|
8
|
+
exports.DeleteBackupButton = DeleteBackupButton;
|
|
9
|
+
exports.RestoreBackupButton = RestoreBackupButton;
|
|
10
|
+
var _ui = require("@arch-cadre/ui");
|
|
11
|
+
var _react = require("react");
|
|
12
|
+
var _backupActions = require("./backup-actions.cjs");
|
|
13
|
+
function CreateBackupButton() {
|
|
14
|
+
const [pending, setPending] = (0, _react.useState)(false);
|
|
15
|
+
const handleCreate = async () => {
|
|
16
|
+
setPending(true);
|
|
17
|
+
try {
|
|
18
|
+
await (0, _backupActions.createBackupAction)();
|
|
19
|
+
_ui.toast.success("Backup created successfully");
|
|
20
|
+
} catch (_error) {
|
|
21
|
+
_ui.toast.error("Failed to create backup");
|
|
22
|
+
} finally {
|
|
23
|
+
setPending(false);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return /* @__PURE__ */React.createElement(_ui.Button, {
|
|
27
|
+
onClick: handleCreate,
|
|
28
|
+
disabled: pending
|
|
29
|
+
}, pending ? /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
30
|
+
icon: "svg-spinners:180-ring",
|
|
31
|
+
className: "mr-2 h-4 w-4"
|
|
32
|
+
}) : /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
33
|
+
icon: "solar:add-circle-broken",
|
|
34
|
+
className: "mr-2 h-4 w-4"
|
|
35
|
+
}), "Create Backup");
|
|
36
|
+
}
|
|
37
|
+
function RestoreBackupButton({
|
|
38
|
+
id
|
|
39
|
+
}) {
|
|
40
|
+
const [pending, setPending] = (0, _react.useState)(false);
|
|
41
|
+
const handleRestore = async () => {
|
|
42
|
+
if (!confirm("WARNING: This will overwrite your current database. Are you sure you want to restore this backup?")) return;
|
|
43
|
+
setPending(true);
|
|
44
|
+
try {
|
|
45
|
+
const result = await (0, _backupActions.restoreBackupAction)(id);
|
|
46
|
+
if (result.success) {
|
|
47
|
+
_ui.toast.success("Database restored successfully. You might need to refresh the page.");
|
|
48
|
+
} else {
|
|
49
|
+
_ui.toast.error(`Failed to restore backup: ${result.error}`);
|
|
50
|
+
}
|
|
51
|
+
} catch (_error) {
|
|
52
|
+
_ui.toast.error("An unexpected error occurred during restore");
|
|
53
|
+
} finally {
|
|
54
|
+
setPending(false);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return /* @__PURE__ */React.createElement(_ui.Button, {
|
|
58
|
+
variant: "ghost",
|
|
59
|
+
size: "icon",
|
|
60
|
+
onClick: handleRestore,
|
|
61
|
+
disabled: pending,
|
|
62
|
+
title: "Restore this backup"
|
|
63
|
+
}, pending ? /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
64
|
+
icon: "svg-spinners:180-ring",
|
|
65
|
+
className: "h-4 w-4"
|
|
66
|
+
}) : /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
67
|
+
icon: "solar:restart-broken",
|
|
68
|
+
className: "h-4 w-4"
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function DeleteBackupButton({
|
|
72
|
+
id
|
|
73
|
+
}) {
|
|
74
|
+
const [pending, setPending] = (0, _react.useState)(false);
|
|
75
|
+
const handleDelete = async () => {
|
|
76
|
+
if (!confirm("Are you sure you want to delete this backup?")) return;
|
|
77
|
+
setPending(true);
|
|
78
|
+
try {
|
|
79
|
+
await (0, _backupActions.deleteBackupAction)(id);
|
|
80
|
+
_ui.toast.success("Backup deleted successfully");
|
|
81
|
+
} catch (_error) {
|
|
82
|
+
_ui.toast.error("Failed to delete backup");
|
|
83
|
+
} finally {
|
|
84
|
+
setPending(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return /* @__PURE__ */React.createElement(_ui.Button, {
|
|
88
|
+
variant: "ghost",
|
|
89
|
+
size: "icon",
|
|
90
|
+
onClick: handleDelete,
|
|
91
|
+
disabled: pending,
|
|
92
|
+
className: "text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
93
|
+
}, pending ? /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
94
|
+
icon: "svg-spinners:180-ring",
|
|
95
|
+
className: "h-4 w-4"
|
|
96
|
+
}) : /* @__PURE__ */React.createElement(_ui.Icon, {
|
|
97
|
+
icon: "solar:trash-bin-trash-broken",
|
|
98
|
+
className: "h-4 w-4"
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function CreateBackupButton(): import("react").JSX.Element;
|
|
2
|
+
export declare function RestoreBackupButton({ id }: {
|
|
3
|
+
id: string;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function DeleteBackupButton({ id }: {
|
|
6
|
+
id: string;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button, Icon, toast } from "@arch-cadre/ui";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
createBackupAction,
|
|
6
|
+
deleteBackupAction,
|
|
7
|
+
restoreBackupAction
|
|
8
|
+
} from "./backup-actions.mjs";
|
|
9
|
+
export function CreateBackupButton() {
|
|
10
|
+
const [pending, setPending] = useState(false);
|
|
11
|
+
const handleCreate = async () => {
|
|
12
|
+
setPending(true);
|
|
13
|
+
try {
|
|
14
|
+
await createBackupAction();
|
|
15
|
+
toast.success("Backup created successfully");
|
|
16
|
+
} catch (_error) {
|
|
17
|
+
toast.error("Failed to create backup");
|
|
18
|
+
} finally {
|
|
19
|
+
setPending(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return /* @__PURE__ */ React.createElement(Button, { onClick: handleCreate, disabled: pending }, pending ? /* @__PURE__ */ React.createElement(Icon, { icon: "svg-spinners:180-ring", className: "mr-2 h-4 w-4" }) : /* @__PURE__ */ React.createElement(Icon, { icon: "solar:add-circle-broken", className: "mr-2 h-4 w-4" }), "Create Backup");
|
|
23
|
+
}
|
|
24
|
+
export function RestoreBackupButton({ id }) {
|
|
25
|
+
const [pending, setPending] = useState(false);
|
|
26
|
+
const handleRestore = async () => {
|
|
27
|
+
if (!confirm(
|
|
28
|
+
"WARNING: This will overwrite your current database. Are you sure you want to restore this backup?"
|
|
29
|
+
))
|
|
30
|
+
return;
|
|
31
|
+
setPending(true);
|
|
32
|
+
try {
|
|
33
|
+
const result = await restoreBackupAction(id);
|
|
34
|
+
if (result.success) {
|
|
35
|
+
toast.success(
|
|
36
|
+
"Database restored successfully. You might need to refresh the page."
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
toast.error(`Failed to restore backup: ${result.error}`);
|
|
40
|
+
}
|
|
41
|
+
} catch (_error) {
|
|
42
|
+
toast.error("An unexpected error occurred during restore");
|
|
43
|
+
} finally {
|
|
44
|
+
setPending(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return /* @__PURE__ */ React.createElement(
|
|
48
|
+
Button,
|
|
49
|
+
{
|
|
50
|
+
variant: "ghost",
|
|
51
|
+
size: "icon",
|
|
52
|
+
onClick: handleRestore,
|
|
53
|
+
disabled: pending,
|
|
54
|
+
title: "Restore this backup"
|
|
55
|
+
},
|
|
56
|
+
pending ? /* @__PURE__ */ React.createElement(Icon, { icon: "svg-spinners:180-ring", className: "h-4 w-4" }) : /* @__PURE__ */ React.createElement(Icon, { icon: "solar:restart-broken", className: "h-4 w-4" })
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
export function DeleteBackupButton({ id }) {
|
|
60
|
+
const [pending, setPending] = useState(false);
|
|
61
|
+
const handleDelete = async () => {
|
|
62
|
+
if (!confirm("Are you sure you want to delete this backup?")) return;
|
|
63
|
+
setPending(true);
|
|
64
|
+
try {
|
|
65
|
+
await deleteBackupAction(id);
|
|
66
|
+
toast.success("Backup deleted successfully");
|
|
67
|
+
} catch (_error) {
|
|
68
|
+
toast.error("Failed to delete backup");
|
|
69
|
+
} finally {
|
|
70
|
+
setPending(false);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ React.createElement(
|
|
74
|
+
Button,
|
|
75
|
+
{
|
|
76
|
+
variant: "ghost",
|
|
77
|
+
size: "icon",
|
|
78
|
+
onClick: handleDelete,
|
|
79
|
+
disabled: pending,
|
|
80
|
+
className: "text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
81
|
+
},
|
|
82
|
+
pending ? /* @__PURE__ */ React.createElement(Icon, { icon: "svg-spinners:180-ring", className: "h-4 w-4" }) : /* @__PURE__ */ React.createElement(Icon, { icon: "solar:trash-bin-trash-broken", className: "h-4 w-4" })
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
module.exports = BackupListPage;
|
|
7
|
+
var _card = require("@arch-cadre/ui/components/card");
|
|
8
|
+
var _table = require("@arch-cadre/ui/components/table");
|
|
9
|
+
var _dateFns = require("date-fns");
|
|
10
|
+
var _locale = require("date-fns/locale");
|
|
11
|
+
var _actions = require("../../actions.cjs");
|
|
12
|
+
var _backupClient = require("../components/backup-client.cjs");
|
|
13
|
+
function formatBytes(bytes, decimals = 2) {
|
|
14
|
+
if (bytes === 0) return "0 Bytes";
|
|
15
|
+
const k = 1024;
|
|
16
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
17
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
18
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
19
|
+
return parseFloat((bytes / k ** i).toFixed(dm)) + " " + sizes[i];
|
|
20
|
+
}
|
|
21
|
+
async function BackupListPage() {
|
|
22
|
+
const backups = await (0, _actions.getBackups)();
|
|
23
|
+
return /* @__PURE__ */React.createElement("div", {
|
|
24
|
+
className: "space-y-6"
|
|
25
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
26
|
+
className: "flex items-center justify-between"
|
|
27
|
+
}, /* @__PURE__ */React.createElement("div", {
|
|
28
|
+
className: "space-y-1"
|
|
29
|
+
}, /* @__PURE__ */React.createElement("h2", {
|
|
30
|
+
className: "text-3xl font-bold tracking-tight"
|
|
31
|
+
}, "Database Backups"), /* @__PURE__ */React.createElement("p", {
|
|
32
|
+
className: "text-muted-foreground text-sm"
|
|
33
|
+
}, "Manage your database snapshots. All backups are stored locally on the server.")), /* @__PURE__ */React.createElement(_backupClient.CreateBackupButton, null)), /* @__PURE__ */React.createElement(_card.Card, null, /* @__PURE__ */React.createElement(_card.CardHeader, null, /* @__PURE__ */React.createElement(_card.CardTitle, null, "Available Backups"), /* @__PURE__ */React.createElement(_card.CardDescription, null, "A list of all backups created in the system.")), /* @__PURE__ */React.createElement(_card.CardContent, null, /* @__PURE__ */React.createElement(_table.Table, null, /* @__PURE__ */React.createElement(_table.TableHeader, null, /* @__PURE__ */React.createElement(_table.TableRow, null, /* @__PURE__ */React.createElement(_table.TableHead, null, "Filename"), /* @__PURE__ */React.createElement(_table.TableHead, null, "Size"), /* @__PURE__ */React.createElement(_table.TableHead, null, "Created"), /* @__PURE__ */React.createElement(_table.TableHead, null, "Status"), /* @__PURE__ */React.createElement(_table.TableHead, {
|
|
34
|
+
className: "text-right"
|
|
35
|
+
}, "Actions"))), /* @__PURE__ */React.createElement(_table.TableBody, null, backups.length === 0 ? /* @__PURE__ */React.createElement(_table.TableRow, null, /* @__PURE__ */React.createElement(_table.TableCell, {
|
|
36
|
+
colSpan: 5,
|
|
37
|
+
className: "text-center py-10 text-muted-foreground"
|
|
38
|
+
}, "No backups found. Create your first backup to see it here.")) : backups.map(backup => /* @__PURE__ */React.createElement(_table.TableRow, {
|
|
39
|
+
key: backup.id
|
|
40
|
+
}, /* @__PURE__ */React.createElement(_table.TableCell, {
|
|
41
|
+
className: "font-medium"
|
|
42
|
+
}, backup.filename), /* @__PURE__ */React.createElement(_table.TableCell, null, formatBytes(backup.size)), /* @__PURE__ */React.createElement(_table.TableCell, {
|
|
43
|
+
title: backup.createdAt.toLocaleString()
|
|
44
|
+
}, (0, _dateFns.formatDistanceToNow)(backup.createdAt, {
|
|
45
|
+
addSuffix: true,
|
|
46
|
+
locale: _locale.pl
|
|
47
|
+
})), /* @__PURE__ */React.createElement(_table.TableCell, null, /* @__PURE__ */React.createElement("span", {
|
|
48
|
+
className: `px-2 py-1 rounded-full text-xs font-semibold ${backup.status === "success" ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`
|
|
49
|
+
}, backup.status)), /* @__PURE__ */React.createElement(_table.TableCell, {
|
|
50
|
+
className: "text-right space-x-1"
|
|
51
|
+
}, /* @__PURE__ */React.createElement(_backupClient.RestoreBackupButton, {
|
|
52
|
+
id: backup.id
|
|
53
|
+
}), /* @__PURE__ */React.createElement(_backupClient.DeleteBackupButton, {
|
|
54
|
+
id: backup.id
|
|
55
|
+
})))))))));
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function BackupListPage(): Promise<import("react").JSX.Element>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardDescription,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle
|
|
7
|
+
} from "@arch-cadre/ui/components/card";
|
|
8
|
+
import {
|
|
9
|
+
Table,
|
|
10
|
+
TableBody,
|
|
11
|
+
TableCell,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow
|
|
15
|
+
} from "@arch-cadre/ui/components/table";
|
|
16
|
+
import { formatDistanceToNow } from "date-fns";
|
|
17
|
+
import { pl } from "date-fns/locale";
|
|
18
|
+
import { getBackups } from "../../actions.mjs";
|
|
19
|
+
import {
|
|
20
|
+
CreateBackupButton,
|
|
21
|
+
DeleteBackupButton,
|
|
22
|
+
RestoreBackupButton
|
|
23
|
+
} from "../components/backup-client.mjs";
|
|
24
|
+
function formatBytes(bytes, decimals = 2) {
|
|
25
|
+
if (bytes === 0) return "0 Bytes";
|
|
26
|
+
const k = 1024;
|
|
27
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
28
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
29
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
30
|
+
return parseFloat((bytes / k ** i).toFixed(dm)) + " " + sizes[i];
|
|
31
|
+
}
|
|
32
|
+
export default async function BackupListPage() {
|
|
33
|
+
const backups = await getBackups();
|
|
34
|
+
return /* @__PURE__ */ React.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React.createElement("h2", { className: "text-3xl font-bold tracking-tight" }, "Database Backups"), /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground text-sm" }, "Manage your database snapshots. All backups are stored locally on the server.")), /* @__PURE__ */ React.createElement(CreateBackupButton, null)), /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, null, /* @__PURE__ */ React.createElement(CardTitle, null, "Available Backups"), /* @__PURE__ */ React.createElement(CardDescription, null, "A list of all backups created in the system.")), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Table, null, /* @__PURE__ */ React.createElement(TableHeader, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableHead, null, "Filename"), /* @__PURE__ */ React.createElement(TableHead, null, "Size"), /* @__PURE__ */ React.createElement(TableHead, null, "Created"), /* @__PURE__ */ React.createElement(TableHead, null, "Status"), /* @__PURE__ */ React.createElement(TableHead, { className: "text-right" }, "Actions"))), /* @__PURE__ */ React.createElement(TableBody, null, backups.length === 0 ? /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(
|
|
35
|
+
TableCell,
|
|
36
|
+
{
|
|
37
|
+
colSpan: 5,
|
|
38
|
+
className: "text-center py-10 text-muted-foreground"
|
|
39
|
+
},
|
|
40
|
+
"No backups found. Create your first backup to see it here."
|
|
41
|
+
)) : backups.map((backup) => /* @__PURE__ */ React.createElement(TableRow, { key: backup.id }, /* @__PURE__ */ React.createElement(TableCell, { className: "font-medium" }, backup.filename), /* @__PURE__ */ React.createElement(TableCell, null, formatBytes(backup.size)), /* @__PURE__ */ React.createElement(TableCell, { title: backup.createdAt.toLocaleString() }, formatDistanceToNow(backup.createdAt, {
|
|
42
|
+
addSuffix: true,
|
|
43
|
+
locale: pl
|
|
44
|
+
})), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
className: `px-2 py-1 rounded-full text-xs font-semibold ${backup.status === "success" ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`
|
|
48
|
+
},
|
|
49
|
+
backup.status
|
|
50
|
+
)), /* @__PURE__ */ React.createElement(TableCell, { className: "text-right space-x-1" }, /* @__PURE__ */ React.createElement(RestoreBackupButton, { id: backup.id }), /* @__PURE__ */ React.createElement(DeleteBackupButton, { id: backup.id })))))))));
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Settings saved successfully": "Settings saved successfully",
|
|
3
|
+
"Failed to save settings": "Failed to save settings",
|
|
4
|
+
"Loading Sample Settings...": "Loading Sample Settings...",
|
|
5
|
+
"Sample Settings": "Sample Settings",
|
|
6
|
+
"Configure your Sample Configuration.": "Configure your Sample Configuration.",
|
|
7
|
+
"Default Var": "Default Var",
|
|
8
|
+
"Save Configuration": "Save Configuration",
|
|
9
|
+
"General": "General",
|
|
10
|
+
"Sample Configuration": "Sample Configuration"
|
|
11
|
+
}
|
package/manifest.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arch-cadre/backup-module",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Backup module for Kryo framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"locales",
|
|
17
|
+
"manifest.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"release": "npm publish --access public --no-git-checks",
|
|
21
|
+
"clean": "rm -rf ./dist",
|
|
22
|
+
"switch:dev": "node scripts/switchToSrc.js",
|
|
23
|
+
"switch:prod": "node scripts/switchToDist.js",
|
|
24
|
+
"dev": "unbuild --stub",
|
|
25
|
+
"build": "unbuild"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@arch-cadre/modules": "^0.0.15",
|
|
29
|
+
"@arch-cadre/ui": "^0.0.15",
|
|
30
|
+
"@hookform/resolvers": "^3.10.0",
|
|
31
|
+
"date-fns": "^4.1.0",
|
|
32
|
+
"drizzle-orm": "1.0.0-beta.15-859cf75",
|
|
33
|
+
"lucide-react": "^0.475.0",
|
|
34
|
+
"pg": "^8.18.0",
|
|
35
|
+
"react-hook-form": "^7.54.2",
|
|
36
|
+
"sonner": "^2.0.7",
|
|
37
|
+
"zod": "^3.24.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@arch-cadre/core": "^0.0.15",
|
|
41
|
+
"@types/pg": "^8.16.0",
|
|
42
|
+
"@types/react": "^19",
|
|
43
|
+
"next": "16.1.1",
|
|
44
|
+
"react": "^19.0.0",
|
|
45
|
+
"typescript": "^5.3.3",
|
|
46
|
+
"unbuild": "^3.6.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@arch-cadre/core": "^0.0.15",
|
|
50
|
+
"@arch-cadre/intl": "^0.0.15",
|
|
51
|
+
"@arch-cadre/ui": "^0.0.15",
|
|
52
|
+
"next": ">=13.0.0",
|
|
53
|
+
"react": "^19.0.0"
|
|
54
|
+
},
|
|
55
|
+
"main": "./dist/index.mjs"
|
|
56
|
+
}
|