Admin task UI update & QoL (#194)

* feat: revise library source names & update droplet

* feat: add internal name hint to library sources

* feat: update library source table with new name + icons

* fix: admin invitation localisation issue

* feat: #164

* feat: overhaul task UIs, #163

* fix: remove debug task

* fix: lint
This commit is contained in:
DecDuck
2025-08-19 15:03:20 +10:00
committed by GitHub
parent 6baddc10e9
commit 6d89b7e510
18 changed files with 420 additions and 215 deletions

View File

@ -7,8 +7,9 @@ export default defineEventHandler(async (h3) => {
const unimportedGames = await libraryManager.fetchUnimportedGames();
const games = await libraryManager.fetchGamesWithStatus();
const libraries = await libraryManager.fetchLibraries();
// Fetch other library data here
return { unimportedGames, games };
return { unimportedGames, games, hasLibraries: libraries.length > 0 };
});

View File

@ -1,5 +1,6 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import type { TaskMessage } from "~/server/internal/tasks";
import taskHandler from "~/server/internal/tasks";
export default defineEventHandler(async (h3) => {
@ -13,7 +14,7 @@ export default defineEventHandler(async (h3) => {
});
const runningTasks = (await taskHandler.runningTasks()).map((e) => e.id);
const historicalTasks = await prisma.task.findMany({
const historicalTasks = (await prisma.task.findMany({
where: {
OR: [
{
@ -28,7 +29,7 @@ export default defineEventHandler(async (h3) => {
ended: "desc",
},
take: 10,
});
})) as Array<TaskMessage>;
const dailyTasks = await taskHandler.dailyTasks();
const weeklyTasks = await taskHandler.weeklyTasks();

View File

@ -0,0 +1,31 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import taskHandler from "~/server/internal/tasks";
import type { TaskGroup } from "~/server/internal/tasks/group";
import { taskGroups } from "~/server/internal/tasks/group";
const StartTask = type({
taskGroup: type("string"),
}).configure(throwingArktype);
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["task:start"]);
if (!allowed) throw createError({ statusCode: 403 });
const body = await readDropValidatedBody(h3, StartTask);
const taskGroup = body.taskGroup as TaskGroup;
if (!taskGroups[taskGroup])
throw createError({
statusCode: 400,
statusMessage: "Invalid task group.",
});
const task = await taskHandler.runTaskGroupByName(taskGroup);
if (!task)
throw createError({
statusCode: 500,
statusMessage: "Could not start task.",
});
return { id: task };
});

View File

@ -116,7 +116,11 @@ class LibraryManager {
async fetchGamesWithStatus() {
const games = await prisma.game.findMany({
include: {
versions: true,
versions: {
select: {
versionName: true,
},
},
library: true,
},
orderBy: {

View File

@ -14,6 +14,9 @@ export const taskGroups = {
"import:game": {
concurrency: true,
},
debug: {
concurrency: true,
},
} as const;
export type TaskGroup = keyof typeof taskGroups;

View File

@ -53,6 +53,7 @@ class TaskHandler {
"cleanup:invitations",
"cleanup:sessions",
"check:update",
"debug",
];
private weeklyScheduledTasks: TaskGroup[] = ["cleanup:objects"];
@ -62,6 +63,7 @@ class TaskHandler {
this.saveScheduledTask(cleanupSessions);
this.saveScheduledTask(checkUpdate);
this.saveScheduledTask(cleanupObjects);
//this.saveScheduledTask(debug);
}
/**
@ -162,6 +164,13 @@ class TaskHandler {
// You can configure timestamp, level, etc. here
timestamp: pino.stdTimeFunctions.isoTime,
base: null, // Remove pid/hostname if not needed
formatters: {
level(label) {
return {
level: label,
};
},
},
},
logStream,
);
@ -339,13 +348,15 @@ class TaskHandler {
return this.weeklyScheduledTasks;
}
runTaskGroupByName(name: TaskGroup) {
const task = this.taskCreators.get(name);
if (!task) {
async runTaskGroupByName(name: TaskGroup) {
const taskConstructor = this.taskCreators.get(name);
if (!taskConstructor) {
logger.warn(`No task found for group ${name}`);
return;
}
this.create(task());
const task = taskConstructor();
await this.create(task);
return task.id;
}
/**
@ -444,7 +455,7 @@ export type TaskMessage = {
name: string;
success: boolean;
progress: number;
error: undefined | { title: string; description: string };
error: null | undefined | { title: string; description: string };
log: string[];
reset?: boolean;
};
@ -470,6 +481,7 @@ interface DropTask {
export const TaskLog = type({
timestamp: "string",
message: "string",
level: "string",
});
// /**
@ -499,8 +511,6 @@ export const TaskLog = type({
// }
export function defineDropTask(buildTask: BuildTask): DropTask {
// TODO: only let one task with the same taskGroup run at the same time if specified
return {
taskGroup: buildTask.taskGroup,
build: () => ({

View File

@ -0,0 +1,18 @@
import { defineDropTask } from "..";
export default defineDropTask({
buildId: () => `debug:${new Date().toISOString()}`,
name: "Debug Task",
acls: ["system:maintenance:read"],
taskGroup: "debug",
async run({ progress, logger }) {
const amount = 1000;
for (let i = 0; i < amount; i++) {
progress((i / amount) * 100);
logger.info(`dajksdkajd ${i}`);
logger.warn("warning");
logger.error("error\nmultiline and stuff\nwoah more lines");
await new Promise((r) => setTimeout(r, 1500));
}
},
});