2023-06-12 09:27:16 +08:00
|
|
|
// 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,
|
2023-06-14 23:27:46 +08:00
|
|
|
owner: Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner,
|
2023-06-12 09:27:16 +08:00
|
|
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
|
|
|
groupName: this.props.match?.params.groupName,
|
2023-06-14 23:27:46 +08:00
|
|
|
groupId: undefined,
|
2023-06-12 09:27:16 +08:00
|
|
|
treeData: [],
|
2023-06-14 23:27:46 +08:00
|
|
|
selectedKeys: [this.props.match?.params.groupName],
|
2023-06-12 09:27:16 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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({
|
2023-06-14 23:27:46 +08:00
|
|
|
treeData: res.data,
|
|
|
|
groupId: this.findNodeId({children: res.data}, this.state.groupName),
|
2023-06-12 09:27:16 +08:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
Setting.showMessage("error", res.msg);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-14 23:27:46 +08:00
|
|
|
findNodeId(node, targetName) {
|
|
|
|
if (node.key === targetName) {
|
|
|
|
return node.id;
|
|
|
|
}
|
|
|
|
if (node.children) {
|
|
|
|
for (let i = 0; i < node.children.length; i++) {
|
|
|
|
const result = this.findNodeId(node.children[i], targetName);
|
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-06-12 09:27:16 +08:00
|
|
|
setTreeTitle(treeData) {
|
|
|
|
const haveChildren = Array.isArray(treeData.children) && treeData.children.length > 0;
|
|
|
|
const isSelected = this.state.groupName === treeData.key;
|
|
|
|
return {
|
|
|
|
id: treeData.id,
|
|
|
|
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}`);
|
|
|
|
}}
|
|
|
|
/>
|
2023-06-14 23:27:46 +08:00
|
|
|
{!haveChildren &&
|
2023-06-12 09:27:16 +08:00
|
|
|
<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}`);
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
2023-06-14 23:27:46 +08:00
|
|
|
}
|
2023-06-12 09:27:16 +08:00
|
|
|
</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,
|
|
|
|
groupId: info.node.id,
|
|
|
|
});
|
2023-06-14 23:27:46 +08:00
|
|
|
this.props.history.push(`/trees/${this.state.organizationName}/${info.node.key}`);
|
2023-06-12 09:27:16 +08:00
|
|
|
};
|
|
|
|
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}
|
2023-06-14 23:27:46 +08:00
|
|
|
selectedKeys={this.state.selectedKeys}
|
2023-06-12 09:27:16 +08:00
|
|
|
expandedKeys={this.state.expandedKeys}
|
|
|
|
onSelect={onSelect}
|
|
|
|
onExpand={onExpand}
|
|
|
|
showIcon={true}
|
|
|
|
treeData={treeData}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderOrganizationSelect() {
|
2023-06-14 23:27:46 +08:00
|
|
|
if (Setting.isAdminUser(this.props.account)) {
|
|
|
|
return (
|
|
|
|
<OrganizationSelect
|
|
|
|
initValue={this.state.organizationName}
|
|
|
|
style={{width: "100%"}}
|
|
|
|
onChange={(value) => {
|
|
|
|
this.setState({
|
|
|
|
organizationName: value,
|
|
|
|
});
|
|
|
|
this.props.history.push(`/trees/${value}`);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
2023-06-12 09:27:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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",
|
2023-06-14 23:27:46 +08:00
|
|
|
parentId: isRoot ? this.state.organizationName : this.state.groupId,
|
2023-06-12 09:27:16 +08:00
|
|
|
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>
|
2023-06-14 23:27:46 +08:00
|
|
|
<Col span={24} style={{textAlign: "center"}}>
|
2023-06-12 09:27:16 +08:00
|
|
|
{this.renderOrganizationSelect()}
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<Row>
|
2023-06-14 23:27:46 +08:00
|
|
|
<Col span={24} style={{marginTop: "10px"}}>
|
|
|
|
<Button size={"small"}
|
2023-06-12 09:27:16 +08:00
|
|
|
onClick={() => {
|
|
|
|
this.setState({
|
2023-06-14 23:27:46 +08:00
|
|
|
selectedKeys: [],
|
2023-06-12 09:27:16 +08:00
|
|
|
groupName: null,
|
2023-06-14 23:27:46 +08:00
|
|
|
groupId: undefined,
|
2023-06-12 09:27:16 +08:00
|
|
|
});
|
2023-06-14 23:27:46 +08:00
|
|
|
this.props.history.push(`/trees/${this.state.organizationName}`);
|
|
|
|
}}
|
2023-06-12 09:27:16 +08:00
|
|
|
>
|
2023-06-14 23:27:46 +08:00
|
|
|
{i18next.t("group:Show all")}
|
|
|
|
</Button>
|
|
|
|
<Button size={"small"} type={"primary"} style={{marginLeft: "10px"}} onClick={() => this.addGroup(true)}>
|
2023-06-12 09:27:16 +08:00
|
|
|
{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}
|
|
|
|
groupId={this.state.groupId}
|
2023-06-14 23:27:46 +08:00
|
|
|
{...this.props}
|
|
|
|
/>
|
2023-06-12 09:27:16 +08:00
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default GroupTreePage;
|