chore: weitere Code-Aufteilung (api.ts, hooks, ldap_sync)

- src/lib/api.ts (1085 Zeilen) → 5 thematische Module unter src/lib/api/
  (core, users, ldap, tenants, mail, system) + index.ts Re-Export
- useLDAPConfig / useTenantLDAPConfig / useTenantUsers Hooks extrahiert;
  admin/page.tsx nutzt diese statt roher useState-Blöcke
- handleSyncTenantLDAP, handleAdminSyncTenantLDAP, doSyncTenantLDAP,
  buildTenantTestConfig, syncResult aus ldap_tenants.go in ldap_sync.go verschoben

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-20 13:05:19 +01:00
parent bc4a98de0d
commit a6a66beaa8
14 changed files with 1797 additions and 1466 deletions
+119
View File
@@ -0,0 +1,119 @@
"use client";
import { useState, useCallback } from "react";
import {
getLDAPConfig,
saveLDAPConfig,
deleteLDAPConfig,
testLDAPConfig,
type LDAPConfig,
type LDAPTestResult,
} from "@/lib/api";
const defaultLDAPForm = (): LDAPConfig => ({
enabled: false,
url: "ldap://",
bind_dn: "",
bind_password: "",
base_dn: "",
user_filter: "(sAMAccountName=%s)",
tls: false,
tls_skip_verify: false,
default_role: "user",
group_mappings: [],
});
export function useLDAPConfig() {
const [ldapConfig, setLdapConfig] = useState<LDAPConfig | null>(null);
const [ldapLoading, setLdapLoading] = useState(false);
const [ldapSaving, setLdapSaving] = useState(false);
const [ldapTesting, setLdapTesting] = useState(false);
const [ldapError, setLdapError] = useState("");
const [ldapTestResult, setLdapTestResult] = useState<LDAPTestResult | null>(null);
const [ldapForm, setLdapForm] = useState<LDAPConfig>(defaultLDAPForm());
const [ldapChangePassword, setLdapChangePassword] = useState(false);
const loadLDAP = useCallback(async () => {
setLdapLoading(true);
setLdapError("");
try {
const cfg = await getLDAPConfig();
if (cfg) {
setLdapConfig(cfg);
setLdapForm({ ...cfg, bind_password: "" });
setLdapChangePassword(false);
}
} catch {
setLdapError("LDAP-Konfiguration konnte nicht geladen werden.");
} finally {
setLdapLoading(false);
}
}, []);
async function handleSaveLDAP(e: React.FormEvent) {
e.preventDefault();
setLdapSaving(true);
setLdapError("");
try {
const payload: Partial<LDAPConfig> = { ...ldapForm };
if (!ldapChangePassword) {
delete payload.bind_password;
}
await saveLDAPConfig(payload);
await loadLDAP();
} catch (err: unknown) {
setLdapError(err instanceof Error ? err.message : "Speichern fehlgeschlagen.");
} finally {
setLdapSaving(false);
}
}
async function handleTestLDAP() {
setLdapTesting(true);
setLdapError("");
setLdapTestResult(null);
try {
const payload = ldapConfig
? { use_saved: true }
: { use_saved: false, ...ldapForm };
const result = await testLDAPConfig(payload as Parameters<typeof testLDAPConfig>[0]);
setLdapTestResult(result);
} catch (err: unknown) {
setLdapError(err instanceof Error ? err.message : "Test fehlgeschlagen.");
} finally {
setLdapTesting(false);
}
}
async function handleDeleteLDAP() {
setLdapSaving(true);
setLdapError("");
try {
await deleteLDAPConfig();
setLdapConfig(null);
setLdapForm(defaultLDAPForm());
setLdapTestResult(null);
} catch (err: unknown) {
setLdapError(err instanceof Error ? err.message : "Löschen fehlgeschlagen.");
} finally {
setLdapSaving(false);
}
}
return {
ldapConfig,
ldapLoading,
ldapSaving,
ldapTesting,
ldapError,
ldapTestResult,
ldapForm,
setLdapForm,
ldapChangePassword,
setLdapChangePassword,
loadLDAP,
handleSaveLDAP,
handleTestLDAP,
handleDeleteLDAP,
};
}
+140
View File
@@ -0,0 +1,140 @@
"use client";
import { useState, useCallback } from "react";
import {
getTenantLDAPConfig,
saveTenantLDAPConfig,
deleteTenantLDAPConfig,
testTenantLDAPConfig,
type TenantLDAPConfig,
type LDAPTestResult,
} from "@/lib/api";
const defaultTenantLDAPForm = (): TenantLDAPConfig => ({
enabled: false,
url: "ldap://",
bind_dn: "",
bind_password: "",
base_dn: "",
user_filter: "(sAMAccountName=%s)",
tls: false,
tls_skip_verify: false,
default_role: "user",
group_mappings: [],
});
export function useTenantLDAPConfig() {
const [tenantLdapConfig, setTenantLdapConfig] = useState<TenantLDAPConfig | null>(null);
const [tenantLdapLoading, setTenantLdapLoading] = useState(false);
const [tenantLdapSaving, setTenantLdapSaving] = useState(false);
const [tenantLdapTesting, setTenantLdapTesting] = useState(false);
const [tenantLdapError, setTenantLdapError] = useState("");
const [tenantLdapTestResult, setTenantLdapTestResult] = useState<LDAPTestResult | null>(null);
const [tenantLdapForm, setTenantLdapForm] = useState<TenantLDAPConfig>(defaultTenantLDAPForm());
const [tenantLdapChangePassword, setTenantLdapChangePassword] = useState(false);
// Own tenant logo state (lives here because it's loaded together with tenant LDAP)
const [ownLogoPreviewUrl, setOwnLogoPreviewUrl] = useState<string | null>(null);
const [ownLogoUploading, setOwnLogoUploading] = useState(false);
const [ownLogoError, setOwnLogoError] = useState("");
const loadTenantLDAP = useCallback(async () => {
setTenantLdapLoading(true);
setTenantLdapError("");
try {
const cfg = await getTenantLDAPConfig();
if (cfg) {
setTenantLdapConfig(cfg);
setTenantLdapForm({ ...cfg, bind_password: "" });
setTenantLdapChangePassword(false);
}
} catch {
setTenantLdapError("LDAP-Konfiguration konnte nicht geladen werden.");
} finally {
setTenantLdapLoading(false);
}
// Load own tenant logo preview
try {
const res = await fetch("/api/tenant/logo", { credentials: "include" });
if (res.ok) {
const blob = await res.blob();
setOwnLogoPreviewUrl(URL.createObjectURL(blob));
}
} catch {
// no logo or not available
}
}, []);
async function handleSaveTenantLDAP(e: React.FormEvent) {
e.preventDefault();
setTenantLdapSaving(true);
setTenantLdapError("");
try {
const payload: Partial<TenantLDAPConfig> = { ...tenantLdapForm };
if (!tenantLdapChangePassword) {
delete payload.bind_password;
}
await saveTenantLDAPConfig(payload);
await loadTenantLDAP();
} catch (err: unknown) {
setTenantLdapError(err instanceof Error ? err.message : "Speichern fehlgeschlagen.");
} finally {
setTenantLdapSaving(false);
}
}
async function handleTestTenantLDAP() {
setTenantLdapTesting(true);
setTenantLdapError("");
setTenantLdapTestResult(null);
try {
const payload = tenantLdapConfig
? { use_saved: true }
: { use_saved: false, ...tenantLdapForm };
const result = await testTenantLDAPConfig(payload as Parameters<typeof testTenantLDAPConfig>[0]);
setTenantLdapTestResult(result);
} catch (err: unknown) {
setTenantLdapError(err instanceof Error ? err.message : "Test fehlgeschlagen.");
} finally {
setTenantLdapTesting(false);
}
}
async function handleDeleteTenantLDAP() {
setTenantLdapSaving(true);
setTenantLdapError("");
try {
await deleteTenantLDAPConfig();
setTenantLdapConfig(null);
setTenantLdapForm(defaultTenantLDAPForm());
setTenantLdapTestResult(null);
} catch (err: unknown) {
setTenantLdapError(err instanceof Error ? err.message : "Löschen fehlgeschlagen.");
} finally {
setTenantLdapSaving(false);
}
}
return {
tenantLdapConfig,
tenantLdapLoading,
tenantLdapSaving,
tenantLdapTesting,
tenantLdapError,
tenantLdapTestResult,
tenantLdapForm,
setTenantLdapForm,
tenantLdapChangePassword,
setTenantLdapChangePassword,
ownLogoPreviewUrl,
setOwnLogoPreviewUrl,
ownLogoUploading,
setOwnLogoUploading,
ownLogoError,
setOwnLogoError,
loadTenantLDAP,
handleSaveTenantLDAP,
handleTestTenantLDAP,
handleDeleteTenantLDAP,
};
}
+69
View File
@@ -0,0 +1,69 @@
"use client";
import { useState } from "react";
import {
getTenantUsers,
syncAdminTenantLDAP,
type User,
type Tenant,
type LDAPSyncResult,
} from "@/lib/api";
export function useTenantUsers() {
const [tenantUsersDialogId, setTenantUsersDialogId] = useState<number | null>(null);
const [tenantUsersDialogName, setTenantUsersDialogName] = useState("");
const [tenantUsersDialogLdap, setTenantUsersDialogLdap] = useState(false);
const [tenantUsers, setTenantUsers] = useState<User[]>([]);
const [tenantUsersLoading, setTenantUsersLoading] = useState(false);
const [tenantUsersError, setTenantUsersError] = useState("");
const [tenantUsersSyncing, setTenantUsersSyncing] = useState(false);
const [tenantUsersSyncResult, setTenantUsersSyncResult] = useState<LDAPSyncResult | null>(null);
async function openUsersDialog(t: Tenant) {
setTenantUsersDialogId(t.id);
setTenantUsersDialogName(t.name);
setTenantUsersDialogLdap(t.ldap_enabled === true);
setTenantUsersLoading(true);
setTenantUsers([]);
setTenantUsersError("");
setTenantUsersSyncResult(null);
try {
const users = await getTenantUsers(t.id);
setTenantUsers(users || []);
} catch (err: unknown) {
setTenantUsersError(err instanceof Error ? err.message : "Nutzer konnten nicht geladen werden.");
} finally {
setTenantUsersLoading(false);
}
}
async function handleSyncLDAPUsers() {
if (!tenantUsersDialogId) return;
setTenantUsersSyncing(true);
setTenantUsersSyncResult(null);
try {
const result = await syncAdminTenantLDAP(tenantUsersDialogId);
setTenantUsersSyncResult(result);
const users = await getTenantUsers(tenantUsersDialogId);
setTenantUsers(users || []);
} catch (err: unknown) {
setTenantUsersSyncResult({ synced: 0, errors: [err instanceof Error ? err.message : "Sync fehlgeschlagen"] });
} finally {
setTenantUsersSyncing(false);
}
}
return {
tenantUsersDialogId,
setTenantUsersDialogId,
tenantUsersDialogName,
tenantUsersDialogLdap,
tenantUsers,
tenantUsersLoading,
tenantUsersError,
tenantUsersSyncing,
tenantUsersSyncResult,
openUsersDialog,
handleSyncLDAPUsers,
};
}