Skip to content

Commit dc6d775

Browse files
committed
fix: add expiry to ui
1 parent cf9a1ea commit dc6d775

7 files changed

Lines changed: 103 additions & 32 deletions

File tree

ui/src/client/AddClientDialog.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ import DialogContent from '@mui/material/DialogContent';
66
import DialogTitle from '@mui/material/DialogTitle';
77
import TextField from '@mui/material/TextField';
88
import Tooltip from '@mui/material/Tooltip';
9+
import {NumberField} from '../common/NumberField';
910

1011
interface IProps {
1112
fClose: VoidFunction;
12-
fOnSubmit: (name: string) => Promise<void>;
13+
fOnSubmit: (name: string, expiresAfterInactivitySeconds: number) => Promise<void>;
1314
}
1415

1516
const AddClientDialog = ({fClose, fOnSubmit}: IProps) => {
1617
const [name, setName] = useState('');
18+
const [expiresAfter, setExpiresAfter] = useState(0);
1719

1820
const submitEnabled = name.length !== 0;
1921
const submitAndClose = async () => {
20-
await fOnSubmit(name);
22+
await fOnSubmit(name, Math.max(0, expiresAfter));
2123
fClose();
2224
};
2325

@@ -35,6 +37,14 @@ const AddClientDialog = ({fClose, fOnSubmit}: IProps) => {
3537
onChange={(e) => setName(e.target.value)}
3638
fullWidth
3739
/>
40+
<NumberField
41+
margin="dense"
42+
className="expires-after"
43+
label="Expires after inactivity (seconds, 0 = never)"
44+
value={expiresAfter}
45+
onChange={(value) => setExpiresAfter(value)}
46+
fullWidth
47+
/>
3848
</DialogContent>
3949
<DialogActions>
4050
<Button onClick={fClose}>Cancel</Button>

ui/src/client/ClientStore.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,35 @@ export class ClientStore extends BaseStore<IClient> {
2020
}
2121

2222
@action
23-
public update = async (id: number, name: string): Promise<void> => {
24-
await axios.put(`${config.get('url')}client/${id}`, {name});
23+
public update = async (
24+
id: number,
25+
name: string,
26+
expiresAfterInactivitySeconds: number
27+
): Promise<void> => {
28+
await axios.put(`${config.get('url')}client/${id}`, {
29+
name,
30+
expiresAfterInactivitySeconds,
31+
});
2532
await this.refresh();
2633
this.snack('Client updated');
2734
};
2835

2936
@action
30-
public createNoNotifcation = async (name: string): Promise<IClient> => {
31-
const client = await axios.post(`${config.get('url')}client`, {name});
37+
public createNoNotifcation = async (
38+
name: string,
39+
expiresAfterInactivitySeconds = 0
40+
): Promise<IClient> => {
41+
const client = await axios.post(`${config.get('url')}client`, {
42+
name,
43+
expiresAfterInactivitySeconds,
44+
});
3245
await this.refresh();
3346
return client.data;
3447
};
3548

3649
@action
37-
public create = async (name: string): Promise<void> => {
38-
await this.createNoNotifcation(name);
50+
public create = async (name: string, expiresAfterInactivitySeconds = 0): Promise<void> => {
51+
await this.createNoNotifcation(name, expiresAfterInactivitySeconds);
3952
this.snack('Client added');
4053
};
4154

ui/src/client/Clients.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const Clients = observer(() => {
5858
<TableCell>Last Used</TableCell>
5959
<TableCell>Elevation ends</TableCell>
6060
<TableCell>Created</TableCell>
61+
<TableCell>Expires in</TableCell>
6162
<TableCell />
6263
<TableCell />
6364
<TableCell />
@@ -72,6 +73,7 @@ const Clients = observer(() => {
7273
createdAt={client.createdAt}
7374
lastUsed={client.lastUsed}
7475
elevatedUntil={client.elevatedUntil}
76+
expiresAt={client.expiresAt}
7577
fEdit={() => setToUpdateClient(client)}
7678
fDelete={() => setToDeleteClient(client)}
7779
fElevate={() => setToElevateClient(client)}
@@ -90,8 +92,13 @@ const Clients = observer(() => {
9092
{toUpdateClient != null && (
9193
<UpdateClientDialog
9294
fClose={() => setToUpdateClient(undefined)}
93-
fOnSubmit={(name) => clientStore.update(toUpdateClient.id, name)}
95+
fOnSubmit={(name, expiresAfterInactivitySeconds) =>
96+
clientStore.update(toUpdateClient.id, name, expiresAfterInactivitySeconds)
97+
}
9498
initialName={toUpdateClient.name}
99+
initialExpiresAfterInactivitySeconds={
100+
toUpdateClient.expiresAfterInactivitySeconds
101+
}
95102
/>
96103
)}
97104
{toDeleteClient != null && (
@@ -120,6 +127,7 @@ interface IRowProps {
120127
createdAt: string;
121128
lastUsed: string | null;
122129
elevatedUntil?: string;
130+
expiresAt: string | null;
123131
fEdit: VoidFunction;
124132
fDelete: VoidFunction;
125133
fElevate: VoidFunction;
@@ -131,6 +139,7 @@ const Row = ({
131139
createdAt,
132140
lastUsed,
133141
elevatedUntil,
142+
expiresAt,
134143
fEdit,
135144
fDelete,
136145
fElevate,
@@ -156,6 +165,13 @@ const Row = ({
156165
<TableCell>
157166
<TimeAgo date={createdAt} formatter={TimeAgoFormatter.long} />
158167
</TableCell>
168+
<TableCell className="expires-in">
169+
{expiresAt ? (
170+
<TimeAgo date={expiresAt} formatter={TimeAgoFormatter.longMinutes} />
171+
) : (
172+
'-'
173+
)}
174+
</TableCell>
159175
<TableCell align="right" padding="none">
160176
<Tooltip title="Elevate">
161177
<IconButton onClick={fElevate} className="elevate">

ui/src/client/UpdateClientDialog.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,27 @@ import DialogContentText from '@mui/material/DialogContentText';
77
import DialogTitle from '@mui/material/DialogTitle';
88
import TextField from '@mui/material/TextField';
99
import Tooltip from '@mui/material/Tooltip';
10+
import {NumberField} from '../common/NumberField';
1011

1112
interface IProps {
1213
fClose: VoidFunction;
13-
fOnSubmit: (name: string) => Promise<void>;
14+
fOnSubmit: (name: string, expiresAfterInactivitySeconds: number) => Promise<void>;
1415
initialName: string;
16+
initialExpiresAfterInactivitySeconds: number;
1517
}
1618

17-
const UpdateClientDialog = ({fClose, fOnSubmit, initialName = ''}: IProps) => {
19+
const UpdateClientDialog = ({
20+
fClose,
21+
fOnSubmit,
22+
initialName = '',
23+
initialExpiresAfterInactivitySeconds,
24+
}: IProps) => {
1825
const [name, setName] = useState(initialName);
26+
const [expiresAfter, setExpiresAfter] = useState(initialExpiresAfterInactivitySeconds);
1927

2028
const submitEnabled = name.length !== 0;
2129
const submitAndClose = async () => {
22-
await fOnSubmit(name);
30+
await fOnSubmit(name, Math.max(0, expiresAfter));
2331
fClose();
2432
};
2533

@@ -41,6 +49,14 @@ const UpdateClientDialog = ({fClose, fOnSubmit, initialName = ''}: IProps) => {
4149
onChange={(e) => setName(e.target.value)}
4250
fullWidth
4351
/>
52+
<NumberField
53+
margin="dense"
54+
className="expires-after"
55+
label="Expires after inactivity (seconds, 0 = never)"
56+
value={expiresAfter}
57+
onChange={(value) => setExpiresAfter(value)}
58+
fullWidth
59+
/>
4460
</DialogContent>
4561
<DialogActions>
4662
<Button onClick={fClose}>Cancel</Button>

ui/src/tests/client.test.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,35 @@ const waitForClient =
2121
await waitForExists(page, $table.cell(row, ClientCol.Name), name);
2222
};
2323

24-
const updateClient =
25-
(id: number, data: {name?: string}): (() => Promise<void>) =>
24+
interface ClientFields {
25+
name?: string;
26+
expiresAfter?: number;
27+
}
28+
29+
const fillClientDialog =
30+
(opener: string, submit: string, data: ClientFields): (() => Promise<void>) =>
2631
async () => {
27-
await page.click($table.cell(id, ClientCol.Edit, '.edit'));
32+
await page.click(opener);
2833
await page.waitForSelector($dialog.selector());
29-
if (data.name) {
34+
if (data.name !== undefined) {
3035
const nameSelector = $dialog.input('.name');
3136
await clearField(page, nameSelector);
3237
await page.type(nameSelector, data.name);
3338
}
34-
await page.click($dialog.button('.update'));
39+
if (data.expiresAfter !== undefined) {
40+
const expiresSelector = $dialog.input('.expires-after');
41+
await clearField(page, expiresSelector);
42+
await page.type(expiresSelector, data.expiresAfter.toString());
43+
}
44+
await page.click($dialog.button(submit));
3545
await waitToDisappear(page, $dialog.selector());
3646
};
3747

48+
const createClient = (data: ClientFields) => fillClientDialog('#create-client', '.create', data);
49+
50+
const updateClient = (id: number, data: ClientFields) =>
51+
fillClientDialog($table.cell(id, ClientCol.Edit, '.edit'), '.update', data);
52+
3853
const $table = selector.table('#client-table');
3954
const $dialog = selector.form('#client-dialog');
4055

@@ -52,17 +67,8 @@ describe('Client', () => {
5267
expect(await count(page, $table.rows())).toBe(1);
5368
});
5469
describe('create clients', () => {
55-
const createClient =
56-
(name: string): (() => Promise<void>) =>
57-
async () => {
58-
await page.click('#create-client');
59-
await page.waitForSelector($dialog.selector());
60-
await page.type($dialog.input('.name'), name);
61-
await page.click($dialog.button('.create'));
62-
await waitToDisappear(page, $dialog.selector());
63-
};
64-
it('phone', createClient('phone'));
65-
it('desktop app', createClient('desktop app'));
70+
it('phone', createClient({name: 'phone'}));
71+
it('desktop app', createClient({name: 'desktop app', expiresAfter: 60 * 60}));
6672
});
6773
it('has created clients', async () => {
6874
await page.waitForSelector($table.row(3));
@@ -73,8 +79,15 @@ describe('Client', () => {
7379
expect(await innerText(page, $table.cell(2, ClientCol.Name))).toBe('phone');
7480
expect(await innerText(page, $table.cell(3, ClientCol.Name))).toBe('desktop app');
7581
});
76-
it('updates client', updateClient(1, {name: 'firefox'}));
82+
it('shows expires after for new clients', async () => {
83+
expect(await innerText(page, $table.cell(2, ClientCol.ExpiresIn))).toBe('-');
84+
expect(await innerText(page, $table.cell(3, ClientCol.ExpiresIn))).toBe('in 1 hour');
85+
});
86+
it('updates client', updateClient(1, {name: 'firefox', expiresAfter: 60 * 60 * 10}));
7787
it('has updated client name', waitForClient('firefox', 1));
88+
it('has updated expires after', async () => {
89+
expect(await innerText(page, $table.cell(1, ClientCol.ExpiresIn))).toBe('in 10 hours');
90+
});
7891
it('shows token', async () => {
7992
await page.click($table.cell(3, ClientCol.Token, '.toggle-visibility'));
8093
expect(

ui/src/tests/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export enum ClientCol {
7474
LastSeen = 3,
7575
ElevationEnds = 4,
7676
Created = 5,
77-
Elevate = 6,
78-
Edit = 7,
79-
Delete = 8,
77+
ExpiresIn = 6,
78+
Elevate = 7,
79+
Edit = 8,
80+
Delete = 9,
8081
}

ui/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface IClient {
1818
lastUsed: string | null;
1919
elevatedUntil?: string;
2020
createdAt: string;
21+
expiresAfterInactivitySeconds: number;
22+
expiresAt: string | null;
2123
}
2224

2325
export interface IPlugin {

0 commit comments

Comments
 (0)