import type { IntegrationType } from "../db/schema.js"; import type { IntegrationField } from "./types.js"; /** * Field schema per integration type. Only types with a working adapter appear * here — the "Add integration" form only offers what's actually implemented. */ export const INTEGRATION_FIELDS: Partial> = { tailscale: [ { key: "tailnet", label: "Tailnet", secret: false, placeholder: "yourorg.github — or - for your default tailnet", }, { key: "apiKey", label: "API key", secret: true, type: "password" }, ], gitea: [ { key: "url", label: "Gitea URL", secret: false, placeholder: "https://gitea.example.lan" }, { key: "token", label: "API token", secret: true, type: "password" }, ], dockhand: [ { key: "url", label: "Dockhand URL", secret: false, placeholder: "https://dockhand.example.lan" }, { key: "token", label: "API token", secret: true, type: "password", placeholder: "dh_..." }, ], semaphore: [ { key: "url", label: "Semaphore URL", secret: false, placeholder: "https://semaphore.example.lan" }, { key: "token", label: "API token", secret: true, type: "password" }, ], proxmox: [ { key: "url", label: "Proxmox URL", secret: false, placeholder: "https://pve.example.lan:8006" }, { key: "tokenId", label: "API token ID", secret: false, placeholder: "root@pam!homelab-manager" }, { key: "tokenSecret", label: "API token secret", secret: true, type: "password" }, { key: "insecure", label: "Allow self-signed certificate", secret: false, type: "checkbox" }, ], synology: [ { key: "url", label: "Synology DSM URL", secret: false, placeholder: "https://nas.example.lan:5001" }, { key: "username", label: "Username", secret: false }, { key: "password", label: "Password", secret: true, type: "password" }, { key: "insecure", label: "Allow self-signed certificate", secret: false, type: "checkbox" }, ], uptimekuma: [ { key: "url", label: "Uptime Kuma URL", secret: false, placeholder: "https://kuma.example.lan" }, { key: "username", label: "Username (leave blank when using an API key)", secret: false, optional: true, placeholder: "only needed on very old installs without API keys", }, { key: "password", label: "API key (or password, on old installs)", secret: true, type: "password" }, ], phpipam: [ { key: "url", label: "phpIPAM URL", secret: false, placeholder: "https://ipam.example.lan" }, { key: "appId", label: "API app ID", secret: false, placeholder: "as set under Administration → API" }, { key: "token", label: "App token (API code)", secret: true, type: "password" }, { key: "insecure", label: "Allow self-signed certificate", secret: false, type: "checkbox" }, ], }; /** Fixed base URL per integration type, stored on the row for display/reference. */ export const INTEGRATION_BASE_URLS: Partial> = { tailscale: "https://api.tailscale.com", }; /** * Resolves the value to store in the integrations.baseUrl column: types that * collect their own URL as a config field (e.g. Gitea) use that; others fall * back to a fixed constant (e.g. Tailscale's API is always api.tailscale.com). */ export function resolveBaseUrl( type: IntegrationType, nonSecretFields: Record, ): string { const url = nonSecretFields.url; if (typeof url === "string" && url) return url; return INTEGRATION_BASE_URLS[type] ?? ""; } export function splitIntegrationConfig( type: IntegrationType, input: Record, ): { secretFields: Record; nonSecretFields: Record } { const fields = INTEGRATION_FIELDS[type] ?? []; const secretFields: Record = {}; const nonSecretFields: Record = {}; for (const field of fields) { if (input[field.key] === undefined) continue; if (field.secret) secretFields[field.key] = input[field.key]; else nonSecretFields[field.key] = input[field.key]; } return { secretFields, nonSecretFields }; } export function validateIntegrationConfig( type: IntegrationType, merged: Record, ): string[] { const fields = INTEGRATION_FIELDS[type] ?? []; return fields .filter((f) => f.type !== "checkbox" && !f.optional) .filter((f) => merged[f.key] === undefined || merged[f.key] === "") .map((f) => f.key); }