src/frontend/views/settings/Variables/ManageCredentialGroup.tsx
import { msg } from "@lingui/macro";
import { useCallback, useMemo, useState } from "react";
import { ActionButtons } from "@/components/app/button/action";
import { DELETE_BUTTON_PROPS } from "@/components/app/button/constants";
import { OffCanvas } from "@/components/app/off-canvas";
import type {
IFETableCell,
IFETableColumn,
} from "@/components/app/pagination-table";
import { FEPaginationTable } from "@/components/app/pagination-table";
import { useToastActionQueryError } from "@/components/app/toast/error";
import { Card } from "@/components/ui/card";
import { useDocumentationActionButton } from "@/frontend/docs/constants";
import { VariablesDocumentation } from "@/frontend/docs/variables";
import { useUserHasPermission } from "@/frontend/hooks/auth/user.store";
import { useDomainMessages } from "@/frontend/lib/crud-config";
import { useApi } from "@/frontend/lib/data/useApi";
import type { IPageDetails } from "@/frontend/lib/routing/usePageDetails";
import { useSetCurrentActionItems } from "@/frontend/lib/routing/usePageDetails";
import {
PasswordMessage,
PasswordToReveal,
} from "@/frontend/views/integrations/Password";
import { INTEGRATIONS_GROUP_CONFIG } from "@/shared/config-bag/integrations";
import { UserPermissions } from "@/shared/constants/user";
import { IntegrationsConfigurationGroup } from "@/shared/types/integrations";
import type { IKeyValue } from "@/shared/types/options";
import {
INTEGRATIONS_GROUP_ENDPOINT,
useIntegrationConfigurationDeletionMutation,
useIntegrationConfigurationUpsertationMutation,
useRevealedCredentialsList,
} from "./configurations.store";
import { INTEGRATIONS_GROUP_CRUD_CONFIG } from "./constants";
import { KeyValueForm } from "./Form";
const NEW_CONFIG_ITEM = "__new_config_item__";
export function ManageCredentialGroup({
group,
currentTab,
}: {
group: IntegrationsConfigurationGroup;
currentTab: IntegrationsConfigurationGroup;
}) {
const dataEndpoint = INTEGRATIONS_GROUP_ENDPOINT(group);
const upsertConfigurationMutation =
useIntegrationConfigurationUpsertationMutation(group);
const deleteConfigurationMutation =
useIntegrationConfigurationDeletionMutation(group);
const tableData = useApi<IKeyValue[]>(dataEndpoint, { defaultData: [] });
const revealedCredentials = useRevealedCredentialsList(group);
useToastActionQueryError(
revealedCredentials.error,
group === IntegrationsConfigurationGroup.Credentials
);
const userHasPermission = useUserHasPermission();
const [currentConfigItem, setCurrentConfigItem] = useState("");
const closeConfigItem = () => {
setCurrentConfigItem("");
};
const domainMessages = useDomainMessages(
INTEGRATIONS_GROUP_CRUD_CONFIG[group].domainDiction
);
const documentationActionButton = useDocumentationActionButton(
domainMessages.TEXT_LANG.TITLE
);
const MemoizedAction = useCallback(
({ row }: IFETableCell<IKeyValue>) => (
<ActionButtons
size="icon"
actionButtons={[
{
id: "edit",
action: () => setCurrentConfigItem(row.original.key),
label: domainMessages.TEXT_LANG.EDIT,
systemIcon: "Edit",
},
{
...DELETE_BUTTON_PROPS({
action: () =>
deleteConfigurationMutation.mutateAsync(row.original.key),
label: domainMessages.TEXT_LANG.DELETE,
isMakingRequest: false,
}),
},
]}
/>
),
[
deleteConfigurationMutation,
domainMessages.TEXT_LANG.DELETE,
domainMessages.TEXT_LANG.EDIT,
]
);
const canManageAction = !(
group === IntegrationsConfigurationGroup.Credentials &&
!userHasPermission(UserPermissions.CAN_MANAGE_APP_CREDENTIALS)
);
const showManageAction =
canManageAction &&
(group !== IntegrationsConfigurationGroup.Credentials ||
(group === IntegrationsConfigurationGroup.Credentials &&
revealedCredentials.data !== undefined));
const actionItems:
| Pick<IPageDetails, "actionItems" | "secondaryActionItems">
| undefined = useMemo(() => {
if (group !== currentTab) {
return undefined;
}
return {
actionItems: showManageAction
? [
{
id: `add-${showManageAction ? "true" : "false"}`,
action: () => {
setCurrentConfigItem(NEW_CONFIG_ITEM);
},
systemIcon: "Plus",
label: domainMessages.TEXT_LANG.CREATE,
},
]
: [],
secondaryActionItems: [documentationActionButton],
};
}, [
group,
currentTab,
showManageAction,
domainMessages.TEXT_LANG.CREATE,
documentationActionButton,
]);
useSetCurrentActionItems(actionItems);
const tableColumns: IFETableColumn<IKeyValue>[] = [
{
Header: msg`Key`,
accessor: "key",
filter: {
_type: "string",
bag: undefined,
},
Cell: ({ value }: { value: unknown }) => (
<span
dangerouslySetInnerHTML={{
__html: `{{ ${INTEGRATIONS_GROUP_CONFIG[group].prefix}.${value} }}`,
}}
/>
),
},
{
Header: msg`Value`,
accessor: "value",
},
];
if (showManageAction) {
tableColumns.push({
Header: msg`Action`,
disableSortBy: true,
accessor: "__action__",
Cell: MemoizedAction,
});
}
return (
<Card>
{group === IntegrationsConfigurationGroup.Credentials &&
userHasPermission(UserPermissions.CAN_MANAGE_APP_CREDENTIALS) &&
revealedCredentials.data === undefined && (
<div className="my-3 px-3">
<PasswordToReveal isLoading={revealedCredentials.isLoading} />
</div>
)}
{!canManageAction && tableData.data.length > 0 && (
<p className="my-2 px-2 text-sm italic">
Your account does not have the permission to view secret values or
manage them
</p>
)}
<FEPaginationTable
dataEndpoint={dataEndpoint}
empty={{
text: domainMessages.TEXT_LANG.EMPTY_LIST,
createNew: showManageAction
? {
label: domainMessages.TEXT_LANG.CREATE,
action: () => setCurrentConfigItem(NEW_CONFIG_ITEM),
}
: undefined,
}}
columns={tableColumns}
/>
<OffCanvas
title={
currentConfigItem === NEW_CONFIG_ITEM
? domainMessages.TEXT_LANG.CREATE
: domainMessages.TEXT_LANG.EDIT
}
onClose={closeConfigItem}
size="sm"
show={!!currentConfigItem}
>
{group === IntegrationsConfigurationGroup.Credentials && (
<div className="mb-3">
<PasswordMessage />
</div>
)}
<div className="px-1">
<KeyValueForm
group={group}
initialValues={tableData.data.find(
({ key }) => key === currentConfigItem
)}
onSubmit={async (values: { key: string; value: string }) => {
await upsertConfigurationMutation.mutateAsync(values);
closeConfigItem();
}}
/>
</div>
</OffCanvas>
<VariablesDocumentation />
</Card>
);
}