feat: display elevation in the clients page

This commit is contained in:
Jannis Mattheis
2026-04-12 20:31:47 +02:00
parent 624ab65742
commit 96116dbfe4
5 changed files with 130 additions and 3 deletions

View File

@@ -38,4 +38,11 @@ export class ClientStore extends BaseStore<IClient> {
await this.createNoNotifcation(name);
this.snack('Client added');
};
@action
public elevate = async (id: number, durationSeconds: number): Promise<void> => {
await axios.post(`${config.get('url')}client:elevate`, {id, durationSeconds});
await this.refresh();
this.snack('Client elevated');
};
}

View File

@@ -9,14 +9,19 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Delete from '@mui/icons-material/Delete';
import Edit from '@mui/icons-material/Edit';
import Security from '@mui/icons-material/Security';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import TimeAgo from 'react-timeago';
import ConfirmDialog from '../common/ConfirmDialog';
import DefaultPage from '../common/DefaultPage';
import AddClientDialog from './AddClientDialog';
import UpdateClientDialog from './UpdateClientDialog';
import ElevateClientDialog from './ElevateClientDialog';
import {IClient} from '../types';
import CopyableSecret from '../common/CopyableSecret';
import {LastUsedCell} from '../common/LastUsedCell';
import {TimeAgoFormatter} from '../common/TimeAgoFormatter';
import {observer} from 'mobx-react-lite';
import {useStores} from '../stores';
@@ -24,6 +29,7 @@ const Clients = observer(() => {
const {clientStore} = useStores();
const [toDeleteClient, setToDeleteClient] = useState<IClient>();
const [toUpdateClient, setToUpdateClient] = useState<IClient>();
const [toElevateClient, setToElevateClient] = useState<IClient>();
const [createDialog, setCreateDialog] = useState<boolean>(false);
const clients = clientStore.getItems();
@@ -32,6 +38,7 @@ const Clients = observer(() => {
return (
<DefaultPage
title="Clients"
maxWidth={1000}
rightControl={
<Button
id="create-client"
@@ -49,6 +56,8 @@ const Clients = observer(() => {
<TableCell>Name</TableCell>
<TableCell style={{width: 200}}>Token</TableCell>
<TableCell>Last Used</TableCell>
<TableCell>Elevation ends</TableCell>
<TableCell />
<TableCell />
<TableCell />
</TableRow>
@@ -60,8 +69,10 @@ const Clients = observer(() => {
name={client.name}
value={client.token}
lastUsed={client.lastUsed}
elevatedUntil={client.elevatedUntil}
fEdit={() => setToUpdateClient(client)}
fDelete={() => setToDeleteClient(client)}
fElevate={() => setToElevateClient(client)}
/>
))}
</TableBody>
@@ -90,6 +101,13 @@ const Clients = observer(() => {
requireElevated
/>
)}
{toElevateClient != null && (
<ElevateClientDialog
clientName={toElevateClient.name}
clientId={toElevateClient.id}
fClose={() => setToElevateClient(undefined)}
/>
)}
</DefaultPage>
);
});
@@ -98,11 +116,13 @@ interface IRowProps {
name: string;
value: string;
lastUsed: string | null;
elevatedUntil?: string;
fEdit: VoidFunction;
fDelete: VoidFunction;
fElevate: VoidFunction;
}
const Row = ({name, value, lastUsed, fEdit, fDelete}: IRowProps) => (
const Row = ({name, value, lastUsed, elevatedUntil, fEdit, fDelete, fElevate}: IRowProps) => (
<TableRow>
<TableCell>{name}</TableCell>
<TableCell>
@@ -114,6 +134,20 @@ const Row = ({name, value, lastUsed, fEdit, fDelete}: IRowProps) => (
<TableCell>
<LastUsedCell lastUsed={lastUsed} />
</TableCell>
<TableCell>
{elevatedUntil && Date.parse(elevatedUntil) > Date.now() ? (
<TimeAgo date={elevatedUntil} formatter={TimeAgoFormatter.long} />
) : (
'-'
)}
</TableCell>
<TableCell align="right" padding="none">
<Tooltip title="Elevate">
<IconButton onClick={fElevate} className="elevate">
<Security />
</IconButton>
</Tooltip>
</TableCell>
<TableCell align="right" padding="none">
<IconButton onClick={fEdit} className="edit">
<Edit />

View File

@@ -0,0 +1,83 @@
import React, {useState} from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import {observer} from 'mobx-react-lite';
import {useStores} from '../stores';
import ElevationForm from '../common/ElevationForm';
interface IProps {
clientName: string;
clientId: number;
fClose: VoidFunction;
}
const durationOptions = [
{label: 'Cancel elevation', seconds: -1},
{label: '1 hour', seconds: 60 * 60},
{label: '1 day', seconds: 24 * 60 * 60},
{label: '30 days', seconds: 30 * 24 * 60 * 60},
{label: '1 year', seconds: 365 * 24 * 60 * 60},
];
const ElevateClientDialog = observer(({clientName, clientId, fClose}: IProps) => {
const {elevateStore, clientStore, currentUser} = useStores();
const [durationSeconds, setDurationSeconds] = useState(durationOptions[1].seconds);
const needsElevation = !elevateStore.elevated;
const handleConfirm = async () => {
await clientStore.elevate(clientId, durationSeconds);
if (clientId === currentUser.user.clientId) {
currentUser.tryAuthenticate();
}
fClose();
};
const handleClose = () => {
elevateStore.cleanupOidcElevate();
fClose();
};
return (
<Dialog open={true} onClose={handleClose} aria-labelledby="elevate-client-dialog-title">
<DialogTitle id="elevate-client-dialog-title">Elevate Client: {clientName}</DialogTitle>
<DialogContent>
{needsElevation ? (
<ElevationForm />
) : (
<FormControl fullWidth style={{marginTop: 8}}>
<InputLabel id="elevate-duration-label">Duration</InputLabel>
<Select
labelId="elevate-duration-label"
label="Duration"
value={durationSeconds}
onChange={(e) => setDurationSeconds(e.target.value as number)}>
{durationOptions.map((opt) => (
<MenuItem key={opt.seconds} value={opt.seconds}>
{opt.label}
</MenuItem>
))}
</Select>
</FormControl>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
{!needsElevation && (
<Button onClick={handleConfirm} autoFocus color="primary" variant="contained">
Elevate
</Button>
)}
</DialogActions>
</Dialog>
);
});
export default ElevateClientDialog;

View File

@@ -19,8 +19,10 @@ enum Col {
Name = 1,
Token = 2,
LastSeen = 3,
Edit = 4,
Delete = 5,
ElevationEnds = 4,
Elevate = 5,
Edit = 6,
Delete = 7,
}
const waitForClient =

View File

@@ -15,6 +15,7 @@ export interface IClient {
token: string;
name: string;
lastUsed: string | null;
elevatedUntil?: string;
}
export interface IPlugin {