2025-01-23 22:58:24 +03:00
|
|
|
import type { AuthenticationInfo } from '@jellyfin/sdk/lib/generated-client/models/authentication-info';
|
|
|
|
import Box from '@mui/material/Box';
|
|
|
|
import Button from '@mui/material/Button';
|
|
|
|
import IconButton from '@mui/material/IconButton';
|
|
|
|
import Tooltip from '@mui/material/Tooltip';
|
|
|
|
import AddIcon from '@mui/icons-material/Add';
|
2025-02-14 15:28:27 -05:00
|
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
|
|
import parseISO from 'date-fns/parseISO';
|
|
|
|
import { type MRT_ColumnDef, useMaterialReactTable } from 'material-react-table';
|
|
|
|
import React, { useCallback, useMemo } from 'react';
|
|
|
|
|
|
|
|
import DateTimeCell from 'apps/dashboard/components/table/DateTimeCell';
|
|
|
|
import TablePage from 'apps/dashboard/components/table/TablePage';
|
|
|
|
import { useApiKeys } from 'apps/dashboard/features/keys/api/useApiKeys';
|
|
|
|
import { useRevokeKey } from 'apps/dashboard/features/keys/api/useRevokeKey';
|
|
|
|
import { useCreateKey } from 'apps/dashboard/features/keys/api/useCreateKey';
|
|
|
|
import confirm from 'components/confirm/confirm';
|
|
|
|
import prompt from 'components/prompt/prompt';
|
|
|
|
import { useApi } from 'hooks/useApi';
|
|
|
|
import globalize from 'lib/globalize';
|
2025-01-23 22:58:24 +03:00
|
|
|
|
2025-02-14 15:28:27 -05:00
|
|
|
export const Component = () => {
|
2025-01-23 22:58:24 +03:00
|
|
|
const { api } = useApi();
|
|
|
|
const { data: keys, isLoading } = useApiKeys();
|
|
|
|
const revokeKey = useRevokeKey();
|
|
|
|
const createKey = useCreateKey();
|
|
|
|
|
|
|
|
const columns = useMemo<MRT_ColumnDef<AuthenticationInfo>[]>(() => [
|
|
|
|
{
|
|
|
|
id: 'ApiKey',
|
|
|
|
accessorKey: 'AccessToken',
|
|
|
|
header: globalize.translate('HeaderApiKey'),
|
|
|
|
size: 300
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'AppName',
|
|
|
|
accessorKey: 'AppName',
|
|
|
|
header: globalize.translate('HeaderApp')
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'DateIssued',
|
2025-01-31 16:20:08 -05:00
|
|
|
accessorFn: item => item.DateCreated ? parseISO(item.DateCreated) : undefined,
|
|
|
|
Cell: DateTimeCell,
|
2025-01-23 22:58:24 +03:00
|
|
|
header: globalize.translate('HeaderDateIssued'),
|
|
|
|
filterVariant: 'datetime-range'
|
|
|
|
}
|
|
|
|
], []);
|
|
|
|
|
|
|
|
const table = useMaterialReactTable({
|
|
|
|
columns,
|
|
|
|
data: keys?.Items || [],
|
|
|
|
|
|
|
|
state: {
|
|
|
|
isLoading
|
|
|
|
},
|
|
|
|
|
|
|
|
rowCount: keys?.TotalRecordCount || 0,
|
|
|
|
|
|
|
|
enableColumnPinning: true,
|
|
|
|
enableColumnResizing: true,
|
|
|
|
|
|
|
|
enableStickyFooter: true,
|
|
|
|
enableStickyHeader: true,
|
|
|
|
muiTableContainerProps: {
|
|
|
|
sx: {
|
|
|
|
maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Enable (delete) row actions
|
|
|
|
enableRowActions: true,
|
|
|
|
positionActionsColumn: 'last',
|
|
|
|
displayColumnDefOptions: {
|
|
|
|
'mrt-row-actions': {
|
|
|
|
header: '',
|
|
|
|
size: 25
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
renderTopToolbarCustomActions: () => (
|
2025-01-31 16:20:08 -05:00
|
|
|
<Button
|
|
|
|
startIcon={<AddIcon />}
|
|
|
|
onClick={showNewKeyPopup}
|
|
|
|
>
|
2025-01-23 22:58:24 +03:00
|
|
|
{globalize.translate('HeaderNewApiKey')}
|
|
|
|
</Button>
|
|
|
|
),
|
|
|
|
|
|
|
|
renderRowActions: ({ row }) => {
|
|
|
|
return (
|
|
|
|
<Box sx={{ display: 'flex' }}>
|
|
|
|
<Tooltip title={globalize.translate('ButtonRevoke')}>
|
|
|
|
<IconButton
|
|
|
|
color='error'
|
|
|
|
// eslint-disable-next-line react/jsx-no-bind
|
|
|
|
onClick={() => row.original?.AccessToken && onRevokeKey(row.original.AccessToken)}
|
|
|
|
>
|
|
|
|
<DeleteIcon />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const onRevokeKey = useCallback((accessToken: string) => {
|
|
|
|
if (!api) return;
|
|
|
|
|
|
|
|
confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () {
|
|
|
|
revokeKey.mutate({
|
|
|
|
key: accessToken
|
|
|
|
});
|
|
|
|
}).catch(err => {
|
|
|
|
console.error('[apikeys] failed to show confirmation dialog', err);
|
|
|
|
});
|
|
|
|
}, [api, revokeKey]);
|
|
|
|
|
|
|
|
const showNewKeyPopup = useCallback(() => {
|
|
|
|
if (!api) return;
|
|
|
|
|
2025-02-14 15:28:27 -05:00
|
|
|
prompt({
|
|
|
|
title: globalize.translate('HeaderNewApiKey'),
|
|
|
|
label: globalize.translate('LabelAppName'),
|
|
|
|
description: globalize.translate('LabelAppNameExample')
|
|
|
|
}).then((value) => {
|
|
|
|
createKey.mutate({
|
|
|
|
app: value
|
2025-01-23 22:58:24 +03:00
|
|
|
});
|
2025-02-14 15:28:27 -05:00
|
|
|
}).catch(() => {
|
|
|
|
// popup closed
|
2025-01-23 22:58:24 +03:00
|
|
|
});
|
|
|
|
}, [api, createKey]);
|
|
|
|
|
|
|
|
return (
|
2025-02-14 15:28:27 -05:00
|
|
|
<TablePage
|
2025-01-23 22:58:24 +03:00
|
|
|
id='apiKeysPage'
|
|
|
|
title={globalize.translate('HeaderApiKeys')}
|
2025-02-14 15:28:27 -05:00
|
|
|
subtitle={globalize.translate('HeaderApiKeysHelp')}
|
2025-01-23 22:58:24 +03:00
|
|
|
className='mainAnimatedPage type-interior'
|
2025-02-14 15:28:27 -05:00
|
|
|
table={table}
|
|
|
|
/>
|
2025-01-23 22:58:24 +03:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2025-02-14 15:28:27 -05:00
|
|
|
Component.displayName = 'ApiKeysPage';
|