feat: Phase 3 – Modul-System (Manifest, ZIP-Installation, Lifecycle, Prozess-Manager)

This commit is contained in:
MPM Dev
2026-10-07 15:37:09 +02:00
parent e87dc1f836
commit 4bbca21fd6
31 changed files with 2055 additions and 19 deletions

View File

@@ -0,0 +1,100 @@
import { apiRequest, ApiError } from './api-client';
import { moduleSchema, type Module } from './schemas';
/** Typsichere API-Funktionen für die Modul-Verwaltung. */
export async function fetchModules(): Promise<Module[]> {
const response = await apiRequest<{ modules: unknown[] }>('/api/v1/modules');
return response.modules.map((module) => moduleSchema.parse(module));
}
export async function installModule(file: File): Promise<Module> {
const formData = new FormData();
formData.append('package', file);
const headers: Record<string, string> = {};
const csrfToken = readCsrfToken();
if (csrfToken) {
headers['X-CSRF-Token'] = csrfToken;
}
const response = await fetch('/api/v1/modules/install', {
method: 'POST',
headers,
credentials: 'same-origin',
body: formData,
});
if (!response.ok) {
let message = 'Installation fehlgeschlagen';
let details: Record<string, string | string[]> | undefined;
try {
const errorBody = (await response.json()) as {
message?: string | string[];
details?: Record<string, string | string[]>;
};
if (typeof errorBody.message === 'string') {
message = errorBody.message;
} else if (Array.isArray(errorBody.message)) {
message = errorBody.message.join(', ');
}
details = errorBody.details;
} catch {
// Kein JSON in der Antwort – Standardmeldung verwenden.
}
throw new ApiError(response.status, message, details);
}
const response_ = (await response.json()) as { module: unknown };
return moduleSchema.parse(response_.module);
}
export async function startModule(id: string): Promise<Module> {
const response = await apiRequest<{ module: unknown }>(`/api/v1/modules/${id}/start`, {
method: 'POST',
});
return moduleSchema.parse(response.module);
}
export async function stopModule(id: string): Promise<Module> {
const response = await apiRequest<{ module: unknown }>(`/api/v1/modules/${id}/stop`, {
method: 'POST',
});
return moduleSchema.parse(response.module);
}
export async function restartModule(id: string): Promise<Module> {
const response = await apiRequest<{ module: unknown }>(`/api/v1/modules/${id}/restart`, {
method: 'POST',
});
return moduleSchema.parse(response.module);
}
export async function setModuleEnabled(id: string, enabled: boolean): Promise<Module> {
const response = await apiRequest<{ module: unknown }>(`/api/v1/modules/${id}/enabled`, {
method: 'PATCH',
body: { enabled },
});
return moduleSchema.parse(response.module);
}
export async function removeModule(id: string): Promise<void> {
await apiRequest(`/api/v1/modules/${id}`, { method: 'DELETE' });
}
export async function checkModuleHealth(
id: string,
): Promise<{ healthy: boolean; latencyMs: number; detail: string }> {
return apiRequest(`/api/v1/modules/${id}/health`);
}
/** Liest das CSRF-Token aus dem nicht-HttpOnly-Cookie. */
function readCsrfToken(): string | null {
for (const part of document.cookie.split(';')) {
const [name, ...value] = part.trim().split('=');
if (name === 'mpm_csrf') {
return decodeURIComponent(value.join('='));
}
}
return null;
}