feat(nextcloud-status): Modul mit Statusabruf, Versionsbewertung und Kachelansicht
- NextcloudInstance mit Zeilenschutz, Migration 20261002150000 - Bewertung (Ampel) als reine Funktion, status.php-Abruf mit Grenzen, endoflife.date-Zwischenspeicher - API: Liste mit Bewertung, Anlegen und Einzelprüfung nur mit Verwalten - Modulseite mit Kacheln, Registrierung in Seitenleiste, Marktplatz und Symbolen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* (`--tile`, `--tile-foreground`, `--primary`, `--primary-foreground`).
|
||||
*/
|
||||
|
||||
export type ModuleIconId = 'radar' | 'fuel' | 'certificate' | 'globe' | 'server' | 'utensils' | 'shopping-bag' | 'tile';
|
||||
export type ModuleIconId = 'radar' | 'fuel' | 'certificate' | 'globe' | 'server' | 'utensils' | 'shopping-bag' | 'cloud' | 'tile';
|
||||
|
||||
const ICONS: Record<string, ModuleIconId> = {
|
||||
'tender-radar': 'radar',
|
||||
@@ -15,6 +15,7 @@ const ICONS: Record<string, ModuleIconId> = {
|
||||
'cert-manager': 'certificate',
|
||||
domaincheck: 'globe',
|
||||
proxmox: 'server',
|
||||
'nextcloud-status': 'cloud',
|
||||
'kantine-datev': 'utensils',
|
||||
'handelsware-datev': 'shopping-bag',
|
||||
};
|
||||
|
||||
@@ -59,6 +59,12 @@ export const MODULE_REGISTRY: Record<string, ModuleRegistryEntry> = {
|
||||
{ ssr: false },
|
||||
),
|
||||
},
|
||||
'nextcloud-status': {
|
||||
component: dynamic(
|
||||
() => import('@/app/(portal)/modules/nextcloud-status/page'),
|
||||
{ ssr: false },
|
||||
),
|
||||
},
|
||||
'kantine-datev': {
|
||||
component: dynamic(
|
||||
() => import('@/app/(portal)/modules/kantine-datev/page'),
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Nextcloud-Status Module API client (quick-261002-k67). Konsumiert
|
||||
* `/modules/nextcloud-status/*`. Vorbild `proxmox-api.ts`:
|
||||
* `credentials: 'include'` fuer Cookie-Auth, `NEXT_PUBLIC_API_URL` als Basis.
|
||||
*
|
||||
* Die Antworten tragen nur ausgewertete Felder — nie einen rohen
|
||||
* Antworttext einer Cloud und nie Logo-Bytes (die kommen ueber die
|
||||
* eigene Logo-Adresse, siehe `logoSrc`).
|
||||
*/
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
export type RatingLevel = 'green' | 'yellow' | 'red' | 'unknown';
|
||||
|
||||
export type RatingReason =
|
||||
| 'current'
|
||||
| 'update-available'
|
||||
| 'eol-soon'
|
||||
| 'eol-passed'
|
||||
| 'unreachable'
|
||||
| 'invalid-response'
|
||||
| 'maintenance'
|
||||
| 'needs-db-upgrade'
|
||||
| 'no-reference'
|
||||
| 'version-unknown'
|
||||
| 'not-checked';
|
||||
|
||||
export interface NextcloudRating {
|
||||
level: RatingLevel;
|
||||
reason: RatingReason;
|
||||
/** Neuester Patchstand im eigenen Zweig, nur wenn ein Update ansteht. */
|
||||
updateTo: string | null;
|
||||
/** Support-Ende des Zweigs als 'YYYY-MM-DD', wenn bekannt. */
|
||||
eolDate: string | null;
|
||||
cycle: number | null;
|
||||
}
|
||||
|
||||
export interface NextcloudInstanceStatus {
|
||||
checkedAt: string | null;
|
||||
reachable: boolean | null;
|
||||
maintenance: boolean | null;
|
||||
needsDbUpgrade: boolean | null;
|
||||
versionString: string | null;
|
||||
edition: string | null;
|
||||
errorKind: string | null;
|
||||
errorDetail: string | null;
|
||||
}
|
||||
|
||||
export interface NextcloudInstance {
|
||||
id: string;
|
||||
customerName: string;
|
||||
baseUrl: string;
|
||||
logoUrl: string | null;
|
||||
hasUploadedLogo: boolean;
|
||||
logoVersion: number;
|
||||
status: NextcloudInstanceStatus;
|
||||
rating: NextcloudRating;
|
||||
}
|
||||
|
||||
export interface NextcloudList {
|
||||
instances: NextcloudInstance[];
|
||||
reference: { newestVersion: string | null; fetchedAt: string | null };
|
||||
}
|
||||
|
||||
/** GET /modules/nextcloud-status/instances — Clouds samt Bewertung und neuester Version. */
|
||||
export async function listInstances(): Promise<NextcloudList> {
|
||||
const res = await fetch(`${API_URL}/modules/nextcloud-status/instances`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to fetch nextcloud instances');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bildquelle fuer das Logo einer Cloud: ein hochgeladenes Logo kommt ueber den
|
||||
* same-origin-Proxy (angemeldet, mit `?v=` gegen alte Zwischenspeicher), eine
|
||||
* Bildadresse laedt der Browser direkt; ohne beides `null` (Initialen).
|
||||
*/
|
||||
export function logoSrc(
|
||||
instance: Pick<NextcloudInstance, 'id' | 'hasUploadedLogo' | 'logoVersion' | 'logoUrl'>,
|
||||
): string | null {
|
||||
if (instance.hasUploadedLogo) {
|
||||
return `/api-proxy/modules/nextcloud-status/instances/${instance.id}/logo?v=${instance.logoVersion}`;
|
||||
}
|
||||
return instance.logoUrl ?? null;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export const useNavStore = create<NavState>()((set) => ({
|
||||
/** Ersatztitel, solange die Seitenleiste die Module noch nicht geladen hat. */
|
||||
const MODULE_TITLE_KEYS: Record<string, string> = {
|
||||
proxmox: 'proxmox.title',
|
||||
'nextcloud-status': 'nextcloudStatus.title',
|
||||
domaincheck: 'domaincheck.title',
|
||||
'cert-manager': 'certManager.title',
|
||||
'dkv-fleet': 'dkvFleet.pageTitle',
|
||||
|
||||
Reference in New Issue
Block a user