140 lines
5.2 KiB
TypeScript
140 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useRouter } from "next/navigation";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
|
|
import { clientApi } from "@homarr/api/client";
|
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
|
import {
|
|
applyCustomWidgetSourceSetup,
|
|
getCustomWidgetSourceSetups,
|
|
getImportReview,
|
|
} from "@homarr/custom-widgets/core";
|
|
import type { CustomWidgetSecretKind, HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core";
|
|
import {
|
|
createCustomWidgetSourceSetupValues,
|
|
CustomWidgetSourceSetupPanel,
|
|
ImportReviewDialog,
|
|
isCustomWidgetSourceSetupReady,
|
|
} from "@homarr/custom-widgets/workbench";
|
|
import type { CustomWidgetSourceSetupValue } from "@homarr/custom-widgets/workbench";
|
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
|
import { useScopedI18n } from "@homarr/translation/client";
|
|
|
|
interface CustomWidgetImportDialogProps {
|
|
opened: boolean;
|
|
widget: HomarrCustomWidgetV2 | null;
|
|
stackId?: string;
|
|
zIndex?: number;
|
|
onClose(): void;
|
|
onImported?(result: { id: string }): void;
|
|
}
|
|
|
|
export function CustomWidgetImportDialog({
|
|
opened,
|
|
widget,
|
|
stackId,
|
|
zIndex,
|
|
onClose,
|
|
onImported,
|
|
}: CustomWidgetImportDialogProps) {
|
|
const t = useScopedI18n("customWidget");
|
|
const router = useRouter();
|
|
const utils = clientApi.useUtils();
|
|
const setups = useMemo(() => getCustomWidgetSourceSetups(widget?.sources ?? {}), [widget]);
|
|
const [values, setValues] = useState<Record<string, CustomWidgetSourceSetupValue>>({});
|
|
const configuredWidget = useMemo(() => {
|
|
if (!widget) return null;
|
|
return {
|
|
...widget,
|
|
sources: applyCustomWidgetSourceSetup(
|
|
widget.sources,
|
|
Object.fromEntries(
|
|
Object.entries(values).map(([sourceId, value]) => [
|
|
sourceId,
|
|
{ baseUrl: value.baseUrl, networkScope: value.networkScope },
|
|
]),
|
|
),
|
|
),
|
|
};
|
|
}, [values, widget]);
|
|
const review = useMemo(() => getImportReview(configuredWidget), [configuredWidget]);
|
|
const mutation = clientApi.customWidget.import.useMutation({
|
|
onSuccess: (result) => {
|
|
showSuccessNotification({ title: t("action.import"), message: t("notification.imported") });
|
|
void utils.customWidget.list.invalidate();
|
|
void utils.customWidget.available.invalidate();
|
|
void revalidatePathActionAsync("/manage/custom-widgets").then(() => router.refresh());
|
|
onImported?.(result);
|
|
onClose();
|
|
},
|
|
onError: (error) => {
|
|
showErrorNotification({ title: t("action.import"), message: error.message || t("notification.importError") });
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (opened) setValues(createCustomWidgetSourceSetupValues(setups));
|
|
}, [opened, setups]);
|
|
|
|
const importWidget = () => {
|
|
if (!configuredWidget) return;
|
|
const secrets = Object.entries(values).flatMap(([sourceId, value]) =>
|
|
Object.entries(value.secrets).flatMap(([kind, secret]) =>
|
|
secret?.trim() ? [{ sourceId, kind: kind as CustomWidgetSecretKind, value: secret }] : [],
|
|
),
|
|
);
|
|
mutation.mutate({ widget: configuredWidget, secrets });
|
|
};
|
|
|
|
return (
|
|
<ImportReviewDialog
|
|
opened={opened}
|
|
stackId={stackId}
|
|
zIndex={zIndex}
|
|
review={review}
|
|
pending={mutation.isPending}
|
|
confirmDisabled={!isCustomWidgetSourceSetupReady(setups, values)}
|
|
onClose={onClose}
|
|
onConfirm={importWidget}
|
|
messages={{
|
|
title: t("importReview.title"),
|
|
description: t("importReview.description"),
|
|
name: t("importReview.name"),
|
|
origin: t("importReview.origin"),
|
|
authentication: t("importReview.authentication"),
|
|
networkScope: t("importReview.networkScope"),
|
|
methods: t("importReview.methods"),
|
|
permissions: t("importReview.permissions"),
|
|
actionWarningTitle: t("importReview.actionWarning.title"),
|
|
actionWarningDescription: t("importReview.actionWarning.description"),
|
|
cancel: t("importReview.cancel"),
|
|
confirm: t("importReview.confirm"),
|
|
permission: (permission) => t(`preview.request.permission.${permission}` as never),
|
|
}}
|
|
>
|
|
<CustomWidgetSourceSetupPanel
|
|
setups={setups}
|
|
values={values}
|
|
onChange={(sourceId, value) => setValues((current) => ({ ...current, [sourceId]: value }))}
|
|
messages={{
|
|
title: t("importReview.sourceSetup.title"),
|
|
description: t("importReview.sourceSetup.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("importReview.sourceSetup.credentialsOptional"),
|
|
configured: t("workbench.sources.configured"),
|
|
secret: (kind) => t(`importReview.credentials.field.${kind}`),
|
|
urlError: (issue) => t(`workbench.sources.baseUrlError.${issue}`),
|
|
}}
|
|
/>
|
|
</ImportReviewDialog>
|
|
);
|
|
}
|