MPM marketplace and UI updates
This commit is contained in:
@@ -11,6 +11,8 @@ export class ApiError extends Error {
|
||||
public readonly status: number,
|
||||
message: string,
|
||||
public readonly details?: Record<string, string | string[]>,
|
||||
public readonly code?: string,
|
||||
public readonly diagnostic?: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
@@ -78,10 +80,14 @@ export async function apiRequest<TResponse>(
|
||||
if (!response.ok) {
|
||||
let message = 'Ein unerwarteter Fehler ist aufgetreten.';
|
||||
let details: Record<string, string[]> | undefined;
|
||||
let code: string | undefined;
|
||||
let diagnostic: string | undefined;
|
||||
try {
|
||||
const errorBody = (await response.json()) as {
|
||||
message?: string | string[];
|
||||
details?: Record<string, string[]>;
|
||||
code?: string;
|
||||
diagnostic?: string;
|
||||
};
|
||||
if (typeof errorBody.message === 'string') {
|
||||
message = errorBody.message;
|
||||
@@ -89,10 +95,12 @@ export async function apiRequest<TResponse>(
|
||||
message = errorBody.message.join(', ');
|
||||
}
|
||||
details = errorBody.details;
|
||||
code = errorBody.code;
|
||||
diagnostic = errorBody.diagnostic;
|
||||
} catch {
|
||||
// Antwort enthält kein JSON – Standardmeldung verwenden.
|
||||
}
|
||||
throw new ApiError(response.status, message, details);
|
||||
throw new ApiError(response.status, message, details, code, diagnostic);
|
||||
}
|
||||
|
||||
return (await response.json()) as TResponse;
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
accessibleModuleSchema,
|
||||
modulePermissionSchema,
|
||||
moduleSchema,
|
||||
moduleConfigurationSchema,
|
||||
type AccessibleModule,
|
||||
type Module,
|
||||
type ModulePermission,
|
||||
type ModuleConfiguration,
|
||||
} from './schemas';
|
||||
|
||||
/** Typsichere API-Funktionen für die Modul-Verwaltung. */
|
||||
@@ -35,10 +37,14 @@ export async function installModule(file: File): Promise<Module> {
|
||||
if (!response.ok) {
|
||||
let message = 'Installation fehlgeschlagen';
|
||||
let details: Record<string, string | string[]> | undefined;
|
||||
let code: string | undefined;
|
||||
let diagnostic: string | undefined;
|
||||
try {
|
||||
const errorBody = (await response.json()) as {
|
||||
message?: string | string[];
|
||||
details?: Record<string, string | string[]>;
|
||||
code?: string;
|
||||
diagnostic?: string;
|
||||
};
|
||||
if (typeof errorBody.message === 'string') {
|
||||
message = errorBody.message;
|
||||
@@ -46,10 +52,12 @@ export async function installModule(file: File): Promise<Module> {
|
||||
message = errorBody.message.join(', ');
|
||||
}
|
||||
details = errorBody.details;
|
||||
code = errorBody.code;
|
||||
diagnostic = errorBody.diagnostic;
|
||||
} catch {
|
||||
// Kein JSON in der Antwort – Standardmeldung verwenden.
|
||||
}
|
||||
throw new ApiError(response.status, message, details);
|
||||
throw new ApiError(response.status, message, details, code, diagnostic);
|
||||
}
|
||||
|
||||
const response_ = (await response.json()) as { module: unknown };
|
||||
@@ -85,8 +93,21 @@ export async function setModuleEnabled(id: string, enabled: boolean): Promise<Mo
|
||||
return moduleSchema.parse(response.module);
|
||||
}
|
||||
|
||||
export async function removeModule(id: string): Promise<void> {
|
||||
await apiRequest(`/api/v1/modules/${id}`, { method: 'DELETE' });
|
||||
export async function removeModule(id: string): Promise<{ cleanupWarning?: string }> {
|
||||
return apiRequest<{ cleanupWarning?: string }>(`/api/v1/modules/${id}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function fetchModuleConfiguration(id: string): Promise<ModuleConfiguration> {
|
||||
return moduleConfigurationSchema.parse(await apiRequest(`/api/v1/modules/${id}/configuration`));
|
||||
}
|
||||
|
||||
export async function saveModuleConfiguration(
|
||||
id: string,
|
||||
input: { values: Record<string, string>; clearKeys: string[] },
|
||||
): Promise<ModuleConfiguration> {
|
||||
return moduleConfigurationSchema.parse(await apiRequest(`/api/v1/modules/${id}/configuration`, {
|
||||
method: 'PATCH', body: input,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function checkModuleHealth(
|
||||
@@ -166,6 +187,24 @@ export async function installMarketplaceRepository(
|
||||
return moduleSchema.parse(response.module);
|
||||
}
|
||||
|
||||
export interface MarketplaceUpdateState {
|
||||
installedBranch: string;
|
||||
installedCommit: string | null;
|
||||
branches: Array<{ name: string; commit: string }>;
|
||||
}
|
||||
|
||||
export async function fetchMarketplaceUpdates(moduleId: string): Promise<MarketplaceUpdateState> {
|
||||
return apiRequest<MarketplaceUpdateState>(`/api/v1/marketplace/modules/${encodeURIComponent(moduleId)}/updates`);
|
||||
}
|
||||
|
||||
export async function updateMarketplaceModule(moduleId: string, branch: string): Promise<Module> {
|
||||
const response = await apiRequest<{ module: unknown }>(
|
||||
`/api/v1/marketplace/modules/${encodeURIComponent(moduleId)}/update`,
|
||||
{ method: 'POST', body: { branch } },
|
||||
);
|
||||
return moduleSchema.parse(response.module);
|
||||
}
|
||||
|
||||
export async function fetchMarketplaceProviders(): Promise<MarketplaceProvider[]> {
|
||||
return apiRequest<MarketplaceProvider[]>('/api/v1/marketplace/providers');
|
||||
}
|
||||
|
||||
@@ -134,9 +134,25 @@ export const moduleSchema = z.object({
|
||||
healthcheckUrl: z.string(),
|
||||
enabled: z.boolean(),
|
||||
createdAt: z.string(),
|
||||
configuration: z.array(z.object({
|
||||
key: z.string(), label: z.string(), description: z.string(),
|
||||
type: z.enum(['text', 'url', 'boolean']), secret: z.boolean(), required: z.boolean(),
|
||||
defaultValue: z.string().optional(), services: z.array(z.string()),
|
||||
})),
|
||||
configurationReady: z.boolean(),
|
||||
});
|
||||
export type Module = z.infer<typeof moduleSchema>;
|
||||
|
||||
export const moduleConfigurationSchema = z.object({
|
||||
ready: z.boolean(),
|
||||
fields: z.array(z.object({
|
||||
key: z.string(), label: z.string(), description: z.string(),
|
||||
type: z.enum(['text', 'url', 'boolean']), secret: z.boolean(), required: z.boolean(),
|
||||
services: z.array(z.string()), isSet: z.boolean(), value: z.string().optional(),
|
||||
})),
|
||||
});
|
||||
export type ModuleConfiguration = z.infer<typeof moduleConfigurationSchema>;
|
||||
|
||||
/** Modul-Kachel für das Dashboard (/api/v1/profile/modules). */
|
||||
export const accessibleModuleSchema = z.object({
|
||||
id: z.string(),
|
||||
@@ -155,4 +171,4 @@ export const modulePermissionSchema = z.object({
|
||||
moduleName: z.string(),
|
||||
permission: z.enum(['GRANTED', 'DENIED']),
|
||||
});
|
||||
export type ModulePermission = z.infer<typeof modulePermissionSchema>;
|
||||
export type ModulePermission = z.infer<typeof modulePermissionSchema>;
|
||||
|
||||
6
apps/platform-frontend/src/lib/utils.ts
Normal file
6
apps/platform-frontend/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user