mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +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});
|
this.fetch({pagination});
|
||||||
}
|
}
|
||||||
|
|
||||||
getColumnSearchProps = dataIndex => ({
|
getColumnSearchProps = (dataIndex, customRender = null) => ({
|
||||||
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
|
||||||
<div style={{padding: 8}}>
|
<div style={{padding: 8}}>
|
||||||
<Input
|
<Input
|
||||||
@ -126,8 +126,8 @@ class BaseListPage extends React.Component {
|
|||||||
setTimeout(() => this.searchInput.select(), 100);
|
setTimeout(() => this.searchInput.select(), 100);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: text =>
|
render: (text, record, index) => {
|
||||||
this.state.searchedColumn === dataIndex ? (
|
const highlightContent = this.state.searchedColumn === dataIndex ? (
|
||||||
<Highlighter
|
<Highlighter
|
||||||
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
|
||||||
searchWords={[this.state.searchText]}
|
searchWords={[this.state.searchText]}
|
||||||
@ -136,7 +136,10 @@ class BaseListPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
text
|
text
|
||||||
),
|
);
|
||||||
|
|
||||||
|
return customRender ? customRender({text, record, index}, highlightContent) : highlightContent;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
handleSearch = (selectedKeys, confirm, dataIndex) => {
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
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 Setting from "./Setting";
|
||||||
import * as RecordBackend from "./backend/RecordBackend";
|
import * as RecordBackend from "./backend/RecordBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import moment from "moment";
|
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import Editor from "./common/Editor";
|
||||||
|
|
||||||
class RecordListPage extends BaseListPage {
|
class RecordListPage extends BaseListPage {
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
@ -28,21 +28,6 @@ class RecordListPage extends BaseListPage {
|
|||||||
this.fetch({pagination});
|
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) {
|
renderTable(records) {
|
||||||
let columns = [
|
let columns = [
|
||||||
{
|
{
|
||||||
@ -65,16 +50,13 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Client IP"),
|
title: i18next.t("general:Client IP"),
|
||||||
dataIndex: "clientIp",
|
dataIndex: "clientIp",
|
||||||
key: "clientIp",
|
key: "clientIp",
|
||||||
width: "100px",
|
width: "120px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("clientIp"),
|
...this.getColumnSearchProps("clientIp", (row, highlightContent) => (
|
||||||
render: (text, record, index) => {
|
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${row.text}`}>
|
||||||
return (
|
{highlightContent}
|
||||||
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${text}`}>
|
|
||||||
{text}
|
|
||||||
</a>
|
</a>
|
||||||
);
|
)),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Timestamp"),
|
title: i18next.t("general:Timestamp"),
|
||||||
@ -120,28 +102,28 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("general:Method"),
|
title: i18next.t("general:Method"),
|
||||||
dataIndex: "method",
|
dataIndex: "method",
|
||||||
key: "method",
|
key: "method",
|
||||||
width: "110px",
|
width: "100px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
{text: "GET", value: "GET"},
|
"GET", "HEAD", "POST", "PUT", "DELETE",
|
||||||
{text: "HEAD", value: "HEAD"},
|
"CONNECT", "OPTIONS", "TRACE", "PATCH",
|
||||||
{text: "POST", value: "POST"},
|
].map(el => ({text: el, value: el})),
|
||||||
{text: "PUT", value: "PUT"},
|
|
||||||
{text: "DELETE", value: "DELETE"},
|
|
||||||
{text: "CONNECT", value: "CONNECT"},
|
|
||||||
{text: "OPTIONS", value: "OPTIONS"},
|
|
||||||
{text: "TRACE", value: "TRACE"},
|
|
||||||
{text: "PATCH", value: "PATCH"},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("general:Request URI"),
|
title: i18next.t("general:Request URI"),
|
||||||
dataIndex: "requestUri",
|
dataIndex: "requestUri",
|
||||||
key: "requestUri",
|
key: "requestUri",
|
||||||
// width: "300px",
|
width: "200px",
|
||||||
sorter: true,
|
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"),
|
title: i18next.t("user:Language"),
|
||||||
@ -155,7 +137,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("record:Status code"),
|
title: i18next.t("record:Status code"),
|
||||||
dataIndex: "statusCode",
|
dataIndex: "statusCode",
|
||||||
key: "statusCode",
|
key: "statusCode",
|
||||||
width: "90px",
|
width: "120px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("statusCode"),
|
...this.getColumnSearchProps("statusCode"),
|
||||||
},
|
},
|
||||||
@ -163,16 +145,26 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("record:Response"),
|
title: i18next.t("record:Response"),
|
||||||
dataIndex: "response",
|
dataIndex: "response",
|
||||||
key: "response",
|
key: "response",
|
||||||
width: "90px",
|
width: "220px",
|
||||||
sorter: true,
|
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"),
|
title: i18next.t("record:Object"),
|
||||||
dataIndex: "object",
|
dataIndex: "object",
|
||||||
key: "object",
|
key: "object",
|
||||||
width: "90px",
|
width: "200px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
...this.getColumnSearchProps("object"),
|
...this.getColumnSearchProps("object"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -191,7 +183,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
title: i18next.t("record:Is triggered"),
|
title: i18next.t("record:Is triggered"),
|
||||||
dataIndex: "isTriggered",
|
dataIndex: "isTriggered",
|
||||||
key: "isTriggered",
|
key: "isTriggered",
|
||||||
width: "140px",
|
width: "120px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
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)) {
|
if (Setting.isLocalAdminUser(this.props.account)) {
|
||||||
@ -220,7 +230,7 @@ class RecordListPage extends BaseListPage {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Records")}
|
{i18next.t("general:Records")}
|
||||||
@ -229,10 +239,73 @@ class RecordListPage extends BaseListPage {
|
|||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
onChange={this.handleTableChange}
|
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>
|
</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 = {}) => {
|
fetch = (params = {}) => {
|
||||||
let field = params.searchedColumn, value = params.searchText;
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
@ -255,6 +328,8 @@ class RecordListPage extends BaseListPage {
|
|||||||
},
|
},
|
||||||
searchText: params.searchText,
|
searchText: params.searchText,
|
||||||
searchedColumn: params.searchedColumn,
|
searchedColumn: params.searchedColumn,
|
||||||
|
detailShow: false,
|
||||||
|
detailRecord: null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (res.data.includes("Please login first")) {
|
if (res.data.includes("Please login first")) {
|
||||||
|
@ -153,7 +153,7 @@ class TokenListPage extends BaseListPage {
|
|||||||
title: i18next.t("token:Authorization code"),
|
title: i18next.t("token:Authorization code"),
|
||||||
dataIndex: "code",
|
dataIndex: "code",
|
||||||
key: "code",
|
key: "code",
|
||||||
// width: '150px',
|
width: "180px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
...this.getColumnSearchProps("code"),
|
...this.getColumnSearchProps("code"),
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
@ -164,7 +164,7 @@ class TokenListPage extends BaseListPage {
|
|||||||
title: i18next.t("token:Access token"),
|
title: i18next.t("token:Access token"),
|
||||||
dataIndex: "accessToken",
|
dataIndex: "accessToken",
|
||||||
key: "accessToken",
|
key: "accessToken",
|
||||||
// width: '150px',
|
width: "220px",
|
||||||
sorter: true,
|
sorter: true,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
...this.getColumnSearchProps("accessToken"),
|
...this.getColumnSearchProps("accessToken"),
|
||||||
@ -225,7 +225,7 @@ class TokenListPage extends BaseListPage {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("general:Tokens")}
|
{i18next.t("general:Tokens")}
|
||||||
|
@ -22,6 +22,10 @@ export const Editor = (props) => {
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
style: {height: "100%"},
|
style: {height: "100%"},
|
||||||
} : {};
|
} : {};
|
||||||
|
const fillWidth = props.fillWidth ? {
|
||||||
|
width: "100%",
|
||||||
|
style: {width: "100%"},
|
||||||
|
} : {};
|
||||||
let extensions = [];
|
let extensions = [];
|
||||||
switch (props.lang) {
|
switch (props.lang) {
|
||||||
case "javascript":
|
case "javascript":
|
||||||
@ -37,13 +41,17 @@ export const Editor = (props) => {
|
|||||||
case "xml":
|
case "xml":
|
||||||
extensions = [langs.xml()];
|
extensions = [langs.xml()];
|
||||||
break;
|
break;
|
||||||
|
case "json":
|
||||||
|
extensions = [langs.json()];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={props.value}
|
value={props.value}
|
||||||
height={props.height}
|
{...props}
|
||||||
{...fillHeight}
|
{...fillHeight}
|
||||||
|
{...fillWidth}
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
theme={props.dark ? materialDark : "light"}
|
theme={props.dark ? materialDark : "light"}
|
||||||
extensions={extensions}
|
extensions={extensions}
|
||||||
|
@ -241,6 +241,7 @@
|
|||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
"Description": "Description",
|
"Description": "Description",
|
||||||
"Description - Tooltip": "Detailed description information for reference, Casdoor itself will not use it",
|
"Description - Tooltip": "Detailed description information for reference, Casdoor itself will not use it",
|
||||||
|
"Detail": "Detail",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
"Display name": "Display name",
|
"Display name": "Display name",
|
||||||
"Display name - Tooltip": "A user-friendly, easily readable name displayed publicly in the UI",
|
"Display name - Tooltip": "A user-friendly, easily readable name displayed publicly in the UI",
|
||||||
|
@ -241,6 +241,7 @@
|
|||||||
"Delete": "删除",
|
"Delete": "删除",
|
||||||
"Description": "描述信息",
|
"Description": "描述信息",
|
||||||
"Description - Tooltip": "供人参考的详细描述信息,Casdoor平台本身不会使用",
|
"Description - Tooltip": "供人参考的详细描述信息,Casdoor平台本身不会使用",
|
||||||
|
"Detail": "详情",
|
||||||
"Disable": "关闭",
|
"Disable": "关闭",
|
||||||
"Display name": "显示名称",
|
"Display name": "显示名称",
|
||||||
"Display name - Tooltip": "在界面里公开显示的、易读的名称",
|
"Display name - Tooltip": "在界面里公开显示的、易读的名称",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user