Add tabbed settings and system shell
This commit is contained in:
+72
-1
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
||||
import argon2 from "argon2";
|
||||
import cookie from "@fastify/cookie";
|
||||
import rateLimit from "@fastify/rate-limit";
|
||||
import websocket from "@fastify/websocket";
|
||||
import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import type { AppConfig } from "./config.js";
|
||||
@@ -28,6 +29,8 @@ import {
|
||||
requestNetworkAction
|
||||
} from "./network.js";
|
||||
import { collectAdbStatus } from "./adb.js";
|
||||
import { openShell } from "./shell.js";
|
||||
import { systemPowerActionSchema, triggerSystemPower } from "./system.js";
|
||||
import "./types.js";
|
||||
|
||||
const usernameSchema = z
|
||||
@@ -114,6 +117,7 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
|
||||
app.decorateRequest("authUser", null);
|
||||
await app.register(cookie);
|
||||
await app.register(rateLimit, { global: false });
|
||||
await app.register(websocket);
|
||||
|
||||
app.addHook("onClose", async () => {
|
||||
jobRunner.close();
|
||||
@@ -405,6 +409,37 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
|
||||
return readUpdateStatus(config);
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/api/system/update/check",
|
||||
{ config: { rateLimit: { max: 6, timeWindow: "10 minutes" } } },
|
||||
async (request, reply) => {
|
||||
if (!requireUser(request, reply)) return;
|
||||
const current = await readUpdateStatus(config);
|
||||
if (!current.supported) {
|
||||
return reply.code(503).send({
|
||||
error: { code: "UPDATES_UNAVAILABLE", message: "Install the system service before checking for updates" }
|
||||
});
|
||||
}
|
||||
if (["checking", "building"].includes(current.state)) {
|
||||
return reply.code(409).send({ error: { code: "UPDATE_IN_PROGRESS", message: "An update task is already running" } });
|
||||
}
|
||||
try {
|
||||
await triggerSystemUpdate("check");
|
||||
audit(database, {
|
||||
actorUserId: request.authUser!.id,
|
||||
action: "system.update-check",
|
||||
result: "success",
|
||||
ipAddress: clientAddress(request)
|
||||
});
|
||||
return reply.code(202).send({ queued: true });
|
||||
} catch {
|
||||
return reply.code(503).send({
|
||||
error: { code: "UPDATE_CHECK_FAILED", message: "The update check could not be started" }
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/system/update",
|
||||
{ config: { rateLimit: { max: 2, timeWindow: "10 minutes" } } },
|
||||
@@ -421,7 +456,7 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
|
||||
}
|
||||
|
||||
try {
|
||||
await triggerSystemUpdate();
|
||||
await triggerSystemUpdate("install");
|
||||
audit(database, {
|
||||
actorUserId: request.authUser!.id,
|
||||
action: "system.update",
|
||||
@@ -445,6 +480,42 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/system/power",
|
||||
{ config: { rateLimit: { max: 3, timeWindow: "10 minutes" } } },
|
||||
async (request, reply) => {
|
||||
if (!requireUser(request, reply)) return;
|
||||
const parsed = z.object({ action: systemPowerActionSchema }).safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return reply.code(400).send({ error: { code: "INVALID_POWER_ACTION", message: "That power action is not available" } });
|
||||
}
|
||||
try {
|
||||
await triggerSystemPower(parsed.data.action);
|
||||
audit(database, {
|
||||
actorUserId: request.authUser!.id,
|
||||
action: `system.${parsed.data.action}`,
|
||||
result: "success",
|
||||
ipAddress: clientAddress(request)
|
||||
});
|
||||
return reply.code(202).send({ queued: true, action: parsed.data.action });
|
||||
} catch {
|
||||
audit(database, {
|
||||
actorUserId: request.authUser!.id,
|
||||
action: `system.${parsed.data.action}`,
|
||||
result: "failure",
|
||||
ipAddress: clientAddress(request)
|
||||
});
|
||||
return reply.code(503).send({
|
||||
error: { code: "POWER_ACTION_FAILED", message: "The Pi could not schedule that power action" }
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get("/api/system/shell", { websocket: true }, (socket, request) => {
|
||||
openShell(socket, request, config);
|
||||
});
|
||||
|
||||
app.get("/api/events", async (request, reply) => {
|
||||
if (!requireUser(request, reply)) return;
|
||||
reply.hijack();
|
||||
|
||||
Reference in New Issue
Block a user