Add update script, enhance admin features, and improve styles
- Created a PowerShell script (update.ps1) for automating the process of pulling the latest repository changes, stopping, building, and starting the Docker Compose stack. - Updated App.jsx to include new admin sections for alarms and integrations, added state management for runner jobs, and enhanced permission checks. - Extended the API module (api.js) with new endpoints for managing integrations and runner jobs. - Improved CSS styles for a more compact and responsive layout in the admin interface, focusing on source and datapoint cards.
This commit is contained in:
@@ -132,6 +132,9 @@ const PERMISSION_CATALOG = [
|
||||
{ id: "manage_users", label: "Benutzer verwalten" },
|
||||
{ id: "manage_roles", label: "Rollen verwalten" },
|
||||
{ id: "view_debug", label: "Treiber-Debug anzeigen" },
|
||||
{ id: "manage_alarms", label: "Alarme verwalten" },
|
||||
{ id: "manage_integrations", label: "API-Verbindungen verwalten" },
|
||||
{ id: "manage_runner", label: "CSV-Runner verwalten" },
|
||||
];
|
||||
|
||||
async function getUserPermissions(roleName) {
|
||||
@@ -198,6 +201,51 @@ async function ensureRolesSchema() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
function sanitizeApiSettings(body = {}) {
|
||||
return {
|
||||
mailUrl: String(body.mailUrl || "https://mailapi.se-inno.de").trim() || "https://mailapi.se-inno.de",
|
||||
mailApiKey: String(body.mailApiKey || "").trim(),
|
||||
whatsappUrl: String(body.whatsappUrl || "https://whatsappapi.se-inno.de").trim() || "https://whatsappapi.se-inno.de",
|
||||
whatsappApiKey: String(body.whatsappApiKey || "").trim(),
|
||||
voipUrl: String(body.voipUrl || "https://voipapi.se-inno.de").trim() || "https://voipapi.se-inno.de",
|
||||
voipApiKey: String(body.voipApiKey || "").trim(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeRunnerJob(body = {}, existing = {}) {
|
||||
const time = String(body.time ?? existing.time ?? "06:00").trim();
|
||||
if (!/^\d{2}:\d{2}$/.test(time)) {
|
||||
throw new Error("Uhrzeit bitte im Format HH:MM angeben.");
|
||||
}
|
||||
return {
|
||||
...existing,
|
||||
id: existing.id || body.id || `job-${Date.now()}`,
|
||||
name: String(body.name ?? existing.name ?? "CSV Export").trim() || "CSV Export",
|
||||
selectionId: String(body.selectionId ?? existing.selectionId ?? "").trim(),
|
||||
enabled: typeof body.enabled === "boolean" ? body.enabled : Boolean(existing.enabled ?? true),
|
||||
time,
|
||||
rangeHours: Number(body.rangeHours ?? existing.rangeHours ?? 24) || 24,
|
||||
delimiter: String(body.delimiter ?? existing.delimiter ?? ";").replace("\\t", "\t") || ";",
|
||||
mailEnabled: typeof body.mailEnabled === "boolean" ? body.mailEnabled : Boolean(existing.mailEnabled),
|
||||
mailTo: String(body.mailTo ?? existing.mailTo ?? "").trim(),
|
||||
mailSubject: String(body.mailSubject ?? existing.mailSubject ?? "SE-LTD CSV Export").trim(),
|
||||
mailMessage: String(body.mailMessage ?? existing.mailMessage ?? "Automatischer CSV Export aus SE Local Trenddata.").trim(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: existing.createdAt || new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async function listRunnerJobs() {
|
||||
const ids = await redis.smembers("runner:job:ids");
|
||||
const jobs = await Promise.all(ids.map(async (id) => {
|
||||
const raw = await redis.get(`runner:job:${id}`);
|
||||
const result = await redis.get(`runner:last-result:${id}`);
|
||||
return raw ? { ...JSON.parse(raw), lastResult: result ? JSON.parse(result) : null } : null;
|
||||
}));
|
||||
return jobs.filter(Boolean).sort((left, right) => String(left.time).localeCompare(String(right.time)));
|
||||
}
|
||||
async function resolveSelectionPoints(body, user) {
|
||||
if (body.selectionId) {
|
||||
const selection = await getSelection(redis, body.selectionId);
|
||||
@@ -752,6 +800,51 @@ app.post("/api/dashboard/data", authenticateTokenOptional, wrap(async (req, res)
|
||||
res.json({ widgets: results });
|
||||
}));
|
||||
|
||||
|
||||
app.get("/api/integrations", authenticateToken, requirePermission("manage_integrations"), wrap(async (_req, res) => {
|
||||
const current = JSON.parse(await redis.get("integration:api") || "{}");
|
||||
res.json({
|
||||
...sanitizeApiSettings(current),
|
||||
mailConfigured: Boolean(current.mailApiKey),
|
||||
whatsappConfigured: Boolean(current.whatsappApiKey),
|
||||
voipConfigured: Boolean(current.voipApiKey),
|
||||
});
|
||||
}));
|
||||
|
||||
app.put("/api/integrations", authenticateToken, requirePermission("manage_integrations"), wrap(async (req, res) => {
|
||||
const payload = sanitizeApiSettings(req.body || {});
|
||||
await redis.set("integration:api", JSON.stringify(payload));
|
||||
res.json({ ...payload, mailConfigured: Boolean(payload.mailApiKey), whatsappConfigured: Boolean(payload.whatsappApiKey), voipConfigured: Boolean(payload.voipApiKey) });
|
||||
}));
|
||||
|
||||
app.get("/api/runner/jobs", authenticateToken, requirePermission("manage_runner"), wrap(async (_req, res) => {
|
||||
res.json({ jobs: await listRunnerJobs() });
|
||||
}));
|
||||
|
||||
app.post("/api/runner/jobs", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
|
||||
const job = sanitizeRunnerJob(req.body || {});
|
||||
if (!job.selectionId) return res.status(400).json({ error: "Selection ist erforderlich." });
|
||||
await redis.set(`runner:job:${job.id}`, JSON.stringify(job));
|
||||
await redis.sadd("runner:job:ids", job.id);
|
||||
res.status(201).json(job);
|
||||
}));
|
||||
|
||||
app.patch("/api/runner/jobs/:jobId", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
|
||||
const id = req.params.jobId;
|
||||
const existing = JSON.parse(await redis.get(`runner:job:${id}`) || "null");
|
||||
if (!existing) return res.status(404).json({ error: "Runner-Job nicht gefunden." });
|
||||
const job = sanitizeRunnerJob(req.body || {}, existing);
|
||||
await redis.set(`runner:job:${id}`, JSON.stringify(job));
|
||||
await redis.sadd("runner:job:ids", id);
|
||||
res.json(job);
|
||||
}));
|
||||
|
||||
app.delete("/api/runner/jobs/:jobId", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
|
||||
const id = req.params.jobId;
|
||||
await redis.del(`runner:job:${id}`, `runner:last:${id}`, `runner:last-result:${id}`);
|
||||
await redis.srem("runner:job:ids", id);
|
||||
res.json({ ok: true });
|
||||
}));
|
||||
app.get("/api/preferences", authenticateToken, wrap(async (req, res) => {
|
||||
const preferences = await getPreferences(redis, req.user.sub);
|
||||
res.json(preferences);
|
||||
@@ -781,3 +874,8 @@ app.listen(config.port, () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ export const DEFAULT_ROLE_DEFINITIONS = [
|
||||
{
|
||||
name: "technician",
|
||||
label: "Techniker",
|
||||
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "view_debug"],
|
||||
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "view_debug", "manage_alarms"],
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
name: "admin",
|
||||
label: "Admin",
|
||||
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "manage_users", "manage_roles", "view_debug"],
|
||||
permissions: ["view_dashboard", "view_trends", "edit_aliases", "manage_sources", "manage_users", "manage_roles", "view_debug", "manage_integrations", "manage_runner", "manage_alarms"],
|
||||
isSystem: true,
|
||||
},
|
||||
];
|
||||
@@ -104,7 +104,7 @@ async function ensureDefaultRoles(redis) {
|
||||
...(existing || {}),
|
||||
name: definition.name,
|
||||
label: existing?.label || definition.label,
|
||||
permissions: Array.isArray(existing?.permissions) && existing.permissions.length ? existing.permissions : definition.permissions,
|
||||
permissions: Array.from(new Set([...(Array.isArray(existing?.permissions) ? existing.permissions : []), ...definition.permissions])),
|
||||
isSystem: true,
|
||||
});
|
||||
await redis.set(key, JSON.stringify(payload));
|
||||
@@ -286,3 +286,6 @@ export async function savePreferences(redis, userId, preferences) {
|
||||
await redis.set(`preferences:${userId}`, JSON.stringify(payload));
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user