Files
kandev/apps/web/components/settings/kubernetes-connection-card.tsx
T
2026-09-02 13:50:03 +00:00

185 lines
6.2 KiB
TypeScript

"use client";
import { useTranslation } from "react-i18next";
import { CardContent } from "@kandev/ui/card";
import { Input } from "@kandev/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select";
import { SettingsCard } from "./settings-card";
import { SettingsCardHeader } from "./settings-card-header";
import { SettingsField } from "./settings-field";
import { settingsControlClassName, settingsCredentialClassName } from "./settings-control";
import { isKubernetesExecutorDirty, type KubernetesExecutorForm } from "./kubernetes-config";
// i18n-exempt: example persisted Kubernetes namespace identifier.
const KUBERNETES_NAMESPACE_EXAMPLE = "agents";
// i18n-exempt: example absolute filesystem path, rendered verbatim.
const KUBECONFIG_PATH_EXAMPLE = "/etc/kandev/kubeconfig";
type KubernetesConnectionCardProps = {
form: KubernetesExecutorForm;
baseline: KubernetesExecutorForm;
onChange: (form: KubernetesExecutorForm) => void;
readOnly?: boolean;
};
export function KubernetesConnectionCard({
form,
baseline,
onChange,
readOnly = false,
}: KubernetesConnectionCardProps) {
const { t } = useTranslation();
const update = <K extends keyof KubernetesExecutorForm>(
key: K,
value: KubernetesExecutorForm[K],
) => onChange({ ...form, [key]: value });
return (
<SettingsCard
className="min-w-0 overflow-hidden"
isDirty={isKubernetesExecutorDirty(form, baseline)}
data-testid="kubernetes-connection-card"
>
<SettingsCardHeader
title={t("executors:kubernetesConnectionTitle")}
description={t("executors:kubernetesConnectionDescription")}
/>
<CardContent>
<fieldset disabled={readOnly} className="grid min-w-0 gap-5 md:grid-cols-2">
<SettingsField
className="md:col-span-2"
label={t("executors:kubernetesExecutorName")}
helper={t("executors:kubernetesExecutorNameHelp")}
labelProps={{ htmlFor: "kubernetes-executor-name" }}
>
<Input
id="kubernetes-executor-name"
data-testid="kubernetes-executor-name"
value={form.name}
onChange={(event) => update("name", event.target.value)}
className={settingsControlClassName()}
/>
</SettingsField>
<KubernetesAuthenticationField form={form} update={update} />
<SettingsField
label={t("executors:kubernetesNamespace")}
helper={t("executors:kubernetesNamespaceHelp")}
labelProps={{ htmlFor: "kubernetes-namespace" }}
>
<Input
id="kubernetes-namespace"
data-testid="kubernetes-namespace"
value={form.namespace}
onChange={(event) => update("namespace", event.target.value)}
placeholder={KUBERNETES_NAMESPACE_EXAMPLE}
className={settingsCredentialClassName()}
/>
</SettingsField>
{form.authMode === "kubeconfig" && <KubeconfigFields form={form} update={update} />}
<SettingsField
label={t("executors:kubernetesRequestTimeout")}
helper={t("executors:kubernetesRequestTimeoutHelp")}
labelProps={{ htmlFor: "kubernetes-request-timeout" }}
>
<Input
id="kubernetes-request-timeout"
data-testid="kubernetes-request-timeout"
type="number"
min={1}
max={300}
inputMode="numeric"
value={form.requestTimeoutSeconds}
onChange={(event) => update("requestTimeoutSeconds", event.target.value)}
className={settingsControlClassName()}
/>
</SettingsField>
</fieldset>
</CardContent>
</SettingsCard>
);
}
type UpdateExecutorField = <K extends keyof KubernetesExecutorForm>(
key: K,
value: KubernetesExecutorForm[K],
) => void;
function KubernetesAuthenticationField({
form,
update,
}: {
form: KubernetesExecutorForm;
update: UpdateExecutorField;
}) {
const { t } = useTranslation();
const helperKey =
form.authMode === "kubeconfig"
? "executors:kubernetesKubeconfigAuthHelp"
: "executors:kubernetesInClusterAuthHelp";
return (
<SettingsField
label={t("executors:kubernetesAuthMode")}
helper={t(helperKey)}
labelProps={{ htmlFor: "kubernetes-auth-mode" }}
>
<Select
value={form.authMode}
onValueChange={(value) => update("authMode", value as KubernetesExecutorForm["authMode"])}
>
<SelectTrigger
id="kubernetes-auth-mode"
data-testid="kubernetes-auth-mode"
className={settingsControlClassName("w-full")}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="kubeconfig">{t("executors:kubernetesAuthKubeconfig")}</SelectItem>
<SelectItem value="in_cluster">{t("executors:kubernetesAuthInCluster")}</SelectItem>
</SelectContent>
</Select>
</SettingsField>
);
}
function KubeconfigFields({
form,
update,
}: {
form: KubernetesExecutorForm;
update: UpdateExecutorField;
}) {
const { t } = useTranslation();
return (
<>
<SettingsField
label={t("executors:kubernetesKubeconfigPath")}
helper={t("executors:kubernetesKubeconfigPathHelp")}
labelProps={{ htmlFor: "kubernetes-kubeconfig-path" }}
>
<Input
id="kubernetes-kubeconfig-path"
data-testid="kubernetes-kubeconfig-path"
value={form.kubeconfigPath}
onChange={(event) => update("kubeconfigPath", event.target.value)}
placeholder={KUBECONFIG_PATH_EXAMPLE}
className={settingsCredentialClassName()}
/>
</SettingsField>
<SettingsField
label={t("executors:kubernetesContext")}
helper={t("executors:kubernetesContextHelp")}
labelProps={{ htmlFor: "kubernetes-context" }}
>
<Input
id="kubernetes-context"
data-testid="kubernetes-context"
value={form.kubeContext}
onChange={(event) => update("kubeContext", event.target.value)}
className={settingsCredentialClassName()}
/>
</SettingsField>
</>
);
}