mirror of
https://gitlab.com/comunic/comunicconsole
synced 2024-12-25 13:08:52 +00:00
Refactor code
This commit is contained in:
parent
69c68f43cb
commit
6c4427d06b
@ -17,6 +17,7 @@ export interface AdminAccount {
|
||||
name: string;
|
||||
email: string;
|
||||
time_create: number;
|
||||
roles: Array<"manage_admins" | "manage_users" | "access_full_admin_logs">;
|
||||
}
|
||||
|
||||
export interface NewAdminGeneralSettings {
|
||||
@ -32,7 +33,7 @@ export interface AdminResetToken {
|
||||
|
||||
export const SESSION_STORAGE_TOKEN = "auth_token";
|
||||
|
||||
let currentAccount: AdminAccount | null = null;
|
||||
let currentAccount: AdminAccount;
|
||||
|
||||
export class AccountHelper {
|
||||
/**
|
||||
|
@ -12,7 +12,7 @@ export interface AdminRole {
|
||||
description: string;
|
||||
}
|
||||
|
||||
let RolesList: AdminRole[] = [];
|
||||
export let RolesList: AdminRole[] = [];
|
||||
|
||||
export class AdminRolesHelper {
|
||||
/**
|
||||
|
93
src/ui/accountSettings/GeneralSettings.tsx
Normal file
93
src/ui/accountSettings/GeneralSettings.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Account general settings section
|
||||
*
|
||||
* @author Pierre Hubert
|
||||
*/
|
||||
|
||||
import { TextField, Button } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { AdminAccount, AccountHelper } from "../../helpers/AccountHelper";
|
||||
import { snackbar, matAlert } from "../widgets/DialogsProvider";
|
||||
import { SettingsSection } from "./SettingsSection";
|
||||
|
||||
export class GeneralSettings extends React.Component<
|
||||
{ admin: AdminAccount },
|
||||
{ newName: string; newEmail: string }
|
||||
> {
|
||||
constructor(p: any) {
|
||||
super(p);
|
||||
|
||||
this.state = {
|
||||
newName: this.props.admin.name,
|
||||
newEmail: this.props.admin.email,
|
||||
};
|
||||
|
||||
this.changedName = this.changedName.bind(this);
|
||||
this.changedEmail = this.changedEmail.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
changedName(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
this.setState({ newName: e.target.value });
|
||||
}
|
||||
|
||||
changedEmail(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
this.setState({ newEmail: e.target.value });
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this.state.newEmail.length > 2 && this.state.newName.length > 2;
|
||||
}
|
||||
|
||||
async handleSubmit() {
|
||||
try {
|
||||
if (!this.isValid) return;
|
||||
|
||||
await AccountHelper.UpdateGeneralSettings({
|
||||
id: this.props.admin.id,
|
||||
name: this.state.newName,
|
||||
email: this.state.newEmail,
|
||||
});
|
||||
|
||||
snackbar("Successfully updated admin settings!");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to update admin settings!");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SettingsSection title="General settings">
|
||||
<div style={{ margin: "10px" }}>
|
||||
<TextField
|
||||
required
|
||||
label="Name"
|
||||
value={this.state.newName}
|
||||
onChange={this.changedName}
|
||||
style={{ width: "100%", paddingBottom: "20px" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
required
|
||||
label="Email"
|
||||
value={this.state.newEmail}
|
||||
onChange={this.changedEmail}
|
||||
type="mail"
|
||||
style={{ width: "100%", paddingBottom: "20px" }}
|
||||
/>
|
||||
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<Button
|
||||
style={{ alignSelf: "end", marginRight: "10px" }}
|
||||
disabled={!this.isValid}
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
}
|
186
src/ui/accountSettings/KeySettingsSection.tsx
Normal file
186
src/ui/accountSettings/KeySettingsSection.tsx
Normal file
@ -0,0 +1,186 @@
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
IconButton,
|
||||
Divider,
|
||||
Button,
|
||||
} from "@material-ui/core";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import React from "react";
|
||||
import { AdminAccount, AccountHelper } from "../../helpers/AccountHelper";
|
||||
import { AdminAccountKey, AdminKeyHelper } from "../../helpers/AdminKeyHelper";
|
||||
import { CopyToClipboard } from "../../utils/ClipboardUtils";
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
import {
|
||||
matConfirm,
|
||||
snackbar,
|
||||
matAlert,
|
||||
input,
|
||||
} from "../widgets/DialogsProvider";
|
||||
import { TimestampWidget } from "../widgets/TimestampWidget";
|
||||
import { SettingsSection } from "./SettingsSection";
|
||||
|
||||
export class KeySettingsSection extends React.Component<
|
||||
{ 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.generateResetToken = this.generateResetToken.bind(this);
|
||||
this.registerNewKey = this.registerNewKey.bind(this);
|
||||
this.deleteKey = this.deleteKey.bind(this);
|
||||
}
|
||||
|
||||
async load() {
|
||||
const keys = await AdminKeyHelper.GetAdminKeys(this.props.admin.id);
|
||||
|
||||
this.setState({ keys: keys });
|
||||
}
|
||||
|
||||
async generateResetToken() {
|
||||
try {
|
||||
if (
|
||||
!(await matConfirm(
|
||||
"Do you really want to generate a reset token for this account?"
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
const token = await AccountHelper.GenerateResetToken(
|
||||
this.props.admin.id
|
||||
);
|
||||
|
||||
CopyToClipboard(token.token);
|
||||
snackbar("Reset token was successfully copied to the clipboard!");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to generate a token!");
|
||||
}
|
||||
}
|
||||
|
||||
async registerNewKey() {
|
||||
try {
|
||||
const challenge =
|
||||
await AdminKeyHelper.GetKeyRegistrationChallenge();
|
||||
const credential = await navigator.credentials.create(challenge);
|
||||
|
||||
if (credential == null) throw new Error("Operation aborted!");
|
||||
|
||||
const name = await input({
|
||||
label: "Key name",
|
||||
maxLength: 40,
|
||||
minLength: 2,
|
||||
});
|
||||
|
||||
await AdminKeyHelper.RegisterKey(name, credential);
|
||||
|
||||
snackbar("Successfully enrolled a new key!");
|
||||
|
||||
this.setState({ counter: this.state.counter + 1 });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to register a new key!");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteKey(key: AdminAccountKey) {
|
||||
try {
|
||||
if (
|
||||
!(await matConfirm(
|
||||
"Do you really want to delete the key '" + key.name + "' ?"
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
await AdminKeyHelper.DeleteAuthKey(this.props.admin.id, key.id);
|
||||
|
||||
snackbar("The key was successfully deleted!");
|
||||
this.setState({ counter: this.state.counter + 1 });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to delete key!");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AsyncWidget
|
||||
errorMessage="Failed to load admin keys!"
|
||||
load={this.load}
|
||||
onBuild={this.build}
|
||||
key={this.props.admin.id + "-" + this.state.counter}
|
||||
></AsyncWidget>
|
||||
);
|
||||
}
|
||||
|
||||
build() {
|
||||
return (
|
||||
<SettingsSection title="Security keys">
|
||||
<Table aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Key name</TableCell>
|
||||
<TableCell align="right">Date added</TableCell>
|
||||
<TableCell align="right"></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">
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
size="small"
|
||||
onClick={() => this.deleteKey(key)}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Action buttons */}
|
||||
<div
|
||||
style={{
|
||||
textAlign: "right",
|
||||
margin: "5px 10px",
|
||||
}}
|
||||
>
|
||||
<Button onClick={this.generateResetToken}>
|
||||
New reset token
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
this.props.admin.id !==
|
||||
AccountHelper.currentAccount.id
|
||||
}
|
||||
onClick={this.registerNewKey}
|
||||
>
|
||||
Register a new key
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
}
|
26
src/ui/accountSettings/SettingsSection.tsx
Normal file
26
src/ui/accountSettings/SettingsSection.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Grid, Paper, Typography, Divider } from "@material-ui/core";
|
||||
import React from "react";
|
||||
|
||||
export function SettingsSection(p: {
|
||||
title: string;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Grid item sm={6}>
|
||||
<Paper>
|
||||
<Typography variant="h6" style={{ padding: "10px 15px " }}>
|
||||
{p.title}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</div>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -4,35 +4,14 @@
|
||||
* @author Pierre Hubert
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper";
|
||||
import { AdminAccountKey, AdminKeyHelper } from "../../helpers/AdminKeyHelper";
|
||||
import { CopyToClipboard } from "../../utils/ClipboardUtils";
|
||||
import { GeneralSettings } from "../accountSettings/GeneralSettings";
|
||||
import { KeySettingsSection } from "../accountSettings/KeySettingsSection";
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
import {
|
||||
input,
|
||||
matAlert,
|
||||
matConfirm,
|
||||
snackbar,
|
||||
} from "../widgets/DialogsProvider";
|
||||
import { PageTitle } from "../widgets/PageTitle";
|
||||
import { TimestampWidget } from "../widgets/TimestampWidget";
|
||||
|
||||
export function AccountSettingsRoute() {
|
||||
let params: any = useParams();
|
||||
@ -93,268 +72,3 @@ class AccountSettingsRouteInner extends React.Component<
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralSettings extends React.Component<
|
||||
{ admin: AdminAccount },
|
||||
{ newName: string; newEmail: string }
|
||||
> {
|
||||
constructor(p: any) {
|
||||
super(p);
|
||||
|
||||
this.state = {
|
||||
newName: this.props.admin.name,
|
||||
newEmail: this.props.admin.email,
|
||||
};
|
||||
|
||||
this.changedName = this.changedName.bind(this);
|
||||
this.changedEmail = this.changedEmail.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
changedName(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
this.setState({ newName: e.target.value });
|
||||
}
|
||||
|
||||
changedEmail(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
this.setState({ newEmail: e.target.value });
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this.state.newEmail.length > 2 && this.state.newName.length > 2;
|
||||
}
|
||||
|
||||
async handleSubmit() {
|
||||
try {
|
||||
if (!this.isValid) return;
|
||||
|
||||
await AccountHelper.UpdateGeneralSettings({
|
||||
id: this.props.admin.id,
|
||||
name: this.state.newName,
|
||||
email: this.state.newEmail,
|
||||
});
|
||||
|
||||
snackbar("Successfully updated admin settings!");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to update admin settings!");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SettingsSection title="General settings">
|
||||
<div style={{ margin: "10px" }}>
|
||||
<TextField
|
||||
required
|
||||
label="Name"
|
||||
value={this.state.newName}
|
||||
onChange={this.changedName}
|
||||
style={{ width: "100%", paddingBottom: "20px" }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
required
|
||||
label="Email"
|
||||
value={this.state.newEmail}
|
||||
onChange={this.changedEmail}
|
||||
type="mail"
|
||||
style={{ width: "100%", paddingBottom: "20px" }}
|
||||
/>
|
||||
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<Button
|
||||
style={{ alignSelf: "end", marginRight: "10px" }}
|
||||
disabled={!this.isValid}
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class KeySettingsSection extends React.Component<
|
||||
{ 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.generateResetToken = this.generateResetToken.bind(this);
|
||||
this.registerNewKey = this.registerNewKey.bind(this);
|
||||
this.deleteKey = this.deleteKey.bind(this);
|
||||
}
|
||||
|
||||
async load() {
|
||||
const keys = await AdminKeyHelper.GetAdminKeys(this.props.admin.id);
|
||||
|
||||
this.setState({ keys: keys });
|
||||
}
|
||||
|
||||
async generateResetToken() {
|
||||
try {
|
||||
if (
|
||||
!(await matConfirm(
|
||||
"Do you really want to generate a reset token for this account?"
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
const token = await AccountHelper.GenerateResetToken(
|
||||
this.props.admin.id
|
||||
);
|
||||
|
||||
CopyToClipboard(token.token);
|
||||
snackbar("Reset token was successfully copied to the clipboard!");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to generate a token!");
|
||||
}
|
||||
}
|
||||
|
||||
async registerNewKey() {
|
||||
try {
|
||||
const challenge =
|
||||
await AdminKeyHelper.GetKeyRegistrationChallenge();
|
||||
const credential = await navigator.credentials.create(challenge);
|
||||
|
||||
if (credential == null) throw new Error("Operation aborted!");
|
||||
|
||||
const name = await input({
|
||||
label: "Key name",
|
||||
maxLength: 40,
|
||||
minLength: 2,
|
||||
});
|
||||
|
||||
await AdminKeyHelper.RegisterKey(name, credential);
|
||||
|
||||
snackbar("Successfully enrolled a new key!");
|
||||
|
||||
this.setState({ counter: this.state.counter + 1 });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to register a new key!");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteKey(key: AdminAccountKey) {
|
||||
try {
|
||||
if (
|
||||
!(await matConfirm(
|
||||
"Do you really want to delete the key '" + key.name + "' ?"
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
await AdminKeyHelper.DeleteAuthKey(this.props.admin.id, key.id);
|
||||
|
||||
snackbar("The key was successfully deleted!");
|
||||
this.setState({ counter: this.state.counter + 1 });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
matAlert("Failed to delete key!");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AsyncWidget
|
||||
errorMessage="Failed to load admin keys!"
|
||||
load={this.load}
|
||||
onBuild={this.build}
|
||||
key={this.props.admin.id + "-" + this.state.counter}
|
||||
></AsyncWidget>
|
||||
);
|
||||
}
|
||||
|
||||
build() {
|
||||
return (
|
||||
<SettingsSection title="Security keys">
|
||||
<Table aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Key name</TableCell>
|
||||
<TableCell align="right">Date added</TableCell>
|
||||
<TableCell align="right"></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">
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
size="small"
|
||||
onClick={() => this.deleteKey(key)}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Action buttons */}
|
||||
<div
|
||||
style={{
|
||||
textAlign: "right",
|
||||
margin: "5px 10px",
|
||||
}}
|
||||
>
|
||||
<Button onClick={this.generateResetToken}>
|
||||
New reset token
|
||||
</Button>
|
||||
<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 }) {
|
||||
return (
|
||||
<Grid item sm={6}>
|
||||
<Paper>
|
||||
<Typography variant="h6" style={{ padding: "10px 15px " }}>
|
||||
{p.title}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</div>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
11
src/utils/AccountUtils.ts
Normal file
11
src/utils/AccountUtils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Account utilities
|
||||
*
|
||||
* @author Pierre Hubert
|
||||
*/
|
||||
|
||||
import { AccountHelper } from "../helpers/AccountHelper";
|
||||
|
||||
export function canManageAdmins(): boolean {
|
||||
return AccountHelper.currentAccount.roles.includes("manage_admins");
|
||||
}
|
Loading…
Reference in New Issue
Block a user