Display the list of registered keys

This commit is contained in:
Pierre HUBERT 2021-05-14 14:47:13 +02:00
parent 41d568e493
commit 7ac554e90e
4 changed files with 191 additions and 43 deletions

View File

@ -33,6 +33,12 @@ export interface NewAdminGeneralSettings {
email: string; email: string;
} }
export interface AdminAccountKey {
id: number;
name: string;
time_add: number;
}
const SESSION_STORAGE_TOKEN = "auth_token"; const SESSION_STORAGE_TOKEN = "auth_token";
let currentAccount: AdminAccount | null = null; let currentAccount: AdminAccount | null = null;
@ -248,4 +254,15 @@ export class AccountHelper {
sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token); sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token);
} }
/**
* Get the list of keys of an admin
*
* @param adminID The id of the target administrator
*/
static async GetAdminKeys(adminID: number): Promise<AdminAccountKey[]> {
return await serverRequest("accounts/keys", {
id: adminID,
});
}
} }

View File

@ -9,15 +9,25 @@ import {
Divider, Divider,
Grid, Grid,
Paper, Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField, TextField,
Typography, Typography,
} from "@material-ui/core"; } from "@material-ui/core";
import React from "react"; import React from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper"; import {
AccountHelper,
AdminAccount,
AdminAccountKey,
} from "../../helpers/AccountHelper";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { input, matAlert, snackbar } from "../widgets/DialogsProvider"; import { input, matAlert, snackbar } from "../widgets/DialogsProvider";
import { PageTitle } from "../widgets/PageTitle"; import { PageTitle } from "../widgets/PageTitle";
import { TimestampWidget } from "../widgets/TimestampWidget";
export function AccountSettingsRoute() { export function AccountSettingsRoute() {
let params: any = useParams(); let params: any = useParams();
@ -70,7 +80,9 @@ class AccountSettingsRouteInner extends React.Component<
admin={this.state.account} admin={this.state.account}
></GeneralSettings> ></GeneralSettings>
<KeySettingsSection></KeySettingsSection> <KeySettingsSection
admin={this.state.account}
></KeySettingsSection>
</Grid> </Grid>
</div> </div>
); );
@ -126,37 +138,63 @@ class GeneralSettings extends React.Component<
render() { render() {
return ( return (
<SettingsSection title="General settings"> <SettingsSection title="General settings">
<TextField <div style={{ margin: "10px" }}>
required <TextField
label="Name" required
value={this.state.newName} label="Name"
onChange={this.changedName} value={this.state.newName}
style={{ width: "100%", paddingBottom: "20px" }} onChange={this.changedName}
/> style={{ width: "100%", paddingBottom: "20px" }}
/>
<TextField <TextField
required required
label="Email" label="Email"
value={this.state.newEmail} value={this.state.newEmail}
onChange={this.changedEmail} onChange={this.changedEmail}
type="mail" type="mail"
style={{ width: "100%", paddingBottom: "20px" }} style={{ width: "100%", paddingBottom: "20px" }}
/> />
<Button <div style={{ textAlign: "right" }}>
style={{ alignSelf: "end", marginRight: "10px" }} <Button
disabled={!this.isValid} style={{ alignSelf: "end", marginRight: "10px" }}
onClick={this.handleSubmit} disabled={!this.isValid}
> onClick={this.handleSubmit}
Update >
</Button> Update
</Button>
</div>
</div>
</SettingsSection> </SettingsSection>
); );
} }
} }
function KeySettingsSection() { export class KeySettingsSection extends React.Component<
const registerNewKey = async () => { { admin: AdminAccount },
{ keys: AdminAccountKey[]; counter: number }
> {
constructor(props: any) {
super(props);
this.state = {
keys: [],
counter: 1,
};
this.load = this.load.bind(this);
this.build = this.build.bind(this);
this.registerNewKey = this.registerNewKey.bind(this);
}
async load() {
const keys = await AccountHelper.GetAdminKeys(this.props.admin.id);
this.setState({ keys: keys });
}
async registerNewKey() {
try { try {
const challenge = await AccountHelper.GetKeyRegistrationChallenge(); const challenge = await AccountHelper.GetKeyRegistrationChallenge();
const credential = await navigator.credentials.create(challenge); const credential = await navigator.credentials.create(challenge);
@ -176,32 +214,79 @@ function KeySettingsSection() {
console.error(e); console.error(e);
matAlert("Failed to register a new key!"); matAlert("Failed to register a new key!");
} }
}; }
return ( render() {
<SettingsSection title="Key setttings"> return (
<Button <AsyncWidget
style={{ alignSelf: "end", marginRight: "10px" }} errorMessage="Failed to load admin keys!"
disabled={false /* TODO : adapt if other admin*/} load={this.load}
onClick={registerNewKey} onBuild={this.build}
> key={this.props.admin.id + "-" + this.state.counter}
Register a new key ></AsyncWidget>
</Button> );
</SettingsSection> }
);
build() {
return (
<SettingsSection title="Key setttings">
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Key name</TableCell>
<TableCell align="right">Date added</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.keys.map((key) => (
<TableRow key={key.id}>
<TableCell component="th" scope="row">
{key.name}
</TableCell>
<TableCell align="right">
<TimestampWidget time={key.time_add} />
</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Divider />
{/* Action buttons */}
<div
style={{
textAlign: "right",
margin: "5px 10px",
}}
>
<Button
disabled={
this.props.admin.id !==
AccountHelper.currentAccount.id
}
onClick={this.registerNewKey}
>
Register a new key
</Button>
</div>
</SettingsSection>
);
}
} }
function SettingsSection(p: { title: string; children?: React.ReactNode }) { function SettingsSection(p: { title: string; children?: React.ReactNode }) {
return ( return (
<Grid item sm={6} spacing={2}> <Grid item sm={6}>
<Paper> <Paper>
<Typography variant="h5" style={{ padding: "10px 10px " }}> <Typography variant="h5" style={{ padding: "10px 10px " }}>
General settings {p.title}
</Typography> </Typography>
<Divider /> <Divider />
<div <div
style={{ style={{
padding: "10px 10px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
}} }}

View File

@ -15,7 +15,7 @@ enum Status {
} }
export interface AsyncWidgetProperties { export interface AsyncWidgetProperties {
key?: number; key?: number | string;
load: () => Promise<void>; load: () => Promise<void>;
errorMessage: string; errorMessage: string;
onBuild: () => ReactNode; onBuild: () => ReactNode;
@ -23,7 +23,7 @@ export interface AsyncWidgetProperties {
interface AsyncWidgetState { interface AsyncWidgetState {
status: Status; status: Status;
key?: number; key?: number | string;
} }
export class AsyncWidget extends React.Component< export class AsyncWidget extends React.Component<

View File

@ -0,0 +1,46 @@
import { Tooltip } from "@material-ui/core";
import React from "react";
/**
* Display in human-readable way a timestamp
*/
export function TimestampWidget(props: { time: number }) {
const date = new Date(props.time * 1000);
const currDate = new Date();
const diff = Math.floor(currDate.getTime() / 1000 - props.time);
let diffStr = diff + " seconds ago";
if (diff === 0) diffStr = "Just now";
else if (diff === 1) diffStr = "1 second ago";
else if (diff === 60) diffStr = "1 minute ago";
if (diff > 60) diffStr = Math.floor(diff / 60) + " minutes ago";
if (diff === 3600) diffStr = "1 hour ago";
if (diff > 3600) diffStr = Math.floor(diff / 3600) + " hours ago";
const daysAgo = Math.floor(diff / (3600 * 24));
if (daysAgo === 1) diffStr = "1 day ago";
else if (daysAgo > 1) diffStr = daysAgo + " days ago";
const monthsAgo = Math.floor(daysAgo / 30);
if (monthsAgo === 1) diffStr = "1 month ago";
else if (monthsAgo > 1) diffStr = monthsAgo + " months ago";
const yearsAgo = Math.floor(monthsAgo / 12);
if (yearsAgo === 1) diffStr = "1 year ago";
else if (yearsAgo > 1) diffStr = yearsAgo + " years ago";
return (
<Tooltip
disableFocusListener
title={date.toString()}
placement="top"
arrow
>
<span>{diffStr}</span>
</Tooltip>
);
}