feat: add UI to view logs

Signed-off-by: killer <1533063601@qq.com>
This commit is contained in:
killer
2021-07-07 14:59:03 +08:00
committed by Yang Luo
parent 6ae8e537b9
commit 21b36bbb47
14 changed files with 1090 additions and 33 deletions

View File

@ -29,6 +29,7 @@ import ApplicationListPage from "./ApplicationListPage";
import ApplicationEditPage from "./ApplicationEditPage";
import TokenListPage from "./TokenListPage";
import TokenEditPage from "./TokenEditPage";
import RecordListPage from "./RecordListPage";
import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./CustomGithubCorner";
@ -317,6 +318,13 @@ class App extends Component {
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="7">
<Link to="/records">
{i18next.t("general:Records")}
</Link>
</Menu.Item>
);
}
res.push(
<Menu.Item key="6" onClick={() => window.location.href = "/swagger"}>
@ -392,6 +400,7 @@ class App extends Component {
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
</Switch>
</div>
)

159
web/src/RecordListPage.js Normal file
View File

@ -0,0 +1,159 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Col, Row, Table} from 'antd';
import * as Setting from "./Setting";
import * as RecordBackend from "./backend/RecordBackend";
import i18next from "i18next";
class RecordListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
records: null,
};
}
UNSAFE_componentWillMount() {
this.getRecords();
}
getRecords() {
RecordBackend.getRecords()
.then((res) => {
this.setState({
records: res,
});
});
}
newRecord() {
return {
id : "",
Record:{
clientIp:"",
timestamp:"",
organization:"",
username:"",
requestUri:"",
action:"login",
},
}
}
renderTable(records) {
const columns = [
{
title: i18next.t("general:Client ip"),
dataIndex: 'Record',
key: 'Record',
width: '120px',
sorter: (a, b) => a.Record.clientIp.localeCompare(b.Record.clientIp),
render: (text, record, index) => {
return text.clientIp;
}
},
{
title: i18next.t("general:Timestamp"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.timestamp.localeCompare(b.Record.timestamp),
render: (text, record, index) => {
return Setting.getFormattedDate(text.timestamp);
}
},
{
title: i18next.t("general:Organization"),
dataIndex: 'Record',
key: 'Record',
width: '120px',
sorter: (a, b) => a.Record.organization.localeCompare(b.Record.organization),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text.organization}`}>
{text.organization}
</Link>
)
}
},
{
title: i18next.t("general:Username"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.username.localeCompare(b.Record.username),
render: (text, record, index) => {
return text.username;
}
},
{
title: i18next.t("general:Request uri"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.requestUri.localeCompare(b.Record.requestUri),
render: (text, record, index) => {
return text.requestUri;
}
},
{
title: i18next.t("general:Action"),
dataIndex: 'Record',
key: 'Record',
width: '160px',
sorter: (a, b) => a.Record.action.localeCompare(b.Record.action),
render: (text, record, index) => {
return text.action;
}
},
];
return (
<div>
<Table columns={columns} dataSource={records} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{i18next.t("general:Records")}&nbsp;&nbsp;&nbsp;&nbsp;
</div>
)}
loading={records === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.records)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default RecordListPage;

View File

@ -0,0 +1,22 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getRecords() {
return fetch(`${Setting.ServerUrl}/api/get-records`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}

View File

@ -13,6 +13,11 @@
"User": "User",
"Applications": "Applications",
"Application": "Application",
"Records": "Records",
"Client ip": "Client ip",
"Timestamp": "Timestamp",
"Username": "Username",
"Request uri": "Request uri",
"Save": "Save",
"Add": "Add",
"Action": "Action",