Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/api/components/ApiKeyLogsDetails.tsx
T

195 lines
7.5 KiB
TypeScript

import { useSubmit, useSearchParams, useNavigation, useParams } from "react-router";
import { useState } from "react";
import { FilterablePropertyDto } from "~/application/dtos/data/FilterablePropertyDto";
import { PaginationDto } from "~/application/dtos/data/PaginationDto";
import SimpleBadge from "~/components/ui/badges/SimpleBadge";
import ButtonSecondary from "~/components/ui/buttons/ButtonSecondary";
import RefreshIcon from "~/components/ui/icons/RefreshIcon";
import InputFilters from "~/components/ui/input/InputFilters";
import InputSearchWithURL from "~/components/ui/input/InputSearchWithURL";
import ShowPayloadModalButton from "~/components/ui/json/ShowPayloadModalButton";
import { FilterableValueLink } from "~/components/ui/links/FilterableValueLink";
import TableSimple from "~/components/ui/tables/TableSimple";
import ApiUtils from "~/utils/app/ApiUtils";
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
import { getUserHasPermission } from "~/utils/helpers/PermissionsHelper";
import { useLocaleFormatters } from "~/locale/format";
import { ApiKeyLogDto } from "../dtos/ApiKeyLogDto";
import ApiCallSpeedBadge from "./ApiCallSpeedBadge";
import { useTranslation } from "react-i18next";
import ApiCallStatusBadge from "./ApiCallStatusBadge";
import { Button } from "~/components/ui/button";
interface Props {
data: {
items: ApiKeyLogDto[];
filterableProperties: FilterablePropertyDto[];
pagination: PaginationDto;
};
}
export default function ApiKeyLogsDetails({ data }: Props) {
const { t } = useTranslation();
const formatters = useLocaleFormatters();
const appOrAdminData = useAppOrAdminData();
const submit = useSubmit();
const navigation = useNavigation();
const params = useParams();
const canDelete = !params.tenant;
const [searchParams, setSearchParams] = useSearchParams();
const [selectedRows, setSelectedRows] = useState<ApiKeyLogDto[]>([]);
function onDelete(ids: string[]) {
if (!canDelete) {
return;
}
const form = new FormData();
form.set("action", "delete");
form.set("ids", ids.join(","));
submit(form, {
method: "post",
});
}
return (
<div className="space-y-2">
<div className="flex w-full items-center space-x-2">
<div className="grow">
<InputSearchWithURL size="sm" />
</div>
{canDelete && selectedRows.length > 0 && (
<Button
type="button"
variant="outline"
disabled={!getUserHasPermission(appOrAdminData, "admin.apiKeys.delete")}
onClick={() => {
onDelete(selectedRows.map((x) => x.id));
setSelectedRows([]);
}}
>
{t("shared.delete")} {selectedRows.length}
</Button>
)}
{/* <Button type="button" variant="outline" size="sm" onClick={() => setSearchParams(searchParams)} isLoading={navigation.state === "loading"}>
<RefreshIcon className="size-3.5" />
</Button> */}
<InputFilters size="sm" filters={data.filterableProperties} />
</div>
<TableSimple
selectedRows={canDelete ? selectedRows : undefined}
onSelected={canDelete ? setSelectedRows : undefined}
items={data.items}
pagination={data.pagination}
actions={[
{
title: t("shared.delete"),
onClick: (_, item) => onDelete([item.id]),
hidden: () => !canDelete,
disabled: () => !canDelete,
destructive: true,
},
]}
headers={[
{
name: "createdAt",
title: t("shared.createdAt"),
value: (item) => formatters.dateTime(item.createdAt),
formattedValue: (item) => (
<div className="text-muted-foreground text-xs">{item.createdAt && <span>{formatters.dateTime(item.createdAt)}</span>}</div>
),
},
{
name: "tenant",
title: t("models.tenant.object"),
value: (item) => {
const tenantName = item.apiKey?.tenant?.name ?? item.tenant?.name;
const tenantId = item.apiKey?.tenant?.id ?? item.tenant?.id;
return <FilterableValueLink name="tenantId" value={tenantName} param={tenantId} />;
},
hidden: !!params.tenant,
},
{
name: "apiKeyId",
title: t("models.apiKey.alias"),
value: (item) => <FilterableValueLink name="apiKeyId" value={item.apiKey?.alias ?? t("shared.notSet")} param={item.apiKeyId ?? "{null}"} />,
},
{
name: "ip",
title: t("models.apiKeyLog.ip"),
value: (item) => item.ip,
formattedValue: (item) => (
<div>{item.ip.length > 0 ? <FilterableValueLink name="ip" value={item.ip} /> : <span className="text-gray-300">{t("shared.notSet")}</span>}</div>
),
},
{
name: "endpoint",
title: t("models.apiKeyLog.endpoint"),
value: (item) => <FilterableValueLink name="endpoint" value={item.endpoint} />,
},
{
name: "method",
title: t("models.apiKeyLog.method"),
value: (item) => item.method,
formattedValue: (item) => (
<div>
<FilterableValueLink name="method" value={item.method}>
<SimpleBadge title={item.method} color={ApiUtils.getMethodColor(item.method)} underline />
</FilterableValueLink>
</div>
),
},
{
name: "status",
title: t("models.apiKeyLog.status"),
value: (item) => item.status,
formattedValue: (item) => (
<div>
{item.status ? (
<span>
<FilterableValueLink name="status" value={item.status.toString()}>
<ApiCallStatusBadge item={item} underline />
</FilterableValueLink>
</span>
) : (
<span className="text-gray-300">{t("shared.notSet")}</span>
)}
</div>
),
},
{
name: "duration",
title: t("models.apiKeyLog.duration"),
value: (item) =>
item.duration === null ? (
<span className="text-muted-foreground text-xs italic">{t("shared.notSet")}</span>
) : (
<div>{formatters.number(Number(item.duration), { style: "unit", unit: "millisecond", unitDisplay: "short", maximumFractionDigits: 3 })}</div>
),
},
{
name: "speed",
title: t("models.apiKeyLog.speed"),
value: (item) =>
item.duration === null ? <span className="text-muted-foreground text-xs italic">{t("shared.notSet")}</span> : <ApiCallSpeedBadge duration={Number(item.duration)} />,
},
{
name: "params",
title: t("models.apiKeyLog.params"),
value: (item) =>
item.params === "{}" ? (
<span className="text-muted-foreground text-xs italic">{t("shared.notSet")}</span>
) : (
<ShowPayloadModalButton description={item.params} payload={item.params} />
),
},
{
name: "error",
title: t("models.apiKeyLog.error"),
value: (item) => <div className="text-red-500">{item.error}</div>,
},
]}
/>
</div>
);
}