mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-22 18:25:47 +08:00
feat: add detail sidebar for record list page, improve token list page (#3589)
This commit is contained in:
parent
26718bc4a1
commit
2a5722e45b
@ -73,7 +73,7 @@ class BaseListPage extends React.Component {
|
||||
this.fetch({pagination});
|
||||
}
|
||||
|
||||
getColumnSearchProps = dataIndex => ({
|
||||
getColumnSearchProps = (dataIndex, customRender = null) => ({
|
||||
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
||||
<div style={{padding: 8}}>
|
||||
<Input
|
||||
@ -126,8 +126,8 @@ class BaseListPage extends React.Component {
|
||||
setTimeout(() => this.searchInput.select(), 100);
|
||||
}
|
||||
},
|
||||
render: text =>
|
||||
this.state.searchedColumn === dataIndex ? (
|
||||
render: (text, record, index) => {
|
||||
const highlightContent = this.state.searchedColumn === dataIndex ? (
|
||||
<Highlighter
|
||||
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
||||
searchWords={[this.state.searchText]}
|
||||
@ -136,7 +136,10 @@ class BaseListPage extends React.Component {
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
),
|
||||
);
|
||||
|
||||
return customRender ? customRender({text, record, index}, highlightContent) : highlightContent;
|
||||
},
|
||||
});
|
||||
|
||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||
|
@ -14,12 +14,12 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Switch, Table} from "antd";
|
||||
import {Button, Descriptions, Drawer, Switch, Table, Tooltip} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as RecordBackend from "./backend/RecordBackend";
|
||||
import i18next from "i18next";
|
||||
import moment from "moment";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import Editor from "./common/Editor";
|
||||
|
||||
class RecordListPage extends BaseListPage {
|
||||
UNSAFE_componentWillMount() {
|
||||
@ -28,21 +28,6 @@ class RecordListPage extends BaseListPage {
|
||||
this.fetch({pagination});
|
||||
}
|
||||
|
||||
newRecord() {
|
||||
return {
|
||||
owner: "built-in",
|
||||
name: "1234",
|
||||
id: "1234",
|
||||
clientIp: "::1",
|
||||
timestamp: moment().format(),
|
||||
organization: "built-in",
|
||||
username: "admin",
|
||||
requestUri: "/api/get-account",
|
||||
action: "login",
|
||||
isTriggered: false,
|
||||
};
|
||||
}
|
||||
|
||||
renderTable(records) {
|
||||
let columns = [
|
||||
{
|
||||
@ -65,16 +50,13 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("general:Client IP"),
|
||||
dataIndex: "clientIp",
|
||||
key: "clientIp",
|
||||
width: "100px",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("clientIp"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${text}`}>
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
...this.getColumnSearchProps("clientIp", (row, highlightContent) => (
|
||||
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${row.text}`}>
|
||||
{highlightContent}
|
||||
</a>
|
||||
)),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Timestamp"),
|
||||
@ -120,28 +102,28 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("general:Method"),
|
||||
dataIndex: "method",
|
||||
key: "method",
|
||||
width: "110px",
|
||||
width: "100px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: "GET", value: "GET"},
|
||||
{text: "HEAD", value: "HEAD"},
|
||||
{text: "POST", value: "POST"},
|
||||
{text: "PUT", value: "PUT"},
|
||||
{text: "DELETE", value: "DELETE"},
|
||||
{text: "CONNECT", value: "CONNECT"},
|
||||
{text: "OPTIONS", value: "OPTIONS"},
|
||||
{text: "TRACE", value: "TRACE"},
|
||||
{text: "PATCH", value: "PATCH"},
|
||||
],
|
||||
"GET", "HEAD", "POST", "PUT", "DELETE",
|
||||
"CONNECT", "OPTIONS", "TRACE", "PATCH",
|
||||
].map(el => ({text: el, value: el})),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Request URI"),
|
||||
dataIndex: "requestUri",
|
||||
key: "requestUri",
|
||||
// width: "300px",
|
||||
width: "200px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("requestUri"),
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
...this.getColumnSearchProps("requestUri", (row, highlightContent) => (
|
||||
<Tooltip placement="topLeft" title={row.text}>
|
||||
{highlightContent}
|
||||
</Tooltip>
|
||||
)),
|
||||
},
|
||||
{
|
||||
title: i18next.t("user:Language"),
|
||||
@ -155,7 +137,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("record:Status code"),
|
||||
dataIndex: "statusCode",
|
||||
key: "statusCode",
|
||||
width: "90px",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("statusCode"),
|
||||
},
|
||||
@ -163,16 +145,26 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("record:Response"),
|
||||
dataIndex: "response",
|
||||
key: "response",
|
||||
width: "90px",
|
||||
width: "220px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("response"),
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
...this.getColumnSearchProps("response", (row, highlightContent) => (
|
||||
<Tooltip placement="topLeft" title={row.text}>
|
||||
{highlightContent}
|
||||
</Tooltip>
|
||||
)),
|
||||
},
|
||||
{
|
||||
title: i18next.t("record:Object"),
|
||||
dataIndex: "object",
|
||||
key: "object",
|
||||
width: "90px",
|
||||
width: "200px",
|
||||
sorter: true,
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
...this.getColumnSearchProps("object"),
|
||||
},
|
||||
{
|
||||
@ -191,7 +183,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("record:Is triggered"),
|
||||
dataIndex: "isTriggered",
|
||||
key: "isTriggered",
|
||||
width: "140px",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
@ -204,6 +196,24 @@ class RecordListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: "80px",
|
||||
sorter: true,
|
||||
fixed: "right",
|
||||
render: (text, record, index) => (
|
||||
<Button type="link" onClick={() => {
|
||||
this.setState({
|
||||
detailRecord: record,
|
||||
detailShow: true,
|
||||
});
|
||||
}}>
|
||||
{i18next.t("general:Detail")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (Setting.isLocalAdminUser(this.props.account)) {
|
||||
@ -220,7 +230,7 @@ class RecordListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={records} rowKey="id" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "100%"}} columns={columns} dataSource={records} rowKey="id" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Records")}
|
||||
@ -229,10 +239,73 @@ class RecordListPage extends BaseListPage {
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
{/* TODO: Should be packaged as a component after confirm it run correctly.*/}
|
||||
<Drawer
|
||||
title={i18next.t("general:Detail")}
|
||||
width={Setting.isMobile() ? "100%" : 640}
|
||||
placement="right"
|
||||
destroyOnClose
|
||||
onClose={() => this.setState({detailShow: false})}
|
||||
open={this.state.detailShow}
|
||||
>
|
||||
<Descriptions bordered size="small" column={1} layout={Setting.isMobile() ? "vertical" : "horizontal"} style={{padding: "12px", height: "100%", overflowY: "auto"}}>
|
||||
<Descriptions.Item label={i18next.t("general:ID")}>{this.getDetailField("id")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Client IP")}>{this.getDetailField("clientIp")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Timestamp")}>{this.getDetailField("createdTime")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Organization")}>
|
||||
<Link to={`/organizations/${this.getDetailField("organization")}`}>
|
||||
{this.getDetailField("organization")}
|
||||
</Link>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:User")}>
|
||||
<Link to={`/users/${this.getDetailField("organization")}/${this.getDetailField("user")}`}>
|
||||
{this.getDetailField("user")}
|
||||
</Link>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Method")}>{this.getDetailField("method")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Request URI")}>{this.getDetailField("requestUri")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("user:Language")}>{this.getDetailField("language")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("record:Status code")}>{this.getDetailField("statusCode")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("general:Action")}>{this.getDetailField("action")}</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("record:Response")}>
|
||||
<Editor
|
||||
value={this.getDetailField("response")}
|
||||
fillHeight
|
||||
fillWidth
|
||||
dark
|
||||
readOnly
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={i18next.t("record:Object")}>
|
||||
<Editor
|
||||
value={this.jsonStrFormatter(this.getDetailField("object"))}
|
||||
lang="json"
|
||||
fillHeight
|
||||
fillWidth
|
||||
dark
|
||||
readOnly
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
jsonStrFormatter = str => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(str), null, 2);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
getDetailField = dataIndex => {
|
||||
return this.state.detailRecord ? this.state.detailRecord?.[dataIndex] ?? "" : "";
|
||||
};
|
||||
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
@ -255,6 +328,8 @@ class RecordListPage extends BaseListPage {
|
||||
},
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
detailShow: false,
|
||||
detailRecord: null,
|
||||
});
|
||||
} else {
|
||||
if (res.data.includes("Please login first")) {
|
||||
|
@ -153,7 +153,7 @@ class TokenListPage extends BaseListPage {
|
||||
title: i18next.t("token:Authorization code"),
|
||||
dataIndex: "code",
|
||||
key: "code",
|
||||
// width: '150px',
|
||||
width: "180px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("code"),
|
||||
render: (text, record, index) => {
|
||||
@ -164,7 +164,7 @@ class TokenListPage extends BaseListPage {
|
||||
title: i18next.t("token:Access token"),
|
||||
dataIndex: "accessToken",
|
||||
key: "accessToken",
|
||||
// width: '150px',
|
||||
width: "220px",
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
...this.getColumnSearchProps("accessToken"),
|
||||
@ -225,7 +225,7 @@ class TokenListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={tokens} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "100%"}} columns={columns} dataSource={tokens} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Tokens")}
|
||||
|
@ -22,6 +22,10 @@ export const Editor = (props) => {
|
||||
height: "100%",
|
||||
style: {height: "100%"},
|
||||
} : {};
|
||||
const fillWidth = props.fillWidth ? {
|
||||
width: "100%",
|
||||
style: {width: "100%"},
|
||||
} : {};
|
||||
let extensions = [];
|
||||
switch (props.lang) {
|
||||
case "javascript":
|
||||
@ -37,13 +41,17 @@ export const Editor = (props) => {
|
||||
case "xml":
|
||||
extensions = [langs.xml()];
|
||||
break;
|
||||
case "json":
|
||||
extensions = [langs.json()];
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
value={props.value}
|
||||
height={props.height}
|
||||
{...props}
|
||||
{...fillHeight}
|
||||
{...fillWidth}
|
||||
readOnly={props.readOnly}
|
||||
theme={props.dark ? materialDark : "light"}
|
||||
extensions={extensions}
|
||||
|
@ -241,6 +241,7 @@
|
||||
"Delete": "Delete",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Detailed description information for reference, Casdoor itself will not use it",
|
||||
"Detail": "Detail",
|
||||
"Disable": "Disable",
|
||||
"Display name": "Display name",
|
||||
"Display name - Tooltip": "A user-friendly, easily readable name displayed publicly in the UI",
|
||||
|
@ -241,6 +241,7 @@
|
||||
"Delete": "删除",
|
||||
"Description": "描述信息",
|
||||
"Description - Tooltip": "供人参考的详细描述信息,Casdoor平台本身不会使用",
|
||||
"Detail": "详情",
|
||||
"Disable": "关闭",
|
||||
"Display name": "显示名称",
|
||||
"Display name - Tooltip": "在界面里公开显示的、易读的名称",
|
||||
|
Loading…
x
Reference in New Issue
Block a user