152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback } from "react";
|
|
import { useRouter } from "@/lib/routing/client-router";
|
|
import { IconTrash, IconPlus, IconChevronRight } from "@tabler/icons-react";
|
|
import { Badge } from "@kandev/ui/badge";
|
|
import { Button } from "@kandev/ui/button";
|
|
import { Card, CardContent } from "@kandev/ui/card";
|
|
import { deleteExecutorProfile, listExecutorProfiles } from "@/lib/api/domains/settings-api";
|
|
import { ExecutorProfileDialog } from "@/components/settings/executor-profile-dialog";
|
|
import { useAppStore } from "@/components/state-provider";
|
|
import type { ExecutorProfile } from "@/lib/types/http";
|
|
import { useTranslation } from "react-i18next";
|
|
import { SettingsCardHeader } from "@/components/settings/settings-card-header";
|
|
import { SETTINGS_TYPOGRAPHY } from "@/components/settings/settings-typography";
|
|
import { settingsActionClassName } from "@/components/settings/settings-control";
|
|
|
|
type ExecutorProfilesCardProps = {
|
|
executorId: string;
|
|
profiles: ExecutorProfile[];
|
|
};
|
|
|
|
function ExecutorProfilesHeader({
|
|
title,
|
|
description,
|
|
actionLabel,
|
|
onCreate,
|
|
}: {
|
|
title: string;
|
|
description: string;
|
|
actionLabel: string;
|
|
onCreate: () => void;
|
|
}) {
|
|
return (
|
|
<SettingsCardHeader
|
|
title={title}
|
|
description={description}
|
|
actions={
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={onCreate}
|
|
className={settingsActionClassName("cursor-pointer")}
|
|
>
|
|
<IconPlus className="h-4 w-4 mr-1" />
|
|
{actionLabel}
|
|
</Button>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function ExecutorProfilesCard({ executorId, profiles }: ExecutorProfilesCardProps) {
|
|
const { t } = useTranslation();
|
|
const router = useRouter();
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
const executors = useAppStore((state) => state.executors.items);
|
|
const setExecutors = useAppStore((state) => state.setExecutors);
|
|
|
|
const refreshProfiles = useCallback(async () => {
|
|
try {
|
|
const resp = await listExecutorProfiles(executorId, { cache: "no-store" });
|
|
setExecutors(
|
|
executors.map((e) => (e.id === executorId ? { ...e, profiles: resp.profiles } : e)),
|
|
);
|
|
} catch {
|
|
// ignore refresh failure
|
|
}
|
|
}, [executorId, executors, setExecutors]);
|
|
|
|
const handleCreate = useCallback(() => {
|
|
setDialogOpen(true);
|
|
}, []);
|
|
|
|
const handleProfileCreated = useCallback(
|
|
(profile: ExecutorProfile) => {
|
|
refreshProfiles();
|
|
router.push(`/settings/executor/${executorId}/profile/${profile.id}`);
|
|
},
|
|
[executorId, refreshProfiles, router],
|
|
);
|
|
|
|
const handleDelete = useCallback(
|
|
async (e: React.MouseEvent, profileId: string) => {
|
|
e.stopPropagation();
|
|
try {
|
|
await deleteExecutorProfile(executorId, profileId);
|
|
await refreshProfiles();
|
|
} catch {
|
|
// ignore delete failure
|
|
}
|
|
},
|
|
[executorId, refreshProfiles],
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Card>
|
|
<ExecutorProfilesHeader
|
|
title={t("executors:profiles")}
|
|
description={t("executors:differentConfigurationsForThisExecutorEach")}
|
|
actionLabel={t("executors:add")}
|
|
onCreate={handleCreate}
|
|
/>
|
|
<CardContent>
|
|
{profiles.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">{t("executors:noProfilesConfigured")}</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{profiles.map((profile) => (
|
|
<div
|
|
key={profile.id}
|
|
className="flex items-center justify-between rounded-md border px-3 py-2 hover:bg-muted/50 cursor-pointer transition-colors"
|
|
onClick={() =>
|
|
router.push(`/settings/executor/${executorId}/profile/${profile.id}`)
|
|
}
|
|
>
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<span className="text-sm font-medium truncate">{profile.name}</span>
|
|
{profile.prepare_script && (
|
|
<Badge variant="outline" className={`${SETTINGS_TYPOGRAPHY.meta} shrink-0`}>
|
|
{t("executors:prepareScriptBadge")}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-1 flex-shrink-0">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={(e) => handleDelete(e, profile.id)}
|
|
className="h-11 w-11 p-0 text-destructive hover:text-destructive cursor-pointer md:h-7 md:w-7"
|
|
>
|
|
<IconTrash className="h-3.5 w-3.5" />
|
|
</Button>
|
|
<IconChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
<ExecutorProfileDialog
|
|
open={dialogOpen}
|
|
onOpenChange={setDialogOpen}
|
|
executorId={executorId}
|
|
onSaved={handleProfileCreated}
|
|
/>
|
|
</>
|
|
);
|
|
}
|