195 lines
7.5 KiB
TypeScript
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>
|
|
);
|
|
}
|