system log + frontend update
This commit is contained in:
@@ -12,6 +12,7 @@ import './res/adminlte.min.css';
|
||||
|
||||
// views
|
||||
import View404 from "./views/404";
|
||||
import LogView from "./views/log-view";
|
||||
const Overview = lazy(() => import('./views/overview'));
|
||||
const UserListView = lazy(() => import('./views/user/user-list'));
|
||||
const UserEditView = lazy(() => import('./views/user/user-edit'));
|
||||
@@ -70,6 +71,7 @@ export default function AdminDashboard(props) {
|
||||
<Route path={"/admin/user/:userId"} element={<UserEditView {...controlObj} />}/>
|
||||
<Route path={"/admin/groups"} element={<GroupListView {...controlObj} />}/>
|
||||
<Route path={"/admin/group/:groupId"} element={<EditGroupView {...controlObj} />}/>
|
||||
<Route path={"/admin/logs"} element={<LogView {...controlObj} />}/>
|
||||
<Route path={"*"} element={<View404 />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
142
react/admin-panel/src/views/log-view.js
Normal file
142
react/admin-panel/src/views/log-view.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import usePagination from "shared/hooks/pagination";
|
||||
import {DataColumn, DataTable, DateTimeColumn, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import {Chip, TextField} from "@mui/material";
|
||||
import {DateTimePicker} from "@mui/x-date-pickers";
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import {API_DATETIME_FORMAT_DAYJS} from "shared/constants";
|
||||
|
||||
export default function LogView(props) {
|
||||
|
||||
//
|
||||
const LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'severe'];
|
||||
|
||||
const api = props.api;
|
||||
const showDialog = props.showDialog;
|
||||
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
|
||||
const navigate = useNavigate();
|
||||
const pagination = usePagination();
|
||||
const [logEntries, setLogEntries] = useState([]);
|
||||
|
||||
// filters
|
||||
const [logLevel, setLogLevel] = useState(2);
|
||||
const [timestamp, setTimestamp] = useState(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const [forceReload, setForceReload] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
requestModules(props.api, ["general"], currentLocale).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog("Error fetching translations: " + data.msg);
|
||||
}
|
||||
});
|
||||
}, [currentLocale]);
|
||||
|
||||
const onFetchLogs = useCallback((page, count, orderBy, sortOrder) => {
|
||||
api.fetchLogEntries(page, count, orderBy, sortOrder, LOG_LEVELS[logLevel], timestamp?.format(API_DATETIME_FORMAT_DAYJS), query).then((res) => {
|
||||
if (res.success) {
|
||||
setLogEntries(res.logs);
|
||||
pagination.update(res.pagination);
|
||||
} else {
|
||||
showDialog(res.msg, "Error fetching log entries");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, [api, showDialog, logLevel, timestamp, query]);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: wait for user to finish typing before force reloading
|
||||
setForceReload(forceReload + 1);
|
||||
}, [query, timestamp, logLevel]);
|
||||
|
||||
const messageColumn = (() => {
|
||||
let column = new DataColumn(L("message"), "message");
|
||||
column.renderData = (L, entry) => {
|
||||
return <pre>{entry.message}</pre>
|
||||
}
|
||||
return column;
|
||||
})();
|
||||
|
||||
const columnDefinitions = [
|
||||
new NumericColumn(L("general.id"), "id"),
|
||||
new StringColumn(L("module"), "module"),
|
||||
new StringColumn(L("severity"), "severity"),
|
||||
new DateTimeColumn(L("timestamp"), "timestamp"),
|
||||
messageColumn,
|
||||
];
|
||||
|
||||
return <>
|
||||
<div className={"content-header"}>
|
||||
<div className={"container-fluid"}>
|
||||
<div className={"row mb-2"}>
|
||||
<div className={"col-sm-6"}>
|
||||
<h1 className={"m-0 text-dark"}>System Log</h1>
|
||||
</div>
|
||||
<div className={"col-sm-6"}>
|
||||
<ol className={"breadcrumb float-sm-right"}>
|
||||
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
|
||||
<li className="breadcrumb-item active">System Log</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"content overflow-auto"}>
|
||||
<div className={"row p-2"}>
|
||||
<div className={"col-2"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.severity")}</label>
|
||||
<select className={"form-control"} value={logLevel} onChange={e => setLogLevel(parseInt(e.target.value))}>
|
||||
{LOG_LEVELS.map((value, index) =>
|
||||
<option key={"option-" + value} value={index}>{value}</option>)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"col-4"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.timestamp")}</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<DateTimePicker className={"form-control"}
|
||||
label="Select date time to filter..."
|
||||
value={timestamp ? dayjs(timestamp) : null}
|
||||
onChange={(newValue) => {console.log(newValue);
|
||||
setTimestamp(newValue)
|
||||
}}
|
||||
slotProps={{ textField: { size: 'small' } }}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"col-6"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.query")}</label>
|
||||
<TextField
|
||||
className={"form-control"}
|
||||
placeholder={L("log.search_query") + "…"}
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
variant={"outlined"}
|
||||
size={"small"}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"container-fluid"}>
|
||||
<DataTable
|
||||
data={logEntries}
|
||||
pagination={pagination}
|
||||
className={"table table-striped"}
|
||||
fetchData={onFetchLogs}
|
||||
forceReload={forceReload}
|
||||
defaultSortColumn={3}
|
||||
defaultSortOrder={"desc"}
|
||||
placeholder={"No log entries to display"}
|
||||
columns={columnDefinitions} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@@ -32,23 +32,25 @@
|
||||
"maxParallelRequests": 1
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"@mui/icons-material": "^5.11.0",
|
||||
"@mui/material": "^5.11.3",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@mui/x-date-pickers": "^7.0.0",
|
||||
"chart.js": "^4.0.1",
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"dayjs": "^1.11.10",
|
||||
"material-ui-color-picker": "^3.5.1",
|
||||
"mini-css-extract-plugin": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^5.0.1",
|
||||
"react-collapse": "^5.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.6.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"sprintf-js": "^1.1.2"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -63,4 +65,4 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,4 +360,13 @@ export default class API {
|
||||
async downloadGPG(userId) {
|
||||
return this.apiCall("user/downloadGPG", { id: userId }, true);
|
||||
}
|
||||
|
||||
/** Log API **/
|
||||
async fetchLogEntries(pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc',
|
||||
severity = "debug", since = null, query = "") {
|
||||
return this.apiCall("logs/get", {
|
||||
page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder,
|
||||
since: since, severity: severity, query: query
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
export const API_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
export const API_DATE_FORMAT = "yyyy-MM-dd";
|
||||
export const API_TIME_FORMAT = "HH:mm:ss";
|
||||
export const API_DATETIME_FORMAT = API_DATE_FORMAT + " " + API_TIME_FORMAT;
|
||||
export const API_DATE_FORMAT_DAYJS = "YYYY-MM-DD";
|
||||
export const API_TIME_FORMAT_DAYJS = "HH:mm:ss";
|
||||
export const API_DATETIME_FORMAT_DAYJS = API_DATE_FORMAT_DAYJS + " " + API_TIME_FORMAT_DAYJS;
|
||||
|
||||
|
||||
export const USER_GROUP_ADMIN = 1;
|
||||
export const USER_GROUP_SUPPORT = 2;
|
||||
|
||||
@@ -16,6 +16,7 @@ export function DataTable(props) {
|
||||
columns, data, pagination,
|
||||
fetchData, onClick, onFilter,
|
||||
defaultSortColumn, defaultSortOrder,
|
||||
forceReload,
|
||||
title, ...other } = props;
|
||||
|
||||
const {translate: L} = useContext(LocaleContext);
|
||||
@@ -53,10 +54,10 @@ export function DataTable(props) {
|
||||
}
|
||||
}, [pagination?.data?.pageSize, pagination?.data?.current]);
|
||||
|
||||
// sorting changed
|
||||
// sorting changed or we forced an update
|
||||
useEffect(() => {
|
||||
onFetchData(true);
|
||||
}, [sortAscending, sortColumn]);
|
||||
}, [sortAscending, sortColumn, forceReload]);
|
||||
|
||||
let headerRow = [];
|
||||
const onChangeSort = useCallback((index, column) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {format, parse, formatDistance as formatDistanceDateFns } from "date-fns";
|
||||
import {API_DATE_FORMAT, API_DATETIME_FORMAT} from "./constants";
|
||||
import {API_DATETIME_FORMAT} from "./constants";
|
||||
|
||||
function createDownload(name, data) {
|
||||
const url = window.URL.createObjectURL(new Blob([data]));
|
||||
|
||||
6893
react/yarn.lock
6893
react/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user