|
1 | 1 | import Page from 'components/Page';
|
2 |
| -import SectionTitleContainer from 'elements/SectionTitleContainer'; |
3 | 2 | import { useApi } from 'hooks/useApi';
|
4 | 3 | import globalize from 'lib/globalize';
|
5 |
| -import React, { useCallback } from 'react'; |
| 4 | +import React, { useCallback, useMemo } from 'react'; |
6 | 5 | import type { AuthenticationInfo } from '@jellyfin/sdk/lib/generated-client/models/authentication-info';
|
7 |
| -import Loading from 'components/loading/LoadingComponent'; |
8 | 6 | import confirm from 'components/confirm/confirm';
|
9 |
| -import ApiKeyCell from 'apps/dashboard/features/keys/components/ApiKeyCell'; |
10 | 7 | import { useApiKeys } from 'apps/dashboard/features/keys/api/useApiKeys';
|
11 | 8 | import { useRevokeKey } from 'apps/dashboard/features/keys/api/useRevokeKey';
|
12 | 9 | import { useCreateKey } from 'apps/dashboard/features/keys/api/useCreateKey';
|
| 10 | +import { Box, Button, IconButton, Stack, Tooltip, Typography } from '@mui/material'; |
| 11 | +import { MaterialReactTable, MRT_ColumnDef, useMaterialReactTable } from 'material-react-table'; |
| 12 | +import { getDisplayTime, parseISO8601Date, toLocaleDateString } from 'scripts/datetime'; |
| 13 | +import DeleteIcon from '@mui/icons-material/Delete'; |
13 | 14 |
|
14 | 15 | const ApiKeys = () => {
|
15 | 16 | const { api } = useApi();
|
16 | 17 | const { data: keys, isLoading } = useApiKeys();
|
17 | 18 | const revokeKey = useRevokeKey();
|
18 | 19 | const createKey = useCreateKey();
|
19 | 20 |
|
| 21 | + const columns = useMemo<MRT_ColumnDef<AuthenticationInfo>[]>(() => [ |
| 22 | + { |
| 23 | + id: 'ApiKey', |
| 24 | + accessorKey: 'AccessToken', |
| 25 | + header: globalize.translate('HeaderApiKey'), |
| 26 | + size: 300 |
| 27 | + }, |
| 28 | + { |
| 29 | + id: 'AppName', |
| 30 | + accessorKey: 'AppName', |
| 31 | + header: globalize.translate('HeaderApp') |
| 32 | + }, |
| 33 | + { |
| 34 | + id: 'DateIssued', |
| 35 | + accessorFn: item => parseISO8601Date(item.DateCreated), |
| 36 | + Cell: ({ cell }) => toLocaleDateString(cell.getValue<Date>()) + ' ' + getDisplayTime(cell.getValue<Date>()), |
| 37 | + header: globalize.translate('HeaderDateIssued'), |
| 38 | + filterVariant: 'datetime-range' |
| 39 | + } |
| 40 | + ], []); |
| 41 | + |
| 42 | + const table = useMaterialReactTable({ |
| 43 | + columns, |
| 44 | + data: keys?.Items || [], |
| 45 | + |
| 46 | + state: { |
| 47 | + isLoading |
| 48 | + }, |
| 49 | + |
| 50 | + rowCount: keys?.TotalRecordCount || 0, |
| 51 | + |
| 52 | + enableColumnPinning: true, |
| 53 | + enableColumnResizing: true, |
| 54 | + |
| 55 | + enableStickyFooter: true, |
| 56 | + enableStickyHeader: true, |
| 57 | + muiTableContainerProps: { |
| 58 | + sx: { |
| 59 | + maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer |
| 60 | + } |
| 61 | + }, |
| 62 | + |
| 63 | + // Enable (delete) row actions |
| 64 | + enableRowActions: true, |
| 65 | + displayColumnDefOptions: { |
| 66 | + 'mrt-row-actions': { |
| 67 | + header: '', |
| 68 | + size: 25 |
| 69 | + } |
| 70 | + }, |
| 71 | + |
| 72 | + renderTopToolbarCustomActions: () => ( |
| 73 | + <Button onClick={showNewKeyPopup}>{globalize.translate('ApiKeysNew')}</Button> |
| 74 | + ), |
| 75 | + |
| 76 | + renderRowActions: ({ row }) => { |
| 77 | + return ( |
| 78 | + <Box sx={{ display: 'flex' }}> |
| 79 | + <Tooltip title={globalize.translate('ButtonRevoke')}> |
| 80 | + <IconButton |
| 81 | + color='error' |
| 82 | + // eslint-disable-next-line react/jsx-no-bind |
| 83 | + onClick={() => row.original?.AccessToken && onRevokeKey(row.original.AccessToken)} |
| 84 | + > |
| 85 | + <DeleteIcon /> |
| 86 | + </IconButton> |
| 87 | + </Tooltip> |
| 88 | + </Box> |
| 89 | + ); |
| 90 | + } |
| 91 | + }); |
| 92 | + |
20 | 93 | const onRevokeKey = useCallback((accessToken: string) => {
|
21 | 94 | if (!api) return;
|
22 | 95 |
|
@@ -49,45 +122,34 @@ const ApiKeys = () => {
|
49 | 122 | });
|
50 | 123 | }, [api, createKey]);
|
51 | 124 |
|
52 |
| - if (isLoading) { |
53 |
| - return <Loading />; |
54 |
| - } |
55 |
| - |
56 | 125 | return (
|
57 | 126 | <Page
|
58 | 127 | id='apiKeysPage'
|
59 | 128 | title={globalize.translate('HeaderApiKeys')}
|
60 | 129 | className='mainAnimatedPage type-interior'
|
61 | 130 | >
|
62 |
| - <div className='content-primary'> |
63 |
| - <SectionTitleContainer |
64 |
| - title={globalize.translate('HeaderApiKeys')} |
65 |
| - isBtnVisible={true} |
66 |
| - btnId='btnAddSchedule' |
67 |
| - btnClassName='fab submit sectionTitleButton btnNewKey' |
68 |
| - btnTitle={globalize.translate('Add')} |
69 |
| - btnIcon='add' |
70 |
| - onClick={showNewKeyPopup} |
71 |
| - /> |
72 |
| - <p>{globalize.translate('HeaderApiKeysHelp')}</p> |
73 |
| - <br /> |
74 |
| - <table className='tblApiKeys detailTable'> |
75 |
| - <caption className='clipForScreenReader'>{globalize.translate('ApiKeysCaption')}</caption> |
76 |
| - <thead> |
77 |
| - <tr> |
78 |
| - <th scope='col' className='detailTableHeaderCell'></th> |
79 |
| - <th scope='col' className='detailTableHeaderCell'>{globalize.translate('HeaderApiKey')}</th> |
80 |
| - <th scope='col' className='detailTableHeaderCell'>{globalize.translate('HeaderApp')}</th> |
81 |
| - <th scope='col' className='detailTableHeaderCell'>{globalize.translate('HeaderDateIssued')}</th> |
82 |
| - </tr> |
83 |
| - </thead> |
84 |
| - <tbody className='resultBody'> |
85 |
| - {keys?.Items?.map((key: AuthenticationInfo) => { |
86 |
| - return <ApiKeyCell key={key.AccessToken} apiKey={key} revokeKey={onRevokeKey} />; |
87 |
| - })} |
88 |
| - </tbody> |
89 |
| - </table> |
90 |
| - </div> |
| 131 | + <Box |
| 132 | + className='content-primary' |
| 133 | + sx={{ |
| 134 | + display: 'flex', |
| 135 | + flexDirection: 'column', |
| 136 | + height: '100%' |
| 137 | + }} |
| 138 | + > |
| 139 | + <Box |
| 140 | + sx={{ |
| 141 | + marginBottom: 1 |
| 142 | + }} |
| 143 | + > |
| 144 | + <Stack spacing={2}> |
| 145 | + <Typography variant='h2'> |
| 146 | + {globalize.translate('HeaderApiKeys')} |
| 147 | + </Typography> |
| 148 | + <Typography>{globalize.translate('HeaderApiKeysHelp')}</Typography> |
| 149 | + </Stack> |
| 150 | + </Box> |
| 151 | + <MaterialReactTable table={table} /> |
| 152 | + </Box> |
91 | 153 | </Page>
|
92 | 154 | );
|
93 | 155 | };
|
|
0 commit comments