mirror of
https://github.com/gotify/server.git
synced 2026-05-07 13:56:55 +08:00
feat: display elevation in the clients page
This commit is contained in:
@@ -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');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
83
ui/src/client/ElevateClientDialog.tsx
Normal file
83
ui/src/client/ElevateClientDialog.tsx
Normal 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;
|
||||
@@ -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 =
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface IClient {
|
||||
token: string;
|
||||
name: string;
|
||||
lastUsed: string | null;
|
||||
elevatedUntil?: string;
|
||||
}
|
||||
|
||||
export interface IPlugin {
|
||||
|
||||
Reference in New Issue
Block a user