mirror of
https://gitlab.com/comunic/comunicconsole
synced 2024-11-23 13:59:23 +00:00
Display admin actions
This commit is contained in:
parent
54c9b84945
commit
0cd6de200d
42
package-lock.json
generated
42
package-lock.json
generated
@ -1788,6 +1788,35 @@
|
|||||||
"react-transition-group": "^4.4.0"
|
"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": {
|
"@material-ui/icons": {
|
||||||
"version": "4.11.2",
|
"version": "4.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz",
|
||||||
@ -2432,6 +2461,14 @@
|
|||||||
"@types/react": "*"
|
"@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": {
|
"@types/react-router": {
|
||||||
"version": "5.1.14",
|
"version": "5.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.14.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.18.1",
|
"version": "1.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.11.4",
|
"@material-ui/core": "^4.11.4",
|
||||||
|
"@material-ui/data-grid": "^4.0.0-alpha.26",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@testing-library/jest-dom": "^5.12.0",
|
"@testing-library/jest-dom": "^5.12.0",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/react": "^11.2.6",
|
||||||
|
@ -17,7 +17,7 @@ export interface AdminAccount {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
time_create: number;
|
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 {
|
export interface NewAdminGeneralSettings {
|
||||||
|
30
src/helpers/AdminLogsHelper.ts
Normal file
30
src/helpers/AdminLogsHelper.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
143
src/ui/routes/AccountLogsRoute.tsx
Normal file
143
src/ui/routes/AccountLogsRoute.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import {
|
|||||||
import { Home, Person } from "@material-ui/icons";
|
import { Home, Person } from "@material-ui/icons";
|
||||||
import GroupIcon from "@material-ui/icons/Group";
|
import GroupIcon from "@material-ui/icons/Group";
|
||||||
import CloseSharpIcon from "@material-ui/icons/CloseSharp";
|
import CloseSharpIcon from "@material-ui/icons/CloseSharp";
|
||||||
|
import HistoryIcon from "@material-ui/icons/History";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router,
|
BrowserRouter as Router,
|
||||||
@ -33,6 +34,7 @@ import { AccountSettingsRoute } from "./AccountSettingsRoute";
|
|||||||
import { HomeRoute } from "./HomeRoute";
|
import { HomeRoute } from "./HomeRoute";
|
||||||
import { NotFoundRoute } from "./NotFoundRoute";
|
import { NotFoundRoute } from "./NotFoundRoute";
|
||||||
import { AccountsListRoute } from "./AccountsListRoute";
|
import { AccountsListRoute } from "./AccountsListRoute";
|
||||||
|
import { AccountLogsRoute } from "./AccountLogsRoute";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@ -93,6 +95,11 @@ function Menu() {
|
|||||||
icon={<GroupIcon />}
|
icon={<GroupIcon />}
|
||||||
uri="/accounts"
|
uri="/accounts"
|
||||||
/>
|
/>
|
||||||
|
<MainMenuItem
|
||||||
|
title="Admin Logs"
|
||||||
|
icon={<HistoryIcon />}
|
||||||
|
uri="/logs"
|
||||||
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<MainMenuItem
|
<MainMenuItem
|
||||||
title="My account"
|
title="My account"
|
||||||
@ -201,7 +208,11 @@ export function MainRoute() {
|
|||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/accounts/:id">
|
<Route path="/accounts/:id">
|
||||||
<AccountSettingsRoute></AccountSettingsRoute>
|
<AccountSettingsRoute />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/logs">
|
||||||
|
<AccountLogsRoute />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*">
|
<Route path="*">
|
||||||
|
Loading…
Reference in New Issue
Block a user