feat(frontend): backup jobs page

This commit is contained in:
Nicolas Meienberger
2025-11-01 17:09:43 +01:00
parent d81f3653ec
commit 18115b374c
19 changed files with 459 additions and 38 deletions

View File

@@ -5,9 +5,12 @@ import type {
repositoryConfigSchema,
RepositoryStatus,
} from "@ironmount/schemas/restic";
import { sql } from "drizzle-orm";
import { relations, sql } from "drizzle-orm";
import { int, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
/**
* Volumes Table
*/
export const volumesTable = sqliteTable("volumes_table", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull().unique(),
@@ -20,9 +23,11 @@ export const volumesTable = sqliteTable("volumes_table", {
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true),
});
export type Volume = typeof volumesTable.$inferSelect;
/**
* Users Table
*/
export const usersTable = sqliteTable("users_table", {
id: int().primaryKey({ autoIncrement: true }),
username: text().notNull().unique(),
@@ -30,9 +35,7 @@ export const usersTable = sqliteTable("users_table", {
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
});
export type User = typeof usersTable.$inferSelect;
export const sessionsTable = sqliteTable("sessions_table", {
id: text().primaryKey(),
userId: int("user_id")
@@ -41,9 +44,11 @@ export const sessionsTable = sqliteTable("sessions_table", {
expiresAt: int("expires_at", { mode: "number" }).notNull(),
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
});
export type Session = typeof sessionsTable.$inferSelect;
/**
* Repositories Table
*/
export const repositoriesTable = sqliteTable("repositories_table", {
id: text().primaryKey(),
name: text().notNull().unique(),
@@ -56,9 +61,11 @@ export const repositoriesTable = sqliteTable("repositories_table", {
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
});
export type Repository = typeof repositoriesTable.$inferSelect;
/**
* Backup Schedules Table
*/
export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
id: int().primaryKey({ autoIncrement: true }),
volumeId: int("volume_id")
@@ -88,5 +95,14 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
updatedAt: int("updated_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
});
export const backupScheduleRelations = relations(backupSchedulesTable, ({ one }) => ({
volume: one(volumesTable, {
fields: [backupSchedulesTable.volumeId],
references: [volumesTable.id],
}),
repository: one(repositoriesTable, {
fields: [backupSchedulesTable.repositoryId],
references: [repositoriesTable.id],
}),
}));
export type BackupSchedule = typeof backupSchedulesTable.$inferSelect;

View File

@@ -1,5 +1,7 @@
import { type } from "arktype";
import { describeRoute, resolver } from "hono-openapi";
import { volumeSchema } from "../volumes/volume.dto";
import { repositorySchema } from "../repositories/repositories.dto";
const retentionPolicySchema = type({
keepLast: "number?",
@@ -58,7 +60,12 @@ export const listBackupSchedulesDto = describeRoute({
/**
* Get a single backup schedule
*/
export const getBackupScheduleResponse = backupScheduleSchema;
export const getBackupScheduleResponse = backupScheduleSchema.and(
type({
volume: volumeSchema,
repository: repositorySchema,
}),
);
export type GetBackupScheduleDto = typeof getBackupScheduleResponse.infer;

View File

@@ -34,6 +34,10 @@ const listSchedules = async () => {
const getSchedule = async (scheduleId: number) => {
const schedule = await db.query.backupSchedulesTable.findFirst({
where: eq(volumesTable.id, scheduleId),
with: {
volume: true,
repository: true,
},
});
if (!schedule) {

View File

@@ -7,7 +7,7 @@ import {
import { type } from "arktype";
import { describeRoute, resolver } from "hono-openapi";
const repositorySchema = type({
export const repositorySchema = type({
id: "string",
name: "string",
type: type.valueOf(REPOSITORY_BACKENDS),

View File

@@ -2,7 +2,7 @@ import { BACKEND_STATUS, BACKEND_TYPES, volumeConfigSchema } from "@ironmount/sc
import { type } from "arktype";
import { describeRoute, resolver } from "hono-openapi";
const volumeSchema = type({
export const volumeSchema = type({
id: "number",
name: "string",
path: "string",

View File

@@ -69,7 +69,7 @@ const ensurePassfile = async () => {
const buildRepoUrl = (config: RepositoryConfig): string => {
switch (config.backend) {
case "local":
return config.path;
return `/repositories/${config.name}`;
case "s3":
return `s3:${config.endpoint}/${config.bucket}`;
default: {