feat(phase3): PWA, auto-discovery, bookmarklet, multi-tab sync
- PWA: manifest, service worker (cache-first static, network-first API), offline page, install prompt banner - Auto-discovery: Docker socket + Traefik API scanning, approval UI - Quick-add bookmarklet: popup-based add page, favicon auto-detect - Multi-tab sync: BroadcastChannel for theme + data changes - i18n translations for all new strings (EN/RU)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { requireAdmin } from '$lib/server/middleware/authorize.js';
|
||||
import { create } from '$lib/server/services/appService.js';
|
||||
import { success, error } from '$lib/server/utils/response.js';
|
||||
|
||||
interface ApproveServiceInput {
|
||||
readonly name: string;
|
||||
readonly url: string;
|
||||
readonly source: 'docker' | 'traefik';
|
||||
readonly icon?: string;
|
||||
readonly description?: string;
|
||||
}
|
||||
|
||||
interface ApproveBody {
|
||||
readonly services: readonly ApproveServiceInput[];
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/admin/discover/approve — Approve discovered services and create app entries. Admin only.
|
||||
*
|
||||
* Body: { services: DiscoveredService[] }
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const user = requireAdmin(event);
|
||||
|
||||
let body: ApproveBody;
|
||||
try {
|
||||
body = await event.request.json();
|
||||
} catch {
|
||||
return json(error('Invalid JSON body'), { status: 400 });
|
||||
}
|
||||
|
||||
if (!body.services || !Array.isArray(body.services) || body.services.length === 0) {
|
||||
return json(error('At least one service must be provided for approval'), { status: 400 });
|
||||
}
|
||||
|
||||
const created: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const service of body.services) {
|
||||
if (!service.name || !service.url) {
|
||||
errors.push(`Skipped invalid service entry (missing name or url)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const app = await create({
|
||||
name: service.name,
|
||||
url: service.url,
|
||||
icon: service.icon,
|
||||
description: service.description ?? `Discovered via ${service.source}`,
|
||||
category: 'Discovered',
|
||||
healthcheckEnabled: true,
|
||||
createdById: user.id
|
||||
});
|
||||
created.push(app.id);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||
errors.push(`Failed to create "${service.name}": ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return json(
|
||||
success({
|
||||
created: created.length,
|
||||
errors
|
||||
})
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user