"use client"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { Button, Center, Group, Loader, Modal, Stack, Text } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { getCustomWidgetSourceSetups } from "@homarr/custom-widgets/core"; import type { CustomWidgetSecretKind } from "@homarr/custom-widgets/core"; import { createCustomWidgetSourceSetupValues, CustomWidgetSourceSetupPanel, isCustomWidgetSourceSetupReady, } from "@homarr/custom-widgets/workbench"; import type { CustomWidgetSourceSetupMessages, CustomWidgetSourceSetupValue } from "@homarr/custom-widgets/workbench"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n } from "@homarr/translation/client"; interface CustomWidgetSourceSetupDialogProps { definitionId: string; opened: boolean; onClose(): void; } export function CustomWidgetSourceSetupDialog({ definitionId, opened, onClose }: CustomWidgetSourceSetupDialogProps) { const t = useI18n("customWidget"); const tCommon = useI18n("common"); const router = useRouter(); const utils = clientApi.useUtils(); const definitionQuery = clientApi.customWidget.get.useQuery({ id: definitionId }, { enabled: opened }); const configureMutation = clientApi.customWidget.sourceConfigure.useMutation(); const [saving, setSaving] = useState(false); const [values, setValues] = useState>({}); const setups = useMemo( () => getCustomWidgetSourceSetups(definitionQuery.data?.sources ?? {}, definitionQuery.data?.secrets ?? []), [definitionQuery.data], ); useEffect(() => { if (opened && definitionQuery.data) setValues(createCustomWidgetSourceSetupValues(setups)); }, [definitionQuery.data, opened, setups]); const messages: CustomWidgetSourceSetupMessages = { title: t("importReview.sourceSetup.title"), description: t("sourceSetupDialog.description"), suggestedUrl: t("importReview.sourceSetup.suggestedUrl"), baseUrl: t("workbench.sources.baseUrl"), networkScope: t("workbench.sources.networkScope"), authentication: t("workbench.sources.authentication"), confirmUrl: t("importReview.sourceSetup.confirmUrl"), ready: t("importReview.sourceSetup.ready"), needsUrl: t("importReview.sourceSetup.needsUrl"), credentialsMissing: t("importReview.sourceSetup.credentialsMissing"), credentialsOptional: t("sourceSetupDialog.credentialsRequired"), configured: t("workbench.sources.configured"), secret: (kind) => t(`secret.${kind}`), urlError: (issue) => t(`workbench.sources.baseUrlError.${issue}`), }; const save = async () => { if (!isCustomWidgetSourceSetupReady(setups, values) || saving) return; setSaving(true); try { for (const setup of setups) { const value = values[setup.sourceId]; if (!value) continue; const secrets = Object.entries(value.secrets).flatMap(([kind, secret]) => secret?.trim() ? [{ sourceId: setup.sourceId, kind: kind as CustomWidgetSecretKind, value: secret }] : [], ); await configureMutation.mutateAsync({ definitionId, sourceId: setup.sourceId, baseUrl: value.baseUrl, networkScope: value.networkScope, secrets, }); } showSuccessNotification({ title: t("action.configureSources"), message: t("notification.sourcesConfigured"), }); await utils.customWidget.get.invalidate({ id: definitionId }); await utils.customWidget.list.invalidate(); await utils.customWidget.available.invalidate(); await utils.widget.customApi.getData.invalidate(); await revalidatePathActionAsync("/manage/custom-widgets"); router.refresh(); onClose(); } catch (error) { showErrorNotification({ title: t("action.configureSources"), message: error instanceof Error ? error.message : t("notification.sourceConfigurationError"), }); } finally { setSaving(false); } }; return ( {definitionQuery.isPending ? (
) : definitionQuery.error ? ( {definitionQuery.error.message} ) : ( setValues((current) => ({ ...current, [sourceId]: value }))} /> )}
); }