Display admin actions

This commit is contained in:
Pierre HUBERT 2021-07-11 15:07:57 +02:00
parent 54c9b84945
commit 0cd6de200d
6 changed files with 229 additions and 2 deletions

42
package-lock.json generated
View File

@ -1788,6 +1788,35 @@
"react-transition-group": "^4.4.0"
}
},
"@material-ui/data-grid": {
"version": "4.0.0-alpha.26",
"resolved": "https://registry.npmjs.org/@material-ui/data-grid/-/data-grid-4.0.0-alpha.26.tgz",
"integrity": "sha512-mA2mncqQAISRLl7FR5NSmlus0wLQ5MSbIjg7WJyGKoOojNwEhK46nmYPvukljXMRoaKQpiv4U+ULI5CuuxlXgQ==",
"requires": {
"@material-ui/utils": "^5.0.0-alpha.14",
"prop-types": "^15.7.2",
"reselect": "^4.0.0"
},
"dependencies": {
"@material-ui/utils": {
"version": "5.0.0-alpha.31",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-5.0.0-alpha.31.tgz",
"integrity": "sha512-4OzVD12+HbfWMftwiHCBforgjkhzbWMdK9GTQLQcekjdG2qpi41BGvanPpHjlxegzou0A2MEaULBvWqsKrUP9A==",
"requires": {
"@babel/runtime": "^7.4.4",
"@types/prop-types": "^15.7.3",
"@types/react-is": "^16.7.1 || ^17.0.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.0"
}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"@material-ui/icons": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz",
@ -2432,6 +2461,14 @@
"@types/react": "*"
}
},
"@types/react-is": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.0.tgz",
"integrity": "sha512-A0DQ1YWZ0RG2+PV7neAotNCIh8gZ3z7tQnDJyS2xRPDNtAtSPcJ9YyfMP8be36Ha0kQRzbZCrrTMznA4blqO5g==",
"requires": {
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.14",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz",
@ -13187,6 +13224,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resolve": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",

View File

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.4",
"@material-ui/data-grid": "^4.0.0-alpha.26",
"@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.6",

View File

@ -17,7 +17,7 @@ export interface AdminAccount {
name: string;
email: string;
time_create: number;
roles: Array<"manage_admins" | "manage_users" | "access_full_admin_logs">;
roles: Array<"manage_admins" | "manage_users" | "access_all_admin_logs">;
}
export interface NewAdminGeneralSettings {

View File

@ -0,0 +1,30 @@
import { serverRequest } from "./APIHelper";
export interface AdminLogMessage {
id: number;
admin_id: number;
ip: string;
time: number;
action: string;
args: any;
format: string;
}
export class AdminLogsHelper {
/**
* Get the list of admin log actions from the server
*/
static async GetLogs(): Promise<AdminLogMessage[]> {
return (await serverRequest("logs/list")).map((el: any) => {
if (typeof el.action === "string") return el;
for (let key in el.action) {
if (!el.action.hasOwnProperty(key)) continue;
el.args = el.action[key];
el.action = key;
}
return el;
});
}
}

View File

@ -0,0 +1,143 @@
import {
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@material-ui/core";
import React from "react";
import { AccountHelper, AdminAccount } from "../../helpers/AccountHelper";
import {
AdminLogMessage,
AdminLogsHelper,
} from "../../helpers/AdminLogsHelper";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { PageTitle } from "../widgets/PageTitle";
import { TimestampWidget } from "../widgets/TimestampWidget";
/**
* View admin logs
*
* @author Pierre Hubert
*/
export class AccountLogsRoute extends React.Component<
{},
{ logs: AdminLogMessage[]; admins: AdminAccount[] }
> {
constructor(p: any) {
super(p);
this.load = this.load.bind(this);
this.build = this.build.bind(this);
}
async load() {
const admins = await AccountHelper.GetAdminsList();
const logs = await AdminLogsHelper.GetLogs();
this.setState({
admins: admins,
logs: logs,
});
}
getAdminName(id: number): string {
const admin = this.state.admins.find((a) => a.id === id);
return admin ? admin.name : "Unknown admin";
}
build() {
return (
<div>
<PageTitle name="Admin activity records" />
<TableContainer component={Paper}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Admin</TableCell>
<TableCell align="right">Time</TableCell>
<TableCell align="right">IP address</TableCell>
<TableCell align="right">Action</TableCell>
<TableCell align="right">Details</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.logs.reverse().map((message) => {
let formattedMessage = message.format;
for (let arg in message.args) {
if (message.args.hasOwnProperty(arg))
formattedMessage =
formattedMessage.replace(
"{" + arg + "}",
message.args[arg]
);
}
// Handle [admin] tag
const regex: any =
"\\[admin\\][0-9]+\\[/admin\\]";
const adminsReferences = Array.from(
formattedMessage.matchAll(regex)
);
for (let entry of adminsReferences) {
const pattern = entry[0];
const adminId = Number(
entry[0].split("]")[1].split("[")[0]
);
const adminName =
this.getAdminName(adminId);
formattedMessage = formattedMessage.replace(
pattern,
adminName
);
}
return (
<TableRow key={message.id} hover>
<TableCell>
{this.getAdminName(
message.admin_id
)}
</TableCell>
<TableCell align="right">
<TimestampWidget
time={message.time}
/>
</TableCell>
<TableCell align="right">
{message.ip}
</TableCell>
<TableCell align="right">
{message.action}
</TableCell>
<TableCell align="right">
{formattedMessage}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<p style={{ color: "white" }}>
Note: your old activity records are automatically deleted
after a period of time.
</p>
</div>
);
}
render() {
return (
<AsyncWidget
errorMessage="Failed to retrieve admin logs!"
onBuild={this.build}
load={this.load}
/>
);
}
}

View File

@ -20,6 +20,7 @@ import {
import { Home, Person } from "@material-ui/icons";
import GroupIcon from "@material-ui/icons/Group";
import CloseSharpIcon from "@material-ui/icons/CloseSharp";
import HistoryIcon from "@material-ui/icons/History";
import React from "react";
import {
BrowserRouter as Router,
@ -33,6 +34,7 @@ import { AccountSettingsRoute } from "./AccountSettingsRoute";
import { HomeRoute } from "./HomeRoute";
import { NotFoundRoute } from "./NotFoundRoute";
import { AccountsListRoute } from "./AccountsListRoute";
import { AccountLogsRoute } from "./AccountLogsRoute";
const useStyles = makeStyles((theme) => ({
root: {
@ -93,6 +95,11 @@ function Menu() {
icon={<GroupIcon />}
uri="/accounts"
/>
<MainMenuItem
title="Admin Logs"
icon={<HistoryIcon />}
uri="/logs"
/>
<Divider />
<MainMenuItem
title="My account"
@ -201,7 +208,11 @@ export function MainRoute() {
</Route>
<Route path="/accounts/:id">
<AccountSettingsRoute></AccountSettingsRoute>
<AccountSettingsRoute />
</Route>
<Route path="/logs">
<AccountLogsRoute />
</Route>
<Route path="*">