diff --git a/web/packages/tca-analysis/src/components/delete-modal/index.tsx b/web/packages/tca-analysis/src/components/delete-modal/index.tsx
new file mode 100644
index 000000000..f2e3529a3
--- /dev/null
+++ b/web/packages/tca-analysis/src/components/delete-modal/index.tsx
@@ -0,0 +1,106 @@
+// Copyright (c) 2021-2022 THL A29 Limited
+//
+// This source code file is made available under MIT License
+// See LICENSE for details
+// ==============================================================================
+
+/**
+ * 确认删除操作弹框
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Modal, Form, Input, message, Button } from 'coding-oa-uikit';
+import { t } from '@src/i18n/i18next';
+
+import s from './style.scss';
+
+interface DeleteModalProps {
+ actionType: string;
+ objectType: string;
+ confirmName: string;
+ visible: boolean;
+ addtionInfo?: string;
+ onCancel: () => void;
+ onOk: () => void;
+}
+
+const DeleteModal = ({ actionType, objectType, confirmName, addtionInfo='', visible, onCancel, onOk }: DeleteModalProps) => {
+ const [form] = Form.useForm();
+ const [confirmed, setConfirmed] = useState(true);
+
+ useEffect(() => {
+ visible && form.resetFields();
+ visible && setConfirmed(false);
+ }, [visible]);
+
+ /**
+ * 表单提交操作
+ * @param formData 参数
+ */
+ const onSubmitHandle = () => {
+ form.validateFields().then((formData) => {
+ if (formData?.confirm === confirmName) {
+ onOk();
+ } else {
+ message.error(t('验证失败,请重新输入'));
+ }
+ });
+ };
+
+ const checkConfirm = (changedValues: any) => {
+ if (changedValues?.confirm === confirmName) {
+ setConfirmed(true);
+ } else {
+ setConfirmed(false);
+ }
+ };
+
+ return (
+
+ {t('确认')}{actionType}
+ ,
+ ,
+ ]}
+ >
+
+ {t('您正在')}{actionType}{objectType} {confirmName}{' '}
+
+ {addtionInfo && {addtionInfo}
}
+ {t(`为确认${actionType}操作,请输入您要${actionType}的`)}{objectType}
+
+
+
+
+
+ );
+};
+
+export default DeleteModal;
diff --git a/web/packages/tca-analysis/src/components/delete-modal/style.scss b/web/packages/tca-analysis/src/components/delete-modal/style.scss
new file mode 100644
index 000000000..59d9b1e32
--- /dev/null
+++ b/web/packages/tca-analysis/src/components/delete-modal/style.scss
@@ -0,0 +1,21 @@
+.delete-modal {
+
+ .warning-message {
+ margin-bottom: 20px;
+ }
+
+ .confirm-message {
+ margin-bottom: 8px;
+ font-weight: 600;
+ }
+
+ .confirm-text {
+ background-color: #ebedf1;
+ font-weight: 500;
+ padding: 0 6px;
+ }
+
+ .confirm-input {
+ margin-bottom: 0px;
+ }
+}
\ No newline at end of file
diff --git a/web/packages/tca-analysis/src/modules/project-team/overview/index.tsx b/web/packages/tca-analysis/src/modules/project-team/overview/index.tsx
index 4784ca61a..9f00b7832 100644
--- a/web/packages/tca-analysis/src/modules/project-team/overview/index.tsx
+++ b/web/packages/tca-analysis/src/modules/project-team/overview/index.tsx
@@ -5,14 +5,17 @@
// ==============================================================================
import React, { useState, useEffect } from 'react';
-import { useParams } from 'react-router-dom';
+import { useParams, useHistory } from 'react-router-dom';
import { Form, Button, Input, message } from 'coding-oa-uikit';
-import { pick } from 'lodash';
+import { pick, get, find } from 'lodash';
+import { useSelector } from 'react-redux';
// 项目内
+import { getProjectListRouter, getProjectOverviewRouter } from '@src/utils/getRoutePath';
import { t } from '@src/i18n/i18next';
import { formatDateTime, getUserName } from '@src/utils';
-import { getProjectTeam, putProjectTeam } from '@src/services/common';
+import { getProjectTeam, putProjectTeam, disableProject} from '@src/services/common';
+import DeleteModal from '@src/components/delete-modal';
const layout = {
labelCol: { span: 6 },
@@ -23,6 +26,14 @@ const Overview = () => {
const [team, setTeam] = useState({});
const [edit, setEdit] = useState(false);
const { org_sid: orgSid, team_name: teamName }: any = useParams();
+ // 判断是否有权限删除团队项目
+ const history: any = useHistory();
+ const APP = useSelector((state: any) => state.APP);
+ const isSuperuser = get(APP, 'user.is_superuser', false); // 当前用户是否是超级管理员
+ const userName = get(APP, 'user.username', null);
+ const isAdmin = !!find(team?.admins, { username: userName }); // 当前用户是否是项目管理员
+ const deletable = isAdmin || isSuperuser; // 删除权限
+ const [deleteVisible, setDeleteVisible] = useState(false);
// 重置
const onReset = () => {
@@ -39,6 +50,9 @@ const Overview = () => {
message.success(t('项目信息更新成功'));
setTeam(response);
onReset();
+ if (values.name !== teamName) {
+ history.replace(getProjectOverviewRouter(orgSid, values.name));
+ }
});
};
@@ -55,8 +69,28 @@ const Overview = () => {
}
}, [orgSid, teamName]);
+ const handleDeleteTeam = () => {
+ disableProject(orgSid, teamName, {status: 2}).then(() => {
+ message.success('项目已禁用');
+ history.push(getProjectListRouter(orgSid));
+ }).finally(() => setDeleteVisible(false));
+ };
+
+ const onDelete = () => {
+ setDeleteVisible(true);
+ };
+
return (
+ setDeleteVisible(false)}
+ onOk={handleDeleteTeam}
+ />
{t('项目概览')}
+
{team.name}
{
{t('编辑')}
)}
+ {deletable && }
diff --git a/web/packages/tca-analysis/src/modules/projects/project/project-list.tsx b/web/packages/tca-analysis/src/modules/projects/project/project-list.tsx
index d0dfd33ae..6e7f2ebbb 100644
--- a/web/packages/tca-analysis/src/modules/projects/project/project-list.tsx
+++ b/web/packages/tca-analysis/src/modules/projects/project/project-list.tsx
@@ -12,9 +12,10 @@
import React, { useEffect, useState } from 'react';
import { Link, useHistory, useParams } from 'react-router-dom';
-import { Table, Tooltip, Button, Input } from 'coding-oa-uikit';
-import { pickBy, isNumber, get, toNumber } from 'lodash';
+import { Table, Tooltip, Button, Input, Menu, Dropdown, message } from 'coding-oa-uikit';
+import { pickBy, isNumber, get, toNumber, find } from 'lodash';
import qs from 'qs';
+import { useSelector } from 'react-redux';
import SelectDropdown from '../../../components/select-dropdown';
import QuestionCircle from 'coding-oa-uikit/lib/icon/QuestionCircle';
@@ -23,10 +24,12 @@ import { useStateStore } from '@src/context/store';
import { DEFAULT_PAGER } from '@src/common/constants';
import { useQuery } from '@src/utils/hooks';
import { getProjectRouter, getSchemeRouter } from '@src/utils/getRoutePath';
-import { getProjects } from '@src/services/projects';
+import { getProjects, delProject } from '@src/services/projects';
+import { getMembers } from '@src/services/common';
import ScanModal from './scan-modal';
import NewProjectModal from './new-project-modal';
+import DeleteModal from '@src/components/delete-modal';
import style from '../style.scss';
@@ -51,6 +54,17 @@ const ProjectList = (props: ProjectListProps) => {
const [visible, setVisible] = useState(false);
const [projectId, setProjectId] = useState() as any;
const [createProjectVsb, setCreateProjectVsb] = useState(false);
+ const [curProjId, setCurProjId] = useState(null);
+ const [reload, setReload] = useState(false);
+
+ // 判断是否有权限删除分支项目
+ const APP = useSelector((state: any) => state.APP);
+ const isSuperuser = get(APP, 'user.is_superuser', false); // 当前用户是否是超级管理员
+ const userName = get(APP, 'user.username', null);
+ const [admins, setAdmins] = useState([]);
+ const isAdmin = !!find(admins, { username: userName }); // 当前用户是否是代码库管理员
+ const deletable = isAdmin || isSuperuser; // 删除权限
+ const [deleteVisible, setDeleteVisible] = useState(false);
const [searchParams, setSearchParams] = useState({
scan_scheme: query.get('scan_scheme') || '',
@@ -67,8 +81,18 @@ const ProjectList = (props: ProjectListProps) => {
pager.pageStart,
searchParams.scan_scheme,
searchParams.branch_or_scheme,
+ reload,
]);
+ useEffect(() => {
+ // 获取代码库成员
+ if (repoId) {
+ getMembers(orgSid, teamName, repoId).then((response) => {
+ setAdmins(response.admins);
+ });
+ }
+ }, [repoId]);
+
const getListData = (limit: number = pageSize, offset: number = pageStart) => {
const params = {
limit,
@@ -104,6 +128,21 @@ const ProjectList = (props: ProjectListProps) => {
setPager(DEFAULT_PAGER);
};
+ const onDeleteProject = (id: number) => {
+ setCurProjId(id);
+ setDeleteVisible(true);
+ };
+
+ const handleDeleteProject = () => {
+ delProject(orgSid, teamName, repoId, curProjId).then(() => {
+ message.success('已删除分支项目');
+ setReload(!reload);
+ }).finally(() => {
+ setDeleteVisible(false);
+ setCurProjId(null);
+ });
+ };
+
return (
@@ -137,9 +176,8 @@ const ProjectList = (props: ProjectListProps) => {
onClick={() => setCreateProjectVsb(true)}
>
添加分支项目
-
+
-
{
title="操作"
dataIndex="id"
width={240}
- render={id => (
- <>
+ render={ id => {
+ const menu = (
+
+ );
+ return (
+ <>
{
}}
>
启动分析
-
-
- 分支概览
-
-
- 分析历史
-
- >
- )}
+
+
+
+ 更多操作
+
+
+ >
+ );
+ }
+ }
/>
{
// getSchemes(branch);
}}
/>
+ setDeleteVisible(false)}
+ onOk={handleDeleteProject}
+ />
);
};
diff --git a/web/packages/tca-analysis/src/modules/projects/style.scss b/web/packages/tca-analysis/src/modules/projects/style.scss
index 5c6972b52..08e53ade3 100644
--- a/web/packages/tca-analysis/src/modules/projects/style.scss
+++ b/web/packages/tca-analysis/src/modules/projects/style.scss
@@ -9,12 +9,12 @@
:global(.ant-table-tbody) {
&>tr {
- td>a.link-name {
+ td>.link-name {
color: $grey-8;
}
&:hover {
- td>a.link-name {
+ td>.link-name {
color: $blue-5 !important;
}
}
diff --git a/web/packages/tca-analysis/src/modules/repos/create.tsx b/web/packages/tca-analysis/src/modules/repos/create.tsx
index 43f8aaf27..7cacf7369 100644
--- a/web/packages/tca-analysis/src/modules/repos/create.tsx
+++ b/web/packages/tca-analysis/src/modules/repos/create.tsx
@@ -203,16 +203,15 @@ const Create = () => {
))}
-
@@ -222,6 +221,27 @@ const Create = () => {
+ prevValues.scm_auth_id !== curValues.scm_auth_id}
+ >
+ {({ getFieldValue }) => {
+ const scmAuth = getFieldValue('scm_auth_id');
+ return scmAuth?.startsWith('ssh_token') && (
+ <>
+
+
+
+ >
+ );
+ }}
+
;
@@ -51,81 +56,122 @@ const RepoList = ({ repos }: IProps) => {
const repoId = toNumber(params.repoId);
const { org_sid: orgSid, team_name: teamName }: any = params;
const history = useHistory();
+ const dispatch = useDispatchStore();
const { curRepo, curRepoMember } = useInitRepo(orgSid, teamName, repoId);
const { admins = [], users = [] } = curRepoMember;
const tabValue = getTabValue();
+ // 判断是否有权限删除代码库
+ const APP = useSelector((state: any) => state.APP);
+ const isSuperuser = get(APP, 'user.is_superuser', false); // 当前用户是否是超级管理员
+ const userName = get(APP, 'user.username', null);
+ const isAdmin = !!find(admins, { username: userName }); // 当前用户是否是代码库管理员
+ const deletable = isAdmin || isSuperuser; // 删除权限
+ const [deleteVisible, setDeleteVisible] = useState(false);
// tab 切换跳转路由
const onTabChange = (key: string) => {
history.push(getRepoRouter(orgSid, teamName, repoId, key));
};
+ const onDeleteRepo = () => {
+ setDeleteVisible(true);
+ };
+
+ const handleDeleteRepo = () => {
+ delRepo(orgSid, teamName, repoId).then(() => {
+ message.success('已删除代码库');
+ remove(repos, (item: any) => item?.id === repoId);
+ const firstRepoId = isEmpty(repos) ? null : repos[0].id;
+ dispatch({
+ type: SET_REPOS,
+ payload: repos,
+ });
+ if (firstRepoId) {
+ history.push(getRepoRouter(orgSid, teamName, firstRepoId));
+ } else {
+ history.push(getReposRouter(orgSid, teamName));
+ }
+ }).finally(() => {
+ setDeleteVisible(false);
+ });
+ };
+
return (
-
-
-
- {curRepo ? (
- <>
-
-
- {curRepo.name}
- }
- onClick={() => history.push(getProjectRouter(orgSid, teamName, repoId))}
- >
- {t('代码分析')}
-
-
-
- {curRepo.scm_url}
-
-
-
- onTabChange(key)}
- className={s.tabs}
- >
-
-
-
-
-
-
-
-
-
-
- >
- ) : (
-
-
- {t('正在加载')}...
-
- )}
-
-
+
+ setDeleteVisible(false)}
+ onOk={handleDeleteRepo}
+ />
+
+
+ {curRepo ? (
+ <>
+
+
+ {curRepo.name}
+ }
+ onClick={() => history.push(getProjectRouter(orgSid, teamName, repoId))}
+ >
+ {t('代码分析')}
+
+
+
+ {curRepo.scm_url}
+
+
+
+ onTabChange(key)}
+ className={s.tabs}
+ >
+
+
+
+
+
+
+
+
+
+
+ >
+ ) : (
+
+
+ {t('正在加载')}...
+
+ )}
+
+
);
};
diff --git a/web/packages/tca-analysis/src/modules/repos/repo-list/tabs/overview.tsx b/web/packages/tca-analysis/src/modules/repos/repo-list/tabs/overview.tsx
index c06b2431b..edfaca826 100644
--- a/web/packages/tca-analysis/src/modules/repos/repo-list/tabs/overview.tsx
+++ b/web/packages/tca-analysis/src/modules/repos/repo-list/tabs/overview.tsx
@@ -4,7 +4,7 @@
// See LICENSE for details
// ==============================================================================
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { Form, Input, Button, message, Avatar } from 'coding-oa-uikit';
import UserIcon from 'coding-oa-uikit/lib/icon/User';
import { merge } from 'lodash';
@@ -20,13 +20,15 @@ interface IProps {
orgSid: string;
teamName: string;
repoId: number;
+ deletable: boolean;
+ onDelete: () => void;
}
const layout = {
labelCol: { span: 4 },
};
-const Overview = ({ curRepo, orgSid, teamName, repoId }: IProps) => {
+const Overview = ({ curRepo, orgSid, teamName, repoId, deletable, onDelete }: IProps) => {
const [form] = Form.useForm();
const [edit, setEdit] = useState(false);
const dispatch = useDispatchStore();
@@ -38,10 +40,9 @@ const Overview = ({ curRepo, orgSid, teamName, repoId }: IProps) => {
form.resetFields();
};
- const onFinish = (name: string) => {
- putRepo(orgSid, teamName, repoId, merge(curRepo, { name })).then((response) => {
+ const onFinish = (values: any) => {
+ putRepo(orgSid, teamName, repoId, merge(curRepo, values)).then((response) => {
message.success('仓库信息已更新');
- onReset();
dispatch({
type: SET_CUR_REPO,
payload: response,
@@ -49,64 +50,74 @@ const Overview = ({ curRepo, orgSid, teamName, repoId }: IProps) => {
});
};
+ useEffect(() => {
+ onReset();
+ }, [curRepo]);
+
return (
-
- {curRepo.scm_url}
-
-
- {edit ? : {curRepo.name}}
-
-
- <>
- }
- />
-
- {creatorInfo.nickname}
-
- >
-
+
+ {curRepo.scm_url}
+
+
+ {edit ? : {curRepo.ssh_url}}
+
+
+ {edit ? : {curRepo.name}}
+
+
+ <>
+ }
+ />
+
+ {creatorInfo.nickname}
+
+ >
+
-
- {formatDateTime(curRepo.created_time)}
-
+
+ {formatDateTime(curRepo.created_time)}
+
-
- {edit ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
+
+ {edit ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+ {deletable && }
+
+
);
};
export default Overview;
diff --git a/web/packages/tca-analysis/src/services/common.ts b/web/packages/tca-analysis/src/services/common.ts
index 8009bd2c5..a3f8f0671 100644
--- a/web/packages/tca-analysis/src/services/common.ts
+++ b/web/packages/tca-analysis/src/services/common.ts
@@ -21,7 +21,6 @@ export const getMainBaseURL = (org_sid: string, team_name: string) => `${MAIN_SE
export const getAnalysisBaseURL = (org_sid: string, team_name: string) => `${ANALYSIS_SERVER_API}${getBaseURL(org_sid, team_name)}`;
-
/**
* 获取代码库列表
* @param query
@@ -85,3 +84,11 @@ export const delProjectTeamMember = (orgSid: string, teamName: string, role: num
* @param orgSid
*/
export const getOrgMembers = (orgSid: string) => get(`${MAIN_SERVER_API}/orgs/${orgSid}/memberconf/`);
+
+/**
+ * 禁用团队项目
+ * @param orgSid
+ * @param teamName
+ * @param params
+ */
+ export const disableProject = (orgSid: string, teamName: string, params: any) => put(`${getMainBaseURL(orgSid, teamName)}/status/`, params);
diff --git a/web/packages/tca-analysis/src/services/projects.ts b/web/packages/tca-analysis/src/services/projects.ts
index 1bc0205d5..29628fdc7 100644
--- a/web/packages/tca-analysis/src/services/projects.ts
+++ b/web/packages/tca-analysis/src/services/projects.ts
@@ -9,7 +9,7 @@
* author luochunlan@coding.net
* create at 2020-10-23
*/
-import { get, post, put, getFile, postFile } from './index';
+import { get, post, put, del, getFile, postFile } from './index';
import { MAIN_SERVER, MAIN_SERVER_API, ANALYSIS_SERVER_API, getMainBaseURL, getAnalysisBaseURL } from './common';
const getProjectBaseURL = (org_sid: string, team_name: string, repoId: string | number, projectId: number) => `${getMainBaseURL(org_sid, team_name)}/repos/${repoId}/projects/${projectId}`;
@@ -45,6 +45,13 @@ export const createJob = (org_sid: string, team_name: string, repoId: string | n
*/
export const getProjectDetail = (org_sid: string, team_name: string, repoId: string | number, projectId: string | number) => get(`${getMainBaseURL(org_sid, team_name)}/repos/${repoId}/projects/${projectId}/`);
+/**
+ * 获取分支项目信息
+ * @param repoId - 代码库ID
+ * @param projectId - 项目ID
+ */
+ export const delProject = (org_sid: string, team_name: string, repoId: string | number, projectId: string | number) => del(`${getMainBaseURL(org_sid, team_name)}/repos/${repoId}/projects/${projectId}/`);
+
/**
* 获取指定代码库下与 CodeDog 关联的分支
* @param repoId - 代码库ID
diff --git a/web/packages/tca-analysis/src/services/repos.ts b/web/packages/tca-analysis/src/services/repos.ts
index c3453ad09..0794d83b7 100644
--- a/web/packages/tca-analysis/src/services/repos.ts
+++ b/web/packages/tca-analysis/src/services/repos.ts
@@ -9,7 +9,7 @@
* author luochunlan@coding.net
* create at 2020-10-23
*/
-import { get, post, put } from './index';
+import { get, post, put, del } from './index';
import { MAIN_SERVER_API, getMainBaseURL } from './common';
/**
@@ -58,7 +58,6 @@ export const getRepo = (orgSid: string, teamName: string, repoId: any) => get(`$
*/
export const putRepo = (orgSid: string, teamName: string, repoId: any, data: any) => put(`${getMainBaseURL(orgSid, teamName)}/repos/${repoId}/`, data);
-
/**
* 添加代码库成员
* @param orgSid
@@ -68,3 +67,9 @@ export const putRepo = (orgSid: string, teamName: string, repoId: any, data: any
* @returns
*/
export const postRepoMembers = (orgSid: string, teamName: string, repoId: any, data: any) => post(`${getMainBaseURL(orgSid, teamName)}/repos/${repoId}/memberconf/`, data);
+
+/**
+ * 删除代码库
+ * @param repoId: 代码库id
+ */
+ export const delRepo = (orgSid: string, teamName: string, repoId: any) => del(`${getMainBaseURL(orgSid, teamName)}/repos/${repoId}/`);
diff --git a/web/packages/tca-analysis/src/utils/getRoutePath.ts b/web/packages/tca-analysis/src/utils/getRoutePath.ts
index e083deb62..c053b596a 100644
--- a/web/packages/tca-analysis/src/utils/getRoutePath.ts
+++ b/web/packages/tca-analysis/src/utils/getRoutePath.ts
@@ -6,11 +6,23 @@
/**
- * 获取基础路由前缀
+ * 获取项目列表路由地址
+ * @param org_sid
+ */
+ export const getProjectListRouter = (org_sid: string) => `/t/${org_sid}/projects`;
+
+ /**
+ * 获取项目概览路由地址
* @param org_sid
* @param name
*/
+ export const getProjectOverviewRouter = (org_sid: string, name: string) => `/t/${org_sid}/p/${name}/profile`;
+/**
+ * 获取基础路由前缀
+ * @param org_sid
+ * @param name
+ */
export const getBaseRouter = (org_sid: string, name: string) => `/t/${org_sid}/p/${name}`;
/**
diff --git a/web/packages/tca-layout/src/components/delete-modal/index.tsx b/web/packages/tca-layout/src/components/delete-modal/index.tsx
new file mode 100644
index 000000000..f2e3529a3
--- /dev/null
+++ b/web/packages/tca-layout/src/components/delete-modal/index.tsx
@@ -0,0 +1,106 @@
+// Copyright (c) 2021-2022 THL A29 Limited
+//
+// This source code file is made available under MIT License
+// See LICENSE for details
+// ==============================================================================
+
+/**
+ * 确认删除操作弹框
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Modal, Form, Input, message, Button } from 'coding-oa-uikit';
+import { t } from '@src/i18n/i18next';
+
+import s from './style.scss';
+
+interface DeleteModalProps {
+ actionType: string;
+ objectType: string;
+ confirmName: string;
+ visible: boolean;
+ addtionInfo?: string;
+ onCancel: () => void;
+ onOk: () => void;
+}
+
+const DeleteModal = ({ actionType, objectType, confirmName, addtionInfo='', visible, onCancel, onOk }: DeleteModalProps) => {
+ const [form] = Form.useForm();
+ const [confirmed, setConfirmed] = useState(true);
+
+ useEffect(() => {
+ visible && form.resetFields();
+ visible && setConfirmed(false);
+ }, [visible]);
+
+ /**
+ * 表单提交操作
+ * @param formData 参数
+ */
+ const onSubmitHandle = () => {
+ form.validateFields().then((formData) => {
+ if (formData?.confirm === confirmName) {
+ onOk();
+ } else {
+ message.error(t('验证失败,请重新输入'));
+ }
+ });
+ };
+
+ const checkConfirm = (changedValues: any) => {
+ if (changedValues?.confirm === confirmName) {
+ setConfirmed(true);
+ } else {
+ setConfirmed(false);
+ }
+ };
+
+ return (
+
+ {t('确认')}{actionType}
+ ,
+ ,
+ ]}
+ >
+
+ {t('您正在')}{actionType}{objectType} {confirmName}{' '}
+
+ {addtionInfo && {addtionInfo}
}
+ {t(`为确认${actionType}操作,请输入您要${actionType}的`)}{objectType}
+
+
+
+
+
+ );
+};
+
+export default DeleteModal;
diff --git a/web/packages/tca-layout/src/components/delete-modal/style.scss b/web/packages/tca-layout/src/components/delete-modal/style.scss
new file mode 100644
index 000000000..59d9b1e32
--- /dev/null
+++ b/web/packages/tca-layout/src/components/delete-modal/style.scss
@@ -0,0 +1,21 @@
+.delete-modal {
+
+ .warning-message {
+ margin-bottom: 20px;
+ }
+
+ .confirm-message {
+ margin-bottom: 8px;
+ font-weight: 600;
+ }
+
+ .confirm-text {
+ background-color: #ebedf1;
+ font-weight: 500;
+ padding: 0 6px;
+ }
+
+ .confirm-input {
+ margin-bottom: 0px;
+ }
+}
\ No newline at end of file
diff --git a/web/packages/tca-layout/src/modules/layout/header/enterprise.tsx b/web/packages/tca-layout/src/modules/layout/header/enterprise.tsx
index b62c51c20..bcb61c84d 100644
--- a/web/packages/tca-layout/src/modules/layout/header/enterprise.tsx
+++ b/web/packages/tca-layout/src/modules/layout/header/enterprise.tsx
@@ -9,6 +9,7 @@ import { Link, useHistory } from 'react-router-dom';
import classnames from 'classnames';
import { Breadcrumb, Select } from 'coding-oa-uikit';
import AngleRight from 'coding-oa-uikit/lib/icon/AngleRight';
+import { filter } from 'lodash';
// 项目内
import { getHomeRouter, getTeamRouter, getTeamsRouter, getProjectRouter } from '@src/utils/getRoutePath';
@@ -35,7 +36,9 @@ const Enterprise = ({ org, orgSid, teamName }: IProps) => {
// 团队变更时重新获取项目列表
if (org.org_sid && teamName) {
getProjects(org.org_sid, null).then((response) => {
- setProjectOptions(response.map((item: any) => ({
+ // 只显示未禁用的项目
+ const activeProjects = filter(response, {status: 1});
+ setProjectOptions(activeProjects.map((item: any) => ({
...item,
label: item.display_name,
value: item.name,
diff --git a/web/packages/tca-layout/src/modules/layout/manage/siderbar-manage/menus.tsx b/web/packages/tca-layout/src/modules/layout/manage/siderbar-manage/menus.tsx
index 7f7e0df7c..dcc57d2d4 100644
--- a/web/packages/tca-layout/src/modules/layout/manage/siderbar-manage/menus.tsx
+++ b/web/packages/tca-layout/src/modules/layout/manage/siderbar-manage/menus.tsx
@@ -15,6 +15,8 @@ import Scan from 'coding-oa-uikit/lib/icon/Scan';
import Sitemap from 'coding-oa-uikit/lib/icon/Sitemap';
import Key from 'coding-oa-uikit/lib/icon/Key';
import Tiles from 'coding-oa-uikit/lib/icon/Tiles';
+import Panel from 'coding-oa-uikit/lib/icon/Panel';
+import Project from 'coding-oa-uikit/lib/icon/Project';
const MENUS: MenuItem[] = [
{
@@ -23,6 +25,18 @@ const MENUS: MenuItem[] = [
link: '/manage/users',
key: 'users',
},
+ {
+ icon: ,
+ title: t('团队管理'),
+ link: '/manage/orgs',
+ key: 'orgs',
+ },
+ {
+ icon: ,
+ title: t('项目管理'),
+ link: '/manage/teams',
+ key: 'teams',
+ },
{
icon: ,
title: t('分析记录管理'),
diff --git a/web/packages/tca-layout/src/modules/team/components/profile/index.tsx b/web/packages/tca-layout/src/modules/team/components/profile/index.tsx
index fb039d36a..5c271173e 100644
--- a/web/packages/tca-layout/src/modules/team/components/profile/index.tsx
+++ b/web/packages/tca-layout/src/modules/team/components/profile/index.tsx
@@ -5,15 +5,18 @@
// ==============================================================================
import React, { useEffect, useState } from 'react';
-import { useParams } from 'react-router-dom';
-
+import { useParams, useHistory } from 'react-router-dom';
+import { find } from 'lodash';
import { Input, Form, Button, Row, Col, Statistic, Card, message } from 'coding-oa-uikit';
import Group from 'coding-oa-uikit/lib/icon/Group';
import Project from 'coding-oa-uikit/lib/icon/Project';
import Package from 'coding-oa-uikit/lib/icon/Package';
+
+import { useStateStore } from '@src/context/store';
import { t } from '@src/i18n/i18next';
-import { getTeamInfo, updateTeamInfo } from '@src/services/team';
+import { getTeamInfo, updateTeamInfo, disableTeam } from '@src/services/team';
import { formatDateTime } from '@src/utils/index';
+import DeleteModal from '@src/components/delete-modal';
import style from './style.scss';
@@ -24,8 +27,15 @@ const layout = {
const Profile = () => {
const [form] = Form.useForm();
const { orgSid }: any = useParams();
- const [data, setData] = useState({}) as any;
- const [isEdit, setIsEdit] = useState(false);
+ const [data, setData] = useState({});
+ const [isEdit, setIsEdit] = useState(false);
+ const history = useHistory();
+ // 判断用户是否有权限删除团队,仅超级管理员和团队管理员可以删除
+ const { userinfo } = useStateStore();
+ const isAdmin = !!find(data?.admins, { username: userinfo.username }); // 当前用户是否是管理员
+ const [deleteVisible, setDeleteVisible] = useState(false);
+ const isSuperuser = userinfo.is_superuser; // 是否为超级管理员
+ const deletable = isAdmin || isSuperuser; // 删除权限
useEffect(() => {
if (orgSid) {
@@ -37,7 +47,7 @@ const Profile = () => {
const onFinish = (formData: any) => {
updateTeamInfo(orgSid, formData).then((res) => {
- message.success('团队信息更新成功');
+ message.success(t('团队信息更新成功'));
reset();
setData(res);
});
@@ -48,8 +58,32 @@ const Profile = () => {
form.resetFields();
};
+ const onDelete = () => {
+ setDeleteVisible(true);
+ }
+
+ const handleDeleteTeam = () => {
+ disableTeam(orgSid, {
+ status: 99
+ }).then(() => {
+ message.success(t('团队已禁用!'));
+ history.push('/teams');
+ }).catch((e: any) => {
+ console.error(e);
+ }).finally(() => setDeleteVisible(false));
+ };
+
return (
+
setDeleteVisible(false)}
+ onOk={handleDeleteTeam}
+ />
{data.name}
{t('团队概览')}
@@ -160,6 +194,9 @@ const Profile = () => {
{t('编辑')}
)}
+ {deletable && }
diff --git a/web/packages/tca-layout/src/modules/team/components/projects/index.tsx b/web/packages/tca-layout/src/modules/team/components/projects/index.tsx
index 702e53c73..ec9ff2028 100644
--- a/web/packages/tca-layout/src/modules/team/components/projects/index.tsx
+++ b/web/packages/tca-layout/src/modules/team/components/projects/index.tsx
@@ -7,6 +7,7 @@
import React, { useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { Tabs, Button, Input } from 'coding-oa-uikit';
+import { filter } from 'lodash';
import { getProjects } from '@src/services/team';
import List from './list';
@@ -18,12 +19,12 @@ const { TabPane } = Tabs;
const Projects = () => {
const { orgSid }: any = useParams();
const history: any = useHistory();
- const [loading, setLoading] = useState(false);
- const [data, setData] = useState();
- const [manageData, setManageData] = useState();
- const [tab, setTab] = useState('all');
- const [projectName, setProjectName] = useState('');
- const [visible, setVisible] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState([]);
+ const [manageData, setManageData] = useState([]);
+ const [tab, setTab] = useState('all');
+ const [projectName, setProjectName] = useState('');
+ const [visible, setVisible] = useState(false);
useEffect(() => {
getListData();
@@ -43,7 +44,9 @@ const Projects = () => {
setLoading(true);
getProjects(orgSid, { ...params }).then((res) => {
- tab === 'manage' ? setManageData(res) : setData(res);
+ // 只显示未禁用的项目
+ const activeProjects = filter(res, {status: 1});
+ tab === 'manage' ? setManageData(activeProjects) : setData(activeProjects);
})
.finally(() => {
setLoading(false);
diff --git a/web/packages/tca-layout/src/modules/team/components/workspace/index.tsx b/web/packages/tca-layout/src/modules/team/components/workspace/index.tsx
index e4819708e..621b78814 100644
--- a/web/packages/tca-layout/src/modules/team/components/workspace/index.tsx
+++ b/web/packages/tca-layout/src/modules/team/components/workspace/index.tsx
@@ -10,6 +10,7 @@ import { useParams, Link } from 'react-router-dom';
import { Tabs, Table } from 'coding-oa-uikit';
import AlignLeft from 'coding-oa-uikit/lib/icon/AlignLeft';
import Clock from 'coding-oa-uikit/lib/icon/Clock';
+import { filter } from 'lodash';
import { DEFAULT_PAGER } from '@src/common/constants';
import { formatDateTime } from '@src/utils/index';
@@ -41,8 +42,8 @@ const Workspace = () => {
pageStart: offset,
count: response.count,
});
-
- setList(response.results || []);
+ const activeTeamRepos = filter(response?.results, ['project_team.status',1]);
+ setList(activeTeamRepos || []);
})
.finally(() => {
setLoading(false);
@@ -97,6 +98,21 @@ const Workspace = () => {
>
)}
/>
+ (
+ <>
+
+ {project?.display_name}
+
+ >
+ )}
+ />
get(`${MAIN_SERVER_API}/orgs/`,
*/
export const createTeam = (params: any) => post(`${MAIN_SERVER_API}/orgs/`, params);
+/**
+ * 禁用团队
+ * @param orgSid 团队唯一标识
+ * @param params 参数
+ */
+ export const disableTeam = (orgSid: string, params: any) => put(`${MAIN_SERVER_API}/orgs/${orgSid}/status/`, params);
/**
* 获取团队详情
diff --git a/web/packages/tca-manage/src/components/delete-modal/index.tsx b/web/packages/tca-manage/src/components/delete-modal/index.tsx
new file mode 100644
index 000000000..8cfb8ea1c
--- /dev/null
+++ b/web/packages/tca-manage/src/components/delete-modal/index.tsx
@@ -0,0 +1,107 @@
+// Copyright (c) 2021-2022 THL A29 Limited
+//
+// This source code file is made available under MIT License
+// See LICENSE for details
+// ==============================================================================
+
+/**
+ * 确认删除操作弹框
+ */
+
+ import React, { useEffect, useState } from 'react';
+ import { Modal, Form, Input, message, Button } from 'coding-oa-uikit';
+ import { t } from '@src/i18n/i18next';
+
+ import s from './style.scss';
+
+ interface DeleteModalProps {
+ actionType: string;
+ objectType: string;
+ confirmName: string;
+ visible: boolean;
+ addtionInfo?: string;
+ onCancel: () => void;
+ onOk: () => void;
+ }
+
+ const DeleteModal = ({ actionType, objectType, confirmName, addtionInfo='', visible, onCancel, onOk }: DeleteModalProps) => {
+ const [form] = Form.useForm();
+ const [confirmed, setConfirmed] = useState(true);
+
+ useEffect(() => {
+ visible && form.resetFields();
+ visible && setConfirmed(false);
+ }, [visible]);
+
+ /**
+ * 表单提交操作
+ * @param formData 参数
+ */
+ const onSubmitHandle = () => {
+ form.validateFields().then((formData) => {
+ if (formData?.confirm === confirmName) {
+ onOk();
+ } else {
+ message.error(t('验证失败,请重新输入'));
+ }
+ });
+ };
+
+ const checkConfirm = (changedValues: any) => {
+ if (changedValues?.confirm === confirmName) {
+ setConfirmed(true);
+ } else {
+ setConfirmed(false);
+ }
+ };
+
+ return (
+
+ {t('确认')}{actionType}
+ ,
+ ,
+ ]}
+ >
+
+ {t('您正在')}{actionType}{objectType} {confirmName}{' '}
+
+ {addtionInfo && {addtionInfo}
}
+ {t(`为确认${actionType}操作,请输入您要${actionType}的`)}{objectType}
+
+
+
+
+
+ );
+ };
+
+ export default DeleteModal;
+
\ No newline at end of file
diff --git a/web/packages/tca-manage/src/components/delete-modal/style.scss b/web/packages/tca-manage/src/components/delete-modal/style.scss
new file mode 100644
index 000000000..59d9b1e32
--- /dev/null
+++ b/web/packages/tca-manage/src/components/delete-modal/style.scss
@@ -0,0 +1,21 @@
+.delete-modal {
+
+ .warning-message {
+ margin-bottom: 20px;
+ }
+
+ .confirm-message {
+ margin-bottom: 8px;
+ font-weight: 600;
+ }
+
+ .confirm-text {
+ background-color: #ebedf1;
+ font-weight: 500;
+ padding: 0 6px;
+ }
+
+ .confirm-input {
+ margin-bottom: 0px;
+ }
+}
\ No newline at end of file
diff --git a/web/packages/tca-manage/src/modules/orgs/constants.ts b/web/packages/tca-manage/src/modules/orgs/constants.ts
new file mode 100644
index 000000000..7af20c199
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/orgs/constants.ts
@@ -0,0 +1,22 @@
+import { t } from '@src/i18n/i18next';
+
+export const ORG_STATUS_ENUM = {
+ ACTIVE: 1,
+ INACTIVE: 99,
+};
+
+export const ORG_STATUS_CHOICES = {
+ [ORG_STATUS_ENUM.ACTIVE]: t('活跃'),
+ [ORG_STATUS_ENUM.INACTIVE]: t('禁用'),
+};
+
+export const ORG_STATUS_OPTIONS = [
+ {
+ label: ORG_STATUS_CHOICES[ORG_STATUS_ENUM.ACTIVE],
+ value: ORG_STATUS_ENUM.ACTIVE,
+ },
+ {
+ label: ORG_STATUS_CHOICES[ORG_STATUS_ENUM.INACTIVE],
+ value: ORG_STATUS_ENUM.INACTIVE,
+ },
+];
diff --git a/web/packages/tca-manage/src/modules/orgs/index.tsx b/web/packages/tca-manage/src/modules/orgs/index.tsx
new file mode 100644
index 000000000..ae4c299e6
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/orgs/index.tsx
@@ -0,0 +1,140 @@
+import React, { useEffect, useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Row, Col, Tabs, Modal, message } from 'coding-oa-uikit';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import { getPaginationParams, getFilterURLPath } from '@src/utils';
+import { DEFAULT_PAGER } from '@src/common/constants';
+import { useURLParams, useDeepEffect } from '@src/utils/hooks';
+import { getOrgs, putOrgStatus } from '@src/services/orgs';
+import DeleteModal from '@src/components/delete-modal';
+
+// 模块内
+import s from './style.scss';
+import Search from './search';
+import OrgTable from './org-table';
+import { ORG_STATUS_ENUM } from './constants';
+
+const { TabPane } = Tabs;
+const { confirm } = Modal;
+
+const FILTER_FIELDS = ['name', 'status'];
+
+const customFilterURLPath = (params = {}) => getFilterURLPath(FILTER_FIELDS, params);
+
+const Orgs = () => {
+ const history = useHistory();
+ const [listData, setListData] = useState>([]);
+ const [count, setCount] = useState(DEFAULT_PAGER.count);
+ const [loading, setLoading] = useState(false);
+ const [reload, setReload] = useState(false);
+ const { filter, currentPage, searchParams } = useURLParams(FILTER_FIELDS);
+ const [deleteVisible, setDeleteVisible] = useState(false);
+ const [curOrg, setCurOrg] = useState(null);
+
+ /**
+ * 根据路由参数获取团队列表
+ */
+ const getListData = () => {
+ setLoading(true);
+ getOrgs(filter).then((response) => {
+ setCount(response.count);
+ setListData(response.results || []);
+ setLoading(false);
+ });
+ };
+
+ // 当路由参数变化时触发
+ useDeepEffect(() => {
+ getListData();
+ }, [filter]);
+
+ // 手动触发
+ useEffect(() => {
+ getListData();
+ }, [reload]);
+
+ // 筛选
+ const onSearch = (params: any) => {
+ history.push(customFilterURLPath({
+ limit: DEFAULT_PAGER.pageSize,
+ offset: DEFAULT_PAGER.pageStart,
+ ...params,
+ }));
+ };
+
+ // 翻页
+ const onChangePageSize = (page: number, pageSize: number) => {
+ const params = getPaginationParams(page, pageSize);
+ history.push(customFilterURLPath(params));
+ };
+
+ // 禁用团队
+ const onDeleteOrg = (org: any) => {
+ setDeleteVisible(true);
+ setCurOrg(org);
+ };
+
+ const handleDeleteOrg = () => {
+ putOrgStatus(curOrg.org_sid, {status: ORG_STATUS_ENUM.INACTIVE}).then(() => {
+ message.success(t('已禁用团队'));
+ setReload(!reload);
+ setDeleteVisible(false);
+ setCurOrg(null);
+ });
+ };
+
+ // 恢复团队
+ const onRecoverOrg = (org: any) => {
+ confirm({
+ title: t('恢复团队'),
+ content: t('确定要恢复已禁用的团队吗?'),
+ onOk() {
+ putOrgStatus(org.org_sid, {status: ORG_STATUS_ENUM.ACTIVE}).then(() => {
+ message.success(t('已恢复团队'));
+ setReload(!reload);
+ });
+ },
+ onCancel() {},
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ `${range[0]} - ${range[1]} 条数据,共 ${total} 条`,
+ onChange: onChangePageSize,
+ }}
+ onDelete={onDeleteOrg}
+ onRecover={onRecoverOrg}
+ />
+
+ setDeleteVisible(false)}
+ onOk={handleDeleteOrg}
+ />
+ >
+ );
+};
+
+export default Orgs;
diff --git a/web/packages/tca-manage/src/modules/orgs/org-table.tsx b/web/packages/tca-manage/src/modules/orgs/org-table.tsx
new file mode 100644
index 000000000..4de17bd1e
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/orgs/org-table.tsx
@@ -0,0 +1,166 @@
+import React from 'react';
+import { Link, useHistory } from 'react-router-dom';
+import { Table, Tag, Tooltip, Button, Statistic, Row, Col, Card } from 'coding-oa-uikit';
+import Project from 'coding-oa-uikit/lib/icon/Project';
+import Repos from 'coding-oa-uikit/lib/icon/Repos';
+import Group from 'coding-oa-uikit/lib/icon/Group';
+import Undo from 'coding-oa-uikit/lib/icon/Undo';
+import Stop from 'coding-oa-uikit/lib/icon/Stop';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import EllipsisTemplate from '@src/components/ellipsis';
+import { formatDateTime } from '@src/utils';
+import { getOrgRouter } from '@src//utils/getRoutePath';
+
+// 模块内
+import {
+ ORG_STATUS_ENUM,
+ ORG_STATUS_CHOICES,
+} from './constants';
+
+const FormatStatus = (org: any) => {
+ const status = org?.status;
+ const info = status === ORG_STATUS_ENUM.ACTIVE ? {
+ text: ORG_STATUS_CHOICES[ORG_STATUS_ENUM.ACTIVE],
+ color: 'success',
+ } : {
+ text: ORG_STATUS_CHOICES[ORG_STATUS_ENUM.INACTIVE],
+ color : 'red',
+ };
+ return (
+ {info.text}
+ );
+};
+
+const FormatName = (org: any) => (
+ <>
+ {org?.status === ORG_STATUS_ENUM.ACTIVE
+ ?
+ {org.name}
+
+ : {org.name}
+ }
+
+ {org.owner} / {org.tel_number} / {formatDateTime(org.created_time)}
+
+ >
+);
+
+const FormatOverview = (org: any) => (
+
+
+
+ }
+ title={{t('成员数')}}
+ value={org.user_count}
+ valueStyle={{ color: '#f0850a', fontSize: '14px' }}
+ />
+
+
+
+
+ }
+ title={{t('项目数')}}
+ value={org.team_count}
+ valueStyle={{ color: '#3d98ff', fontSize: '14px' }}
+ />
+
+
+
+
+ }
+ title={{t('代码库')}}
+ value={org.repo_count}
+ valueStyle={{ color: '#3f8600', fontSize: '14px' }}
+ />
+
+
+
+);
+
+const FormatOp = (org: any, onDelete: (org: any) => void, onRecover: (org: any) => void) => {
+ const history = useHistory();
+ const status = org?.status;
+ const orgSid = org?.org_sid;
+ return (
+ <>
+
+ }
+ onClick={() => {
+ history.push(`/manage/teams?organization_sid=${orgSid}`);
+ }}
+ />
+
+ {status === ORG_STATUS_ENUM.ACTIVE &&
+ }
+ danger
+ onClick={() => onDelete(org)}
+ />
+ }
+ {status === ORG_STATUS_ENUM.INACTIVE &&
+ }
+ onClick={() => onRecover(org)}
+ />
+ }
+ >
+ );
+};
+
+interface IProps {
+ dataSource: Array;
+ pagination: any;
+ onDelete: (org: any) => void;
+ onRecover: (org: any) => void;
+}
+
+const OrgTable = ({ dataSource, pagination, onDelete, onRecover }: IProps) => {
+ const columns = [
+ {
+ title: t('团队名称'),
+ dataIndex: 'name',
+ render: (_: string, org: any) => FormatName(org),
+ },
+ {
+ title: t('团队概览'),
+ dataIndex: 'overview',
+ render: (_: string, org: any) => FormatOverview(org),
+ },
+ {
+ title: t('状态'),
+ dataIndex: 'status',
+ render: (_: number, org: any) => FormatStatus(org),
+ },
+ {
+ title: t('操作'),
+ dataIndex: 'op',
+ render: (_: any, org: any) => FormatOp(org, onDelete, onRecover),
+ },
+ ];
+ return (
+ <>
+ item.id}
+ dataSource={dataSource}
+ columns={columns}
+ />
+ >
+ );
+};
+
+export default OrgTable;
diff --git a/web/packages/tca-manage/src/modules/orgs/search.tsx b/web/packages/tca-manage/src/modules/orgs/search.tsx
new file mode 100644
index 000000000..593941c9d
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/orgs/search.tsx
@@ -0,0 +1,84 @@
+import React, { useEffect } from 'react';
+import { toNumber, isString, isEmpty, isArray, cloneDeep } from 'lodash';
+import { Form, Button, Input, Select } from 'coding-oa-uikit';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import Filter from '@src/components/filter';
+
+// 模块内
+import { ORG_STATUS_OPTIONS } from './constants';
+
+const numberParams: Array = ['status'];
+const arrayParams: Array = [];
+
+interface SearchProps {
+ searchParams: any;
+ loading: boolean;
+ callback: (params: any) => void;
+}
+
+const Search = ({ searchParams, loading, callback }: SearchProps) => {
+ const [form] = Form.useForm();
+ const initialValues = cloneDeep(searchParams);
+
+ Object.entries(initialValues).map(([key, value]: [string, string]) => {
+ if (numberParams.includes(key) && isString(value)) {
+ initialValues[key] = value.split(',').map((item: string) => toNumber(item));
+ }
+
+ if (arrayParams.includes(key) && isString(value)) {
+ initialValues[key] = value.split(',');
+ }
+ return [key, value];
+ });
+
+ useEffect(() => {
+ if (!loading) {
+ form.resetFields();
+ }
+ }, [loading]);
+
+ const onChange = (key: string, value: any) => {
+ callback({
+ ...searchParams,
+ [key]: value,
+ });
+ };
+
+ const onClear = () => {
+ form.resetFields();
+ callback({
+ name: '',
+ status: '',
+ });
+ };
+
+ return (
+
+
+
+
+ onChange('name', value)}
+ />
+
+ {Object.keys(searchParams).some((key: string) => (
+ isArray(searchParams[key]) ? !isEmpty(searchParams[key]) : searchParams[key]))
+ && (
+
+ )}
+
+ );
+};
+
+export default Search;
diff --git a/web/packages/tca-manage/src/modules/orgs/style.scss b/web/packages/tca-manage/src/modules/orgs/style.scss
new file mode 100644
index 000000000..3ca476c83
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/orgs/style.scss
@@ -0,0 +1,13 @@
+@import "@src/common/style/color.scss";
+
+.header {
+ padding: 0 24px;
+ border-bottom: 1px solid $grey-3;
+ :global(.ant-tabs-nav::before) {
+ border: none;
+ }
+}
+
+.filter-content {
+ padding: 0 24px;
+}
diff --git a/web/packages/tca-manage/src/modules/teams/constants.ts b/web/packages/tca-manage/src/modules/teams/constants.ts
new file mode 100644
index 000000000..48415f120
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/teams/constants.ts
@@ -0,0 +1,22 @@
+import { t } from '@src/i18n/i18next';
+
+export const TEAM_STATUS_ENUM = {
+ ACTIVE: 1,
+ INACTIVE: 2,
+};
+
+export const TEAM_STATUS_CHOICES = {
+ [TEAM_STATUS_ENUM.ACTIVE]: t('活跃'),
+ [TEAM_STATUS_ENUM.INACTIVE]: t('禁用'),
+};
+
+export const TEAM_STATUS_OPTIONS = [
+ {
+ label: TEAM_STATUS_CHOICES[TEAM_STATUS_ENUM.ACTIVE],
+ value: TEAM_STATUS_ENUM.ACTIVE,
+ },
+ {
+ label: TEAM_STATUS_CHOICES[TEAM_STATUS_ENUM.INACTIVE],
+ value: TEAM_STATUS_ENUM.INACTIVE,
+ },
+];
diff --git a/web/packages/tca-manage/src/modules/teams/index.tsx b/web/packages/tca-manage/src/modules/teams/index.tsx
new file mode 100644
index 000000000..46811d72e
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/teams/index.tsx
@@ -0,0 +1,141 @@
+import React, { useEffect, useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Row, Col, Tabs, Modal, message } from 'coding-oa-uikit';
+import { get } from 'lodash';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import { getPaginationParams, getFilterURLPath } from '@src/utils';
+import { DEFAULT_PAGER } from '@src/common/constants';
+import { useURLParams, useDeepEffect } from '@src/utils/hooks';
+import { getTeams, putTeamStatus } from '@src/services/teams';
+import DeleteModal from '@src/components/delete-modal';
+
+// 模块内
+import s from './style.scss';
+import Search from './search';
+import TeamTable from './team-table';
+import { TEAM_STATUS_ENUM } from './constants';
+
+const { TabPane } = Tabs;
+const { confirm } = Modal;
+
+const FILTER_FIELDS = ['status', 'organization_name', 'display_name', 'organization_sid'];
+
+const customFilterURLPath = (params = {}) => getFilterURLPath(FILTER_FIELDS, params);
+
+const Teams = () => {
+ const history = useHistory();
+ const [listData, setListData] = useState>([]);
+ const [count, setCount] = useState(DEFAULT_PAGER.count);
+ const [loading, setLoading] = useState(false);
+ const { filter, currentPage, searchParams } = useURLParams(FILTER_FIELDS);
+ const [deleteVisible, setDeleteVisible] = useState(false);
+ const [curTeam, setCurTeam] = useState(null);
+ const [reload, setReload] = useState(false);
+
+ /**
+ * 根据路由参数获取团队列表
+ */
+ const getListData = () => {
+ setLoading(true);
+ getTeams(filter).then((response) => {
+ setCount(response.count);
+ setListData(response.results || []);
+ setLoading(false);
+ });
+ };
+
+ // 当路由参数变化时触发
+ useDeepEffect(() => {
+ getListData();
+ }, [filter]);
+
+ // 手动触发
+ useEffect(() => {
+ getListData();
+ }, [reload]);
+
+ // 筛选
+ const onSearch = (params: any) => {
+ history.push(customFilterURLPath({
+ limit: DEFAULT_PAGER.pageSize,
+ offset: DEFAULT_PAGER.pageStart,
+ ...params,
+ }));
+ };
+
+ // 翻页
+ const onChangePageSize = (page: number, pageSize: number) => {
+ const params = getPaginationParams(page, pageSize);
+ history.push(customFilterURLPath(params));
+ };
+
+ // 禁用项目
+ const onDeleteTeam = (team: any) => {
+ setDeleteVisible(true);
+ setCurTeam(team);
+ };
+
+ const handleDeleteTeam = () => {
+ putTeamStatus(get(curTeam,['organization', 'org_sid']), get(curTeam, 'name'), {status: TEAM_STATUS_ENUM.INACTIVE}).then(() => {
+ message.success(t('已禁用项目'));
+ setReload(!reload);
+ setDeleteVisible(false);
+ setCurTeam(null);
+ });
+ };
+
+ // 恢复团队
+ const onRecoverTeam = (team: any) => {
+ confirm({
+ title: t('恢复项目'),
+ content: t('确定要恢复已禁用的项目吗?'),
+ onOk() {
+ putTeamStatus(get(team,['organization', 'org_sid']), get(team, 'name'), {status: TEAM_STATUS_ENUM.ACTIVE}).then(() => {
+ message.success(t('已恢复项目'));
+ setReload(!reload);
+ });
+ },
+ onCancel() {},
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ `${range[0]} - ${range[1]} 条数据,共 ${total} 条`,
+ onChange: onChangePageSize,
+ }}
+ onDelete={onDeleteTeam}
+ onRecover={onRecoverTeam}
+ />
+
+ setDeleteVisible(false)}
+ onOk={handleDeleteTeam}
+ />
+ >
+ );
+};
+
+export default Teams;
diff --git a/web/packages/tca-manage/src/modules/teams/search.tsx b/web/packages/tca-manage/src/modules/teams/search.tsx
new file mode 100644
index 000000000..3320c6c99
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/teams/search.tsx
@@ -0,0 +1,108 @@
+import React, { useEffect } from 'react';
+import { toNumber, isString, isEmpty, isArray, cloneDeep } from 'lodash';
+import { Form, Button, Input, Select } from 'coding-oa-uikit';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import Filter from '@src/components/filter';
+
+// 模块内
+import { TEAM_STATUS_OPTIONS } from './constants';
+
+const numberParams: Array = ['status'];
+const arrayParams: Array = [];
+
+interface SearchProps {
+ searchParams: any;
+ loading: boolean;
+ callback: (params: any) => void;
+}
+
+const Search = ({ searchParams, loading, callback }: SearchProps) => {
+ const [form] = Form.useForm();
+ const initialValues = cloneDeep(searchParams);
+
+ Object.entries(initialValues).map(([key, value]: [string, string]) => {
+ if (numberParams.includes(key) && isString(value)) {
+ initialValues[key] = value.split(',').map((item: string) => toNumber(item));
+ }
+
+ if (arrayParams.includes(key) && isString(value)) {
+ initialValues[key] = value.split(',');
+ }
+ return [key, value];
+ });
+
+ useEffect(() => {
+ if (!loading) {
+ form.resetFields();
+ }
+ }, [loading]);
+
+ const onChange = (key: string, value: any) => {
+ callback({
+ ...searchParams,
+ [key]: value,
+ });
+ };
+
+ const onClear = () => {
+ form.resetFields();
+ callback({
+ display_name: '',
+ organization_name: '',
+ status: '',
+ organization_sid: '',
+ });
+ };
+
+ return (
+
+
+
+
+ onChange('display_name', value)}
+ />
+
+
+ onChange('organization_name', value)}
+ />
+
+ prevValues.organization_sid !== curValues.organization_sid
+ }
+ >
+ {({ getFieldValue }: { getFieldValue: any }) => getFieldValue('organization_sid') && (
+
+
+
+ )}
+
+ {Object.keys(searchParams).some((key: string) => (
+ isArray(searchParams[key]) ? !isEmpty(searchParams[key]) : searchParams[key]))
+ && (
+
+ )}
+
+ );
+};
+
+export default Search;
diff --git a/web/packages/tca-manage/src/modules/teams/style.scss b/web/packages/tca-manage/src/modules/teams/style.scss
new file mode 100644
index 000000000..295427f54
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/teams/style.scss
@@ -0,0 +1,14 @@
+@import "@src/common/style/color.scss";
+
+.header {
+ padding: 0 24px;
+ border-bottom: 1px solid $grey-3;
+ :global(.ant-tabs-nav::before) {
+ border: none;
+ }
+}
+
+.filter-content {
+ padding: 0 24px;
+ // border-bottom: 1px solid $grey-3;
+}
diff --git a/web/packages/tca-manage/src/modules/teams/team-table.tsx b/web/packages/tca-manage/src/modules/teams/team-table.tsx
new file mode 100644
index 000000000..449a56601
--- /dev/null
+++ b/web/packages/tca-manage/src/modules/teams/team-table.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Table, Tooltip, Button, Tag } from 'coding-oa-uikit';
+import Stop from 'coding-oa-uikit/lib/icon/Stop';
+import Undo from 'coding-oa-uikit/lib/icon/Undo';
+
+// 项目内
+import { t } from '@src/i18n/i18next';
+import EllipsisTemplate from '@src/components/ellipsis';
+import { formatDateTime, getUserName } from '@src/utils';
+import { getProjectTeamRouter } from '@src/utils/getRoutePath';
+
+// 模块内
+import { TEAM_STATUS_CHOICES, TEAM_STATUS_ENUM } from './constants';
+
+const { Column } = Table;
+
+interface IProps {
+ dataSource: Array;
+ pagination: any;
+ onDelete: (team: any) => void;
+ onRecover: (team: any) => void;
+}
+
+const formatStatus = (status: any) => {
+ const info = status === TEAM_STATUS_ENUM.ACTIVE ? {
+ text: TEAM_STATUS_CHOICES[TEAM_STATUS_ENUM.ACTIVE],
+ color: 'success',
+ } : {
+ text: TEAM_STATUS_CHOICES[TEAM_STATUS_ENUM.INACTIVE],
+ color : 'red',
+ };
+ return (
+ {info.text}
+ );
+};
+
+const TeamTable = ({ dataSource, pagination, onDelete, onRecover }: IProps) => {
+ return (
+ <>
+ item.id} dataSource={dataSource}>
+ (
+ <>
+ {status === TEAM_STATUS_ENUM.ACTIVE
+ ?
+ {team.display_name}
+
+ : {team.display_name}
+ }
+
+ {team.name}
+
+ >
+ )}
+ />
+ {organization?.name}}
+ />
+ ) => admins.map((user: any) => getUserName(user)).join('; ')
+ }
+ />
+ formatDateTime(created_time)}
+ />
+ formatStatus(status)}
+ />
+ (
+ <>
+ {status === TEAM_STATUS_ENUM.ACTIVE &&
+ }
+ danger
+ onClick={() => onDelete(team)}
+ />
+ }
+ {status === TEAM_STATUS_ENUM.INACTIVE &&
+ }
+ onClick={() => onRecover(team)}
+ />
+ }
+ >
+ )}
+ />
+
+ >
+ );
+};
+
+export default TeamTable;
diff --git a/web/packages/tca-manage/src/root.tsx b/web/packages/tca-manage/src/root.tsx
index 3e72defe1..53c7ab199 100644
--- a/web/packages/tca-manage/src/root.tsx
+++ b/web/packages/tca-manage/src/root.tsx
@@ -6,6 +6,8 @@ import { get } from 'lodash';
// 项目内
import Users from '@src/modules/users';
+import Orgs from '@src/modules/orgs';
+import Teams from '@src/modules/teams';
import Jobs from '@src/modules/jobs';
import Nodes from '@src/modules/nodes';
import NodeProcess from '@src/modules/nodes/process';
@@ -26,6 +28,8 @@ const Root = () => {
+
+
diff --git a/web/packages/tca-manage/src/services/orgs.ts b/web/packages/tca-manage/src/services/orgs.ts
new file mode 100644
index 000000000..c67b2a2c9
--- /dev/null
+++ b/web/packages/tca-manage/src/services/orgs.ts
@@ -0,0 +1,30 @@
+import { get, post, put } from './index';
+import { MAIN_SERVER_API } from './common';
+
+/**
+ * 获取团队列表
+ * @param params 筛选参数
+ */
+export const getOrgs = (params: any = null) => get(`${MAIN_SERVER_API}/orgs/`, params);
+
+/**
+ * 获取团队信息
+ * @param orgSid 团队唯一标识
+ */
+export const getOrg = (orgSid: string) => get(`${MAIN_SERVER_API}/orgs/${orgSid}/`);
+
+/**
+ * 更新审批单
+ * @param applyId 审批单ID
+ * @param params 参数
+ */
+export const putOrgsPerm = (applyId: number, params: any) => put(`${MAIN_SERVER_API}/orgs/perms/${applyId}/`, params);
+
+export const postOrgLelel = (orgSid: string, params: any) => post(`${MAIN_SERVER_API}/orgs/${orgSid}/level/`, params);
+
+/**
+ * 禁用/恢复团队
+ * @param orgSid 团队唯一标识
+ * @param params 参数
+ */
+ export const putOrgStatus = (orgSid: string, params: any) => put(`${MAIN_SERVER_API}/orgs/${orgSid}/status/`, params);
diff --git a/web/packages/tca-manage/src/services/teams.ts b/web/packages/tca-manage/src/services/teams.ts
new file mode 100644
index 000000000..52368a9ef
--- /dev/null
+++ b/web/packages/tca-manage/src/services/teams.ts
@@ -0,0 +1,16 @@
+import { get, put } from './index';
+import { MAIN_SERVER_API } from './common';
+
+/**
+ * 获取项目组列表
+ * @param params
+ */
+export const getTeams = (params: any = null) => get(`${MAIN_SERVER_API}/teams/`, params);
+
+/**
+ * 禁用/恢复项目组
+ * @param orgSid 团队唯一标识
+ * @param teamName 项目唯一标识
+ * @param params 参数
+ */
+ export const putTeamStatus = (orgSid: string, teamName: string, params: any) => put(`${MAIN_SERVER_API}/orgs/${orgSid}/teams/${teamName}/status/`, params);
diff --git a/web/tca-deploy-source/build_zip/tca-analysis.zip b/web/tca-deploy-source/build_zip/tca-analysis.zip
index 1c35d4491..cfbe907db 100644
Binary files a/web/tca-deploy-source/build_zip/tca-analysis.zip and b/web/tca-deploy-source/build_zip/tca-analysis.zip differ
diff --git a/web/tca-deploy-source/build_zip/tca-layout.zip b/web/tca-deploy-source/build_zip/tca-layout.zip
index 0b68e7070..da703996e 100644
Binary files a/web/tca-deploy-source/build_zip/tca-layout.zip and b/web/tca-deploy-source/build_zip/tca-layout.zip differ
diff --git a/web/tca-deploy-source/build_zip/tca-manage.zip b/web/tca-deploy-source/build_zip/tca-manage.zip
index cdfd85296..6216b966a 100644
Binary files a/web/tca-deploy-source/build_zip/tca-manage.zip and b/web/tca-deploy-source/build_zip/tca-manage.zip differ
diff --git a/web/tca-deploy-source/conf/configs.json b/web/tca-deploy-source/conf/configs.json
index c1c5b2796..ad900ccd3 100644
--- a/web/tca-deploy-source/conf/configs.json
+++ b/web/tca-deploy-source/conf/configs.json
@@ -1,33 +1,74 @@
-[{
- "name": "tca-layout",
- "description": "tca layout微前端",
- "commitId": "9ecf7d4d4999f91c09dcd46015d39f2528abbcf8",
- "match": "/",
- "js": ["/static/tca-layout/runtime~tca-layout-7ee8a3c6.js", "/static/tca-layout/vendors~tca-layout-d2bb73ce.js", "/static/tca-layout/tca-layout-962558d8.js"],
- "css": ["/static/tca-layout/vendors~tca-layout-9b6df0cb.css", "/static/tca-layout/tca-layout-f88f8f50.css"],
- "prefix": ["/static/tca-layout/"]
-}, {
- "name": "login",
- "description": "login 登录微前端",
- "commitId": "2daeb9c5b97de9248e7106b4f6fa3212e9721ce0",
- "match": "^/login",
- "js": ["/static/login/runtime~login-2bb1a4ed.js", "/static/login/vendors~login-7db5b5da.js", "/static/login/login-82d49404.js"],
- "css": ["/static/login/vendors~login-5ad90a5b.css", "/static/login/login-95717dcd.css"],
- "prefix": ["/static/login/"]
-}, {
- "name": "tca-analysis",
- "description": "TCA Analysis 微前端",
- "commitId": "b949d0f432cb616231f531a44bce6b457b454d51",
- "match": "^/t/[^/]+/p/[^/]+/(code-analysis|repos|template|profile|group)",
- "js": ["/static/tca-analysis/runtime~tca-analysis-ee70aba9.js", "/static/tca-analysis/vendors~tca-analysis-4d35a288.js", "/static/tca-analysis/tca-analysis-5ccbca7e.js"],
- "css": ["/static/tca-analysis/vendors~tca-analysis-9b6df0cb.css", "/static/tca-analysis/tca-analysis-fbc8addf.css"],
- "prefix": ["/static/tca-analysis/"]
-}, {
- "name": "tca-manage",
- "description": "TCA 后台管理微前端",
- "commitId": "a09c9d85965e98e0aa3bd849b2fb8f091fb48ba9",
- "match": "^/manage",
- "js": ["/static/tca-manage/runtime~tca-manage-0ae45884.js", "/static/tca-manage/vendors~tca-manage-c704461d.js", "/static/tca-manage/tca-manage-625c4e91.js"],
- "css": ["/static/tca-manage/vendors~tca-manage-9b6df0cb.css", "/static/tca-manage/tca-manage-3bff30ee.css"],
- "prefix": ["/static/tca-manage/"]
-}]
\ No newline at end of file
+[
+ {
+ "name": "tca-layout",
+ "description": "tca layout微前端",
+ "commitId": "707f4fa8507d86d5fb0c18b9ce78ec1dab40e5c3",
+ "match": "/",
+ "js": [
+ "/static/tca-layout/runtime~tca-layout-7ee8a3c6.js",
+ "/static/tca-layout/vendors~tca-layout-d2bb73ce.js",
+ "/static/tca-layout/tca-layout-b357edd2.js"
+ ],
+ "css": [
+ "/static/tca-layout/vendors~tca-layout-9b6df0cb.css",
+ "/static/tca-layout/tca-layout-f634d067.css"
+ ],
+ "prefix": [
+ "/static/tca-layout/"
+ ]
+ },
+ {
+ "name": "login",
+ "description": "login 登录微前端",
+ "commitId": "2daeb9c5b97de9248e7106b4f6fa3212e9721ce0",
+ "match": "^/login",
+ "js": [
+ "/static/login/runtime~login-2bb1a4ed.js",
+ "/static/login/vendors~login-7db5b5da.js",
+ "/static/login/login-82d49404.js"
+ ],
+ "css": [
+ "/static/login/vendors~login-5ad90a5b.css",
+ "/static/login/login-95717dcd.css"
+ ],
+ "prefix": [
+ "/static/login/"
+ ]
+ },
+ {
+ "name": "tca-analysis",
+ "description": "TCA Analysis 微前端",
+ "commitId": "f7cd531afb070241f8393058abc278e763c7042a",
+ "match": "^/t/[^/]+/p/[^/]+/(code-analysis|repos|template|profile|group)",
+ "js": [
+ "/static/tca-analysis/runtime~tca-analysis-ee70aba9.js",
+ "/static/tca-analysis/vendors~tca-analysis-4d35a288.js",
+ "/static/tca-analysis/tca-analysis-e7cf0336.js"
+ ],
+ "css": [
+ "/static/tca-analysis/vendors~tca-analysis-9b6df0cb.css",
+ "/static/tca-analysis/tca-analysis-3c510ffb.css"
+ ],
+ "prefix": [
+ "/static/tca-analysis/"
+ ]
+ },
+ {
+ "name": "tca-manage",
+ "description": "TCA 后台管理微前端",
+ "commitId": "707f4fa8507d86d5fb0c18b9ce78ec1dab40e5c3",
+ "match": "^/manage",
+ "js": [
+ "/static/tca-manage/runtime~tca-manage-f4c30b2c.js",
+ "/static/tca-manage/vendors~tca-manage-2e89801d.js",
+ "/static/tca-manage/tca-manage-8fc73ff4.js"
+ ],
+ "css": [
+ "/static/tca-manage/vendors~tca-manage-9b6df0cb.css",
+ "/static/tca-manage/tca-manage-616e9f49.css"
+ ],
+ "prefix": [
+ "/static/tca-manage/"
+ ]
+ }
+]
\ No newline at end of file