casdoor/web/src/GroupTreePage.js
Yaodong Yu 1a9d02be46
feat: use the casbin model to store relationships between users and groups (#2178)
* fix:reslove conflict

* fix: remove interface
2023-08-11 10:59:18 +08:00

317 lines
10 KiB
JavaScript

// Copyright 2023 The Casdoor 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 {DeleteOutlined, EditOutlined, HolderOutlined, PlusOutlined, UsergroupAddOutlined} from "@ant-design/icons";
import {Button, Col, Empty, Row, Space, Tree} from "antd";
import i18next from "i18next";
import moment from "moment/moment";
import React from "react";
import * as GroupBackend from "./backend/GroupBackend";
import * as Setting from "./Setting";
import OrganizationSelect from "./common/select/OrganizationSelect";
import UserListPage from "./UserListPage";
class GroupTreePage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
owner: Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
groupName: this.props.match?.params.groupName,
treeData: [],
selectedKeys: [this.props.match?.params.groupName],
};
}
UNSAFE_componentWillMount() {
this.getTreeData();
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.organizationName !== prevState.organizationName) {
this.getTreeData();
}
if (prevState.treeData !== this.state.treeData) {
this.setTreeExpandedKeys();
}
}
getTreeData() {
GroupBackend.getGroups(this.state.organizationName, true).then((res) => {
if (res.status === "ok") {
this.setState({
treeData: res.data,
});
} else {
Setting.showMessage("error", res.msg);
}
});
}
setTreeTitle(treeData) {
const haveChildren = Array.isArray(treeData.children) && treeData.children.length > 0;
const isSelected = this.state.groupName === treeData.key;
return {
key: treeData.key,
title: <Space>
{treeData.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
<span>{treeData.title}</span>
{isSelected && (
<React.Fragment>
<PlusOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.addGroup();
}}
/>
<EditOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.props.history.push(`/groups/${this.state.organizationName}/${treeData.key}`);
}}
/>
{!haveChildren &&
<DeleteOutlined
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
GroupBackend.deleteGroup({owner: treeData.owner, name: treeData.key})
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.getTreeData();
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}}
/>
}
</React.Fragment>
)}
</Space>,
children: haveChildren ? treeData.children.map(i => this.setTreeTitle(i)) : [],
};
}
setTreeExpandedKeys = () => {
const expandedKeys = [];
const setExpandedKeys = (nodes) => {
for (const node of nodes) {
expandedKeys.push(node.key);
if (node.children) {
setExpandedKeys(node.children);
}
}
};
setExpandedKeys(this.state.treeData);
this.setState({
expandedKeys: expandedKeys,
});
};
renderTree() {
const onSelect = (selectedKeys, info) => {
this.setState({
selectedKeys: selectedKeys,
groupName: info.node.key,
});
this.props.history.push(`/trees/${this.state.organizationName}/${info.node.key}`);
};
const onExpand = (expandedKeysValue) => {
this.setState({
expandedKeys: expandedKeysValue,
});
};
if (this.state.treeData.length === 0) {
return <Empty />;
}
const treeData = this.state.treeData.map(i => this.setTreeTitle(i));
return (
<Tree
blockNode={true}
defaultSelectedKeys={[this.state.groupName]}
defaultExpandAll={true}
selectedKeys={this.state.selectedKeys}
expandedKeys={this.state.expandedKeys}
onSelect={onSelect}
onExpand={onExpand}
showIcon={true}
treeData={treeData}
/>
);
}
renderOrganizationSelect() {
if (Setting.isAdminUser(this.props.account)) {
return (
<OrganizationSelect
initValue={this.state.organizationName}
style={{width: "100%"}}
onChange={(value) => {
this.setState({
organizationName: value,
groupName: "",
});
this.props.history.push(`/trees/${value}`);
}}
/>
);
}
}
newGroup(isRoot) {
const randomName = Setting.getRandomName();
return {
owner: this.state.organizationName,
name: `group_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Group - ${randomName}`,
type: "Virtual",
parentId: isRoot ? this.state.organizationName : this.state.groupName,
isTopGroup: isRoot,
isEnabled: true,
};
}
addGroup(isRoot = false) {
const newGroup = this.newGroup(isRoot);
GroupBackend.addGroup(newGroup)
.then((res) => {
if (res.status === "ok") {
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
this.props.history.push({pathname: `/groups/${newGroup.owner}/${newGroup.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
render() {
return (
<div style={{
flex: 1,
backgroundColor: "white",
padding: "5px 5px 2px 5px",
}}>
<Row>
<Col span={5}>
<Row>
<Col span={24} style={{textAlign: "center"}}>
{this.renderOrganizationSelect()}
</Col>
</Row>
<Row>
<Col span={24} style={{marginTop: "10px"}}>
<Button size={"small"}
onClick={() => {
this.setState({
selectedKeys: [],
groupName: undefined,
});
this.props.history.push(`/trees/${this.state.organizationName}`);
}}
>
{i18next.t("group:Show all")}
</Button>
<Button size={"small"} type={"primary"} style={{marginLeft: "10px"}} onClick={() => this.addGroup(true)}>
{i18next.t("general:Add")}
</Button>
</Col>
</Row>
<Row style={{marginTop: 10}}>
<Col span={24} style={{textAlign: "left"}}>
{this.renderTree()}
</Col>
</Row>
</Col>
<Col span={19}>
<UserListPage
organizationName={this.state.organizationName}
groupName={this.state.groupName}
{...this.props}
/>
</Col>
</Row>
</div>
);
}
}
export default GroupTreePage;