Portal-Dienste und geteiltes Design-System für die anderen Apps
- Dienste (Gitea, Kanban, Cloud …) werden im System-Tab gepflegt und erscheinen als Kacheln auf der Startseite; GET /api/services ist öffentlich, Pflege braucht den settings-Scope - /brand.css liefert die Design-Tokens (Farben live aus dem Brand-Tab, ändern sich damit überall mit) plus Basis-Klassen d4rk-card, d4rk-btn, d4rk-title, d4rk-tag - /brand-nav.js baut die D4RKST3R-Leiste in jede fremde App ein, mit Links zum Hub und zu allen gepflegten Diensten; die Links stecken fertig im Skript, dadurch kein zweiter Request und kein CORS nötig - Beide Dateien mit offenem CORS-Header und 5 Minuten Cache - Doku für Anbindung + Schriften in docs/sso.md ergänzt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
getLevelRow, isPlaytester, saveWish, toggleWishVote, myWishVotes,
|
||||
logAudit, listAudit, playerHistory, deleteUserData,
|
||||
saveSsoApp, listSsoApps, deleteSsoApp, getSsoApp,
|
||||
createService, updateService, listServices, deleteService,
|
||||
} from '../db.js';
|
||||
import crypto from 'node:crypto';
|
||||
import { lastResults, GAME_ICONS } from '../bot/server-monitor.js';
|
||||
@@ -162,6 +163,51 @@ export function registerApiRoutes(app, client) {
|
||||
}),
|
||||
}));
|
||||
|
||||
// Dienste-Kacheln fürs Portal — öffentlich
|
||||
app.get('/api/services', async () => ({ services: listServices() }));
|
||||
|
||||
/** Dienst-Body prüfen; antwortet bei Fehlern selbst */
|
||||
function parseServiceBody(request, reply) {
|
||||
const body = request.body ?? {};
|
||||
const name = String(body.name ?? '').trim();
|
||||
const url = String(body.url ?? '').trim();
|
||||
if (!name || !/^https?:\/\/.+/.test(url)) {
|
||||
reply.code(400).send({ error: 'name und url (http/https) nötig' });
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: name.slice(0, 60),
|
||||
url: url.slice(0, 300),
|
||||
icon: (String(body.icon ?? '').trim() || '🔗').slice(0, 8),
|
||||
description: String(body.description ?? '').trim().slice(0, 160),
|
||||
sort: Number(body.sort) || 0,
|
||||
};
|
||||
}
|
||||
|
||||
app.post('/api/services', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
const service = parseServiceBody(request, reply);
|
||||
if (!service) return;
|
||||
const id = createService(service);
|
||||
logAudit(getSessionUser(request), 'dienst hinzugefügt', service.name);
|
||||
return { ok: true, services: listServices(), id };
|
||||
});
|
||||
|
||||
app.put('/api/services/:id', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
const service = parseServiceBody(request, reply);
|
||||
if (!service) return;
|
||||
updateService({ ...service, id: Number(request.params.id) });
|
||||
logAudit(getSessionUser(request), 'dienst geändert', service.name);
|
||||
return { ok: true, services: listServices() };
|
||||
});
|
||||
|
||||
app.delete('/api/services/:id', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
logAudit(getSessionUser(request), 'dienst gelöscht', String(request.params.id));
|
||||
return { deleted: deleteService(Number(request.params.id)), services: listServices() };
|
||||
});
|
||||
|
||||
// Level-Bestenliste + Server-Aktivität — öffentlich
|
||||
app.get('/api/levels', async () => ({ levels: topLevels(50) }));
|
||||
app.get('/api/serverstats', async () => ({
|
||||
|
||||
Reference in New Issue
Block a user