diff --git a/scaleph-dao/src/main/java/cn/sliew/scaleph/dao/entity/master/resource/ResourceJar.java b/scaleph-dao/src/main/java/cn/sliew/scaleph/dao/entity/master/resource/ResourceJar.java index aa4a6e076..efb540509 100644 --- a/scaleph-dao/src/main/java/cn/sliew/scaleph/dao/entity/master/resource/ResourceJar.java +++ b/scaleph-dao/src/main/java/cn/sliew/scaleph/dao/entity/master/resource/ResourceJar.java @@ -36,9 +36,6 @@ public class ResourceJar extends BaseDO { private static final long serialVersionUID = 1L; - @TableField("`group`") - private String group; - @TableField("file_name") private String fileName; diff --git a/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/resource/ResourceJarMapper.xml b/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/resource/ResourceJarMapper.xml index 2364ba333..6e7d0f43b 100644 --- a/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/resource/ResourceJarMapper.xml +++ b/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/resource/ResourceJarMapper.xml @@ -26,7 +26,6 @@ - @@ -35,7 +34,7 @@ id, creator, create_time, editor, update_time, - `group`, file_name, `path`, remark + file_name, `path`, remark diff --git a/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/security/SecUserRoleMapper.xml b/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/security/SecUserRoleMapper.xml index 614cfd678..bad8979d4 100644 --- a/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/security/SecUserRoleMapper.xml +++ b/scaleph-dao/src/main/resources/cn/sliew/scaleph/dao/mapper/master/security/SecUserRoleMapper.xml @@ -37,7 +37,7 @@ sec_user t1 JOIN sec_user_role t2 ON t1.id = t2.user_id WHERE - t2.role_id = 1 + t2.role_id = #{roleId} AND t1.`status` = #{status} diff --git a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/convert/JarConvert.java b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/convert/JarConvert.java index f9926b77d..fe2133e56 100644 --- a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/convert/JarConvert.java +++ b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/convert/JarConvert.java @@ -35,8 +35,7 @@ public interface JarConvert extends BaseConvert { default JarListParam convert(ResourceListParam param) { JarListParam target = BeanUtil.copy(param, new JarListParam()); - target.setGroup(param.getLabel()); - target.setFileName(param.getName()); + target.setFileName(param.getLabel()); return target; } diff --git a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/impl/JarServiceImpl.java b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/impl/JarServiceImpl.java index 3d30430dc..675fa17c8 100644 --- a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/impl/JarServiceImpl.java +++ b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/impl/JarServiceImpl.java @@ -80,7 +80,6 @@ public Page list(JarListParam param) throws IOException { final Page page = jarMapper.selectPage( new Page<>(param.getCurrent(), param.getPageSize()), Wrappers.lambdaQuery(ResourceJar.class) - .eq(StringUtils.hasText(param.getGroup()), ResourceJar::getGroup, param.getGroup()) .like(StringUtils.hasText(param.getFileName()), ResourceJar::getFileName, param.getFileName())); Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); @@ -101,7 +100,7 @@ public void upload(JarUploadParam param, MultipartFile file) throws IOException String fileName = file.getOriginalFilename(); Path path = null; try (InputStream inputStream = file.getInputStream()) { - path = fileSystemService.upload(inputStream, getJarPath(param.getGroup(), fileName)); + path = fileSystemService.upload(inputStream, getJarPath(fileName)); } ResourceJar record = new ResourceJar(); BeanUtils.copyProperties(param, record); @@ -134,11 +133,10 @@ public void delete(Long id) throws IOException { jarMapper.deleteById(id); } - private String getJarPath(String group, String fileName) { - return String.format("%s/%s/%s", getJarRootPath(), group, fileName); + private String getJarPath(String fileName) { + return String.format("%s/%s", getJarRootPath(), fileName); } - private String getJarRootPath() { return "jar"; } diff --git a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarListParam.java b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarListParam.java index 4ae6c32d8..27d553528 100644 --- a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarListParam.java +++ b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarListParam.java @@ -27,9 +27,6 @@ @EqualsAndHashCode(callSuper = true) public class JarListParam extends PaginationParam { - @Schema(description = "jar group") - private String group; - @Schema(description = "文件名称。支持模糊匹配") private String fileName; diff --git a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarUploadParam.java b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarUploadParam.java index 127599d71..11ec99ef2 100644 --- a/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarUploadParam.java +++ b/scaleph-resource/src/main/java/cn/sliew/scaleph/resource/service/param/JarUploadParam.java @@ -21,15 +21,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import javax.validation.constraints.NotBlank; - @Data public class JarUploadParam { - - @NotBlank - @Schema(description = "jar group") - private String group; - + @Schema(description = "备注") private String remark; } diff --git a/scaleph-ui-react/config/routes.ts b/scaleph-ui-react/config/routes.ts index 1fcc0a931..a68cb2902 100644 --- a/scaleph-ui-react/config/routes.ts +++ b/scaleph-ui-react/config/routes.ts @@ -150,6 +150,13 @@ export default [ pCode: PRIVILEGE_CODE.workspaceClusterConfigShow, access: 'normalRouteFilter' }, + { + path: '/workspace/flink/kubernetes/template/steps', + exact: true, + component: './Project/Workspace/Kubernetes/Template/Steps', + pCode: PRIVILEGE_CODE.workspaceClusterConfigShow, + access: 'normalRouteFilter' + }, { path: '/workspace/flink/kubernetes/template/detail', exact: true, diff --git a/scaleph-ui-react/src/locales/zh-CN/pages/base.ts b/scaleph-ui-react/src/locales/zh-CN/pages/base.ts index 2ff2fae0e..a7bc0531b 100644 --- a/scaleph-ui-react/src/locales/zh-CN/pages/base.ts +++ b/scaleph-ui-react/src/locales/zh-CN/pages/base.ts @@ -14,6 +14,16 @@ export default { 'app.common.operate.label': '操作', 'app.common.operate.success': '操作成功', 'app.common.operate.new.label': '新增', + 'app.common.operate.new.roles': '分配角色', + 'app.common.operate.new.rolesUser': '角色分配用户', + 'app.common.operate.new.rolesWeb': '角色配置web资源', + 'app.common.operate.new.user': '配置用户', + 'app.common.operate.new.webs': '配置web资源', + 'app.common.operate.new.accreditUser': '已授权用户', + 'app.common.operate.new.accreditRoles': '已授权角色', + 'app.common.operate.new.notAccreditUser': '未授权用户', + 'app.common.operate.new.notAccreditRoles': '未授权角色', + 'app.common.operate.new.assets': '权限配置', 'app.common.operate.new.success': '新增成功', 'app.common.operate.new.failure': '新增失败', 'app.common.operate.new.directive': '没有?去创建', diff --git a/scaleph-ui-react/src/locales/zh-CN/pages/project.ts b/scaleph-ui-react/src/locales/zh-CN/pages/project.ts index a6c40469c..d06611bde 100644 --- a/scaleph-ui-react/src/locales/zh-CN/pages/project.ts +++ b/scaleph-ui-react/src/locales/zh-CN/pages/project.ts @@ -818,11 +818,12 @@ export default { // Sql 'pages.project.di.step.sql.query': 'SQL', - 'pages.project.flink.kubernetes.deployment.template': 'Deployment Template', - 'pages.project.flink.kubernetes.deployment.template.name': '名称', - 'pages.project.flink.kubernetes.deployment.template.deploymentKind': '部署模式', - 'pages.project.flink.kubernetes.deployment.template.namespace': 'Namespace', - 'pages.project.flink.kubernetes.deployment.template.define': '模板定义', + 'pages.project.flink.kubernetes.template': 'Template', + 'pages.project.flink.kubernetes.template.name': '名称', + 'pages.project.flink.kubernetes.template.deploymentKind': '部署模式', + 'pages.project.flink.kubernetes.template.namespace': 'Namespace', + 'pages.project.flink.kubernetes.template.detail': '模板详情', + 'pages.project.flink.kubernetes.template.step.base': '基础信息', 'pages.project.flink.kubernetes.template.step.advanced.configOptions': 'ConfigOption', 'pages.project.flink.kubernetes.template.step.advanced.configOptions.key': 'Config', 'pages.project.flink.kubernetes.template.step.advanced.configOptions.value': 'Value', diff --git a/scaleph-ui-react/src/locales/zh-CN/pages/resource.ts b/scaleph-ui-react/src/locales/zh-CN/pages/resource.ts index 9ba8a5ad3..b1972973f 100644 --- a/scaleph-ui-react/src/locales/zh-CN/pages/resource.ts +++ b/scaleph-ui-react/src/locales/zh-CN/pages/resource.ts @@ -21,7 +21,6 @@ export default { 'YARN 上传 core-site.xml 和 hdfs-site.xml, ' + 'Kubernetes 上传 kubeconfig 文件', 'pages.resource.jar': '公共 Jar', - 'pages.resource.jar.group': 'Group', 'pages.resource.jar.file': 'Jar', 'pages.resource.jar.fileName': '文件名', 'pages.resource.jar.path': '存储路径', diff --git a/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateDetail.ts b/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateDetail.ts new file mode 100644 index 000000000..552ea1cd0 --- /dev/null +++ b/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateDetail.ts @@ -0,0 +1,55 @@ +import {WsFlinkKubernetesTemplate} from "@/services/project/typings"; +import { Reducer, Effect } from "umi"; +import {WsFlinkKubernetesTemplateService} from "@/services/project/WsFlinkKubernetesTemplateService"; +import YAML from "yaml"; + +export interface StateType { + template: WsFlinkKubernetesTemplate, + templateYaml: string + templateYamlWithDefault: string +} + +export interface ModelType { + namespace: string; + + state: StateType; + + effects: { + queryTemplate: Effect; + }; + + reducers: { + updateTemplate: Reducer; + }; +} + +const model: ModelType = { + namespace: "flinkKubernetesTemplateDetail", + + state: { + template: null, + templateYaml: null, + templateYamlWithDefault: null + }, + + effects: { + *editTemplate({ payload }, { call, put }) { + const { data } = yield call(WsFlinkKubernetesTemplateService.asYaml, payload); + const response = yield call(WsFlinkKubernetesTemplateService.asYamlWithDefault, payload); + yield put({ type: 'updateTemplate', payload: {template: payload, templateYaml: YAML.stringify(data), templateYamlWithDefault: YAML.stringify(response.data)} }); + }, + }, + + reducers: { + updateTemplate(state, { payload }) { + return { + ...state, + template: payload.template, + templateYaml: payload.templateYaml, + templateYamlWithDefault: payload.templateYamlWithDefault, + }; + }, + }, +}; + +export default model; diff --git a/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateSteps.ts b/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateSteps.ts new file mode 100644 index 000000000..0e17e8ebd --- /dev/null +++ b/scaleph-ui-react/src/models/project/workspace/kubernetes/template/flinkKubernetesTemplateSteps.ts @@ -0,0 +1,55 @@ +import {WsFlinkKubernetesTemplate} from "@/services/project/typings"; +import { Reducer, Effect } from "umi"; +import {WsFlinkKubernetesTemplateService} from "@/services/project/WsFlinkKubernetesTemplateService"; +import YAML from "yaml"; + +export interface StateType { + template: WsFlinkKubernetesTemplate, + templateYaml: string + templateYamlWithDefault: string +} + +export interface ModelType { + namespace: string; + + state: StateType; + + effects: { + queryTemplate: Effect; + }; + + reducers: { + updateTemplate: Reducer; + }; +} + +const model: ModelType = { + namespace: "flinkKubernetesTemplateSteps", + + state: { + template: null, + templateYaml: null, + templateYamlWithDefault: null + }, + + effects: { + *editTemplate({ payload }, { call, put }) { + const { data } = yield call(WsFlinkKubernetesTemplateService.asYaml, payload); + const response = yield call(WsFlinkKubernetesTemplateService.asYamlWithDefault, payload); + yield put({ type: 'updateTemplate', payload: {template: payload, templateYaml: YAML.stringify(data), templateYamlWithDefault: YAML.stringify(response.data)} }); + }, + }, + + reducers: { + updateTemplate(state, { payload }) { + return { + ...state, + template: payload.template, + templateYaml: payload.templateYaml, + templateYamlWithDefault: payload.templateYamlWithDefault, + }; + }, + }, +}; + +export default model; diff --git a/scaleph-ui-react/src/models/useMainSize.ts b/scaleph-ui-react/src/models/useMainSize.ts new file mode 100644 index 000000000..07436b494 --- /dev/null +++ b/scaleph-ui-react/src/models/useMainSize.ts @@ -0,0 +1,26 @@ +import { useLayoutEffect, useState } from "react"; +import { throttle } from "lodash"; + +export default (mainCl = ".ant-pro-basicLayout-content") => { + const [container, setContainer] = useState<{ height: number; width: number }>({ + width: 100, + height: 100 + }); + // const mainCl = '.ant-pro-basicLayout-content'; + useLayoutEffect(() => { + const handler = throttle(() => { + const rt = document.querySelector(mainCl)?.getBoundingClientRect(); + setContainer({ + height: rt?.height, + width: rt?.width + }); + }, 100); + handler(); + window.addEventListener("resize", handler, false); + return () => { + window.removeEventListener("resize", handler, false); + }; + }, []); + + return container; +}; diff --git a/scaleph-ui-react/src/pages/Admin/Resource/Web/components/TransferTable.tsx b/scaleph-ui-react/src/pages/Admin/Resource/Web/components/TransferTable.tsx new file mode 100644 index 000000000..2ae7fb9a3 --- /dev/null +++ b/scaleph-ui-react/src/pages/Admin/Resource/Web/components/TransferTable.tsx @@ -0,0 +1,56 @@ +import { Table, Transfer } from 'antd'; +import { difference } from 'lodash'; // 注意这里改为大写的Difference +import React from 'react'; + +// 定义组件的Props类型 +interface Props { + leftColumns: any[]; // 左侧表格列配置数组 + rightColumns: any[]; // 右侧表格列配置数组 + containerHeight: number; // 容器高度 +} + +// 使用React.FC声明函数组件,并传入Props类型 +const TableTransfer: React.FC = ({ leftColumns, rightColumns, ...restProps }) => ( + + {({ + direction, + filteredItems, + onItemSelectAll, + onItemSelect, + selectedKeys: listSelectedKeys, + disabled: listDisabled, + }) => { + const columns = direction === 'left' ? leftColumns : rightColumns; + // 表格行选择配置 + const rowSelection = { + onSelectAll(selected: boolean, selectedRows: any[]) { + const treeSelectedKeys = selectedRows.map(({ key }) => key); + const diffKeys = selected + ? difference(treeSelectedKeys, listSelectedKeys) + : difference(listSelectedKeys, treeSelectedKeys); + onItemSelectAll(diffKeys, selected); + }, + onSelect: ({ id }: { id: string }, selected: boolean) => { + onItemSelect(id, selected); + }, + selectedRowKeys: listSelectedKeys, + }; + + return ( + + ); + }} + +); +export default React.memo(TableTransfer); diff --git a/scaleph-ui-react/src/pages/Admin/Resource/Web/components/WebAssugnRoles.tsx b/scaleph-ui-react/src/pages/Admin/Resource/Web/components/WebAssugnRoles.tsx new file mode 100644 index 000000000..7550f0aef --- /dev/null +++ b/scaleph-ui-react/src/pages/Admin/Resource/Web/components/WebAssugnRoles.tsx @@ -0,0 +1,170 @@ +import mainHeight from '@/models/useMainSize'; +import { SecResourceWeb } from '@/services/admin/typings'; +import { AuthService } from '@/services/auth'; +import { Button, Card, Form, message, Modal, Space } from 'antd'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useIntl } from 'umi'; +import TableTransfer from './TransferTable'; + +// 定义组件 Props 类型 +interface ModalFormParentProps { + data: T; + visible: boolean; + onVisibleChange?: (visible: boolean) => void; + onCancel: () => void; + onOK?: (values: any) => void; +} + +const WebResourceForm: React.FC> = ({ + data, + visible, + onVisibleChange, + onCancel, +}) => { + const intl = useIntl(); + const [form] = Form.useForm(); + const containerInfo = mainHeight('.ant-layout-content'); + const [roleLists, setRoleLists] = useState([]); + + // 角色表格列配置 + const tableColumns = [ + { + dataIndex: 'name', + title: '角色名称', + width: 300, + }, + { + dataIndex: 'desc', + title: '描述', + width: 300, + }, + ]; + + // 角色类型定义 + interface Role { + id: string; + name: string; + resourceAuthorized: number; + checkOut?: number; + } + + // 合并数组 + function mergeArrays(array1: Role[], array2: Role[]): Role[] { + array1.forEach((obj, index) => { + obj.checkOut = 0; + }); + array2.forEach((obj, index) => { + obj.checkOut = 1; + }); + return [...array1, ...array2]; + } + + // 异步获取数据 + const fetchData = useCallback(async () => { + try { + const res1 = await AuthService.unauthorizedRoles({ resourceWebId: data?.id }); + const res2 = await AuthService.authorizedRoles({ resourceWebId: data?.id }); + if (res1?.records && res2?.records) { + const mergedArray = mergeArrays(res1.records, res2.records); + setRoleLists(mergedArray); + } + } catch (error) { + console.error(error); + } + }, [data]); + + useEffect(() => { + if (data) { + fetchData(); + } + }, [data, fetchData]); + + // 页面标题 + const returnTitle = useMemo(() => { + return ( + + {` ${intl.formatMessage({ id: `menu.${data?.name}` })}-资源分配详情`} + + ); + }, [data, intl]); + + // 获取选中角色的 id 数组 + const originTargetKeys = useMemo(() => { + return roleLists?.filter((item) => item.checkOut === 1).map((item) => item.id); + }, [roleLists]); + + // 过滤方法 + const handleFilter = useCallback((inputValue, item) => { + return item?.name.indexOf(inputValue) !== -1; + }, []); + + // 角色转移事件处理 + const handleChange = useCallback( + async (targetKeys, direction, moveKeys) => { + const roleIds = moveKeys.map((item: string | number) => +item); + const params = { + resourceWebId: data?.id, + roleIds: roleIds, + }; + if (direction === 'right') { + // 绑定角色资源 + await AuthService.resourceWebRoles(params).then((res) => { + if (res?.success) { + message.success(intl.formatMessage({ id: 'app.common.operate.edit.success' }), 2); + } + }); + } else { + // 删除绑定的角色资源 + await AuthService.resourceWebRolesDelete(params).then((res) => { + message.success(intl.formatMessage({ id: 'app.common.operate.edit.success' }), 2); + }); + } + fetchData(); + }, + [data, fetchData, intl], + ); + + return ( + + {intl.formatMessage({ id: 'app.common.operate.close.label' })} + , + ]} + > +
+ + record.id} + onChange={handleChange} + filterOption={handleFilter} + listStyle={{ + width: 500, + }} + leftColumns={tableColumns} + rightColumns={tableColumns} + /> + +
+
+ ); +}; + +export default WebResourceForm; diff --git a/scaleph-ui-react/src/pages/Admin/Resource/Web/index.tsx b/scaleph-ui-react/src/pages/Admin/Resource/Web/index.tsx index d50592fe2..f21ddd1e9 100644 --- a/scaleph-ui-react/src/pages/Admin/Resource/Web/index.tsx +++ b/scaleph-ui-react/src/pages/Admin/Resource/Web/index.tsx @@ -1,9 +1,10 @@ import { PRIVILEGE_CODE } from '@/constant'; +import WebAssugnRoles from '@/pages/Admin/Resource/Web/components/WebAssugnRoles'; import WebResourceForm from '@/pages/Admin/Resource/Web/components/WebResourceForm'; import { PrivilegeService } from '@/services/admin/privilege.service'; import { ResourceWebService } from '@/services/admin/resourceWeb.service'; import { SecResourceWeb } from '@/services/admin/typings'; -import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; +import { DeleteOutlined, EditOutlined, FormOutlined, PlusOutlined } from '@ant-design/icons'; import { ActionType, ProColumns, ProFormInstance, ProTable } from '@ant-design/pro-components'; import { Button, message, Modal, Space, Tag, Tooltip } from 'antd'; import { isEmpty } from 'lodash'; @@ -21,6 +22,10 @@ const WebResourceWeb: React.FC = () => { parent: SecResourceWeb; data: SecResourceWeb; }>({ visiable: false, parent: {}, data: {} }); + const [webAssignRoles, setWebAssignRoles] = useState<{ + visiable: boolean; + data: SecResourceWeb; + }>({ visiable: false, parent: {}, data: {} }); const onExpand = (expanded: boolean, record: SecResourceWeb) => { if (expanded && record.children && isEmpty(record.children)) { @@ -44,6 +49,9 @@ const WebResourceWeb: React.FC = () => { { title: intl.formatMessage({ id: 'pages.admin.resource.web.name' }), dataIndex: 'name', + // render: (dom, entity) => { + // return intl.formatMessage({ id: `menu.${entity?.name}` }); + // }, }, { title: intl.formatMessage({ id: 'pages.admin.resource.web.path' }), @@ -91,12 +99,22 @@ const WebResourceWeb: React.FC = () => { title: intl.formatMessage({ id: 'app.common.operate.label' }), dataIndex: 'actions', align: 'center', - width: 120, + width: 160, fixed: 'right', valueType: 'option', render: (_, record) => ( <> + {access.canAccess(PRIVILEGE_CODE.datadevProjectEdit) && ( + + , + ]} + > +
+ + record.id} + onChange={handleChange} + filterOption={handleFilter} + listStyle={{ + width: 500, + }} + leftColumns={tableColumns} + rightColumns={tableColumns} + /> + +
+ + ); +}; + +export default WebResourceForm; diff --git a/scaleph-ui-react/src/pages/Admin/Role/index.tsx b/scaleph-ui-react/src/pages/Admin/Role/index.tsx index 5b34a7ab9..e1b98455d 100644 --- a/scaleph-ui-react/src/pages/Admin/Role/index.tsx +++ b/scaleph-ui-react/src/pages/Admin/Role/index.tsx @@ -1,13 +1,21 @@ -import {useAccess, useIntl} from "umi"; -import React, {useRef, useState} from "react"; -import {Button, message, Modal, Space, Tag, Tooltip} from "antd"; -import {DeleteOutlined, EditOutlined} from "@ant-design/icons"; -import {ActionType, ProColumns, ProFormInstance, ProFormSelect, ProTable} from "@ant-design/pro-components"; -import {DICT_TYPE, PRIVILEGE_CODE} from "@/constant"; -import {SecRole} from "@/services/admin/typings"; -import {RoleService} from "@/services/admin/role.service"; -import RoleForm from "@/pages/Admin/Role/components/RoleForm"; -import {DictDataService} from "@/services/admin/dictData.service"; +import { DICT_TYPE, PRIVILEGE_CODE } from '@/constant'; +import RoleForm from '@/pages/Admin/Role/components/RoleForm'; +import { DictDataService } from '@/services/admin/dictData.service'; +import { RoleService } from '@/services/admin/role.service'; +import { SecRole } from '@/services/admin/typings'; +import { DeleteOutlined, EditOutlined, FormOutlined, SelectOutlined } from '@ant-design/icons'; +import { + ActionType, + ProColumns, + ProFormInstance, + ProFormSelect, + ProTable, +} from '@ant-design/pro-components'; +import { Button, message, Modal, Space, Tag, Tooltip } from 'antd'; +import React, { useRef, useState } from 'react'; +import { useAccess, useIntl } from 'umi'; +import WebAssugnRoles from './components/WebAssugnRoles'; +import ResourceWebs from './components/ResourceWebs'; const RoleWeb: React.FC = () => { const intl = useIntl(); @@ -18,27 +26,37 @@ const RoleWeb: React.FC = () => { const [roleFormData, setRoleFormData] = useState<{ visiable: boolean; data: SecRole; - }>({visiable: false, data: {}}); + }>({ visiable: false, data: {} }); + + const [webAssignRoles, setWebAssignRoles] = useState<{ + visiable: boolean; + data: SecRole; + }>({ visiable: false, parent: {}, data: {} }); + + const [resourceWebs, setResourceWebs] = useState<{ + visiable: boolean; + data: SecRole; + }>({ visiable: false, parent: {}, data: {} }); const tableColumns: ProColumns[] = [ { - title: intl.formatMessage({id: 'pages.admin.role.name'}), + title: intl.formatMessage({ id: 'pages.admin.role.name' }), dataIndex: 'name', - width: 200 + width: 200, }, { - title: intl.formatMessage({id: 'pages.admin.role.code'}), + title: intl.formatMessage({ id: 'pages.admin.role.code' }), dataIndex: 'code', hideInSearch: true, - width: 200 + width: 200, }, { - title: intl.formatMessage({id: 'pages.admin.role.type'}), + title: intl.formatMessage({ id: 'pages.admin.role.type' }), dataIndex: 'type', render: (dom, entity) => { - return ({entity.type?.label}) + return {entity.type?.label}; }, - renderFormItem: (item, {defaultRender, ...rest}, form) => { + renderFormItem: (item, { defaultRender, ...rest }, form) => { return ( { /> ); }, - width: 200 + width: 200, }, { - title: intl.formatMessage({id: 'pages.admin.role.status'}), + title: intl.formatMessage({ id: 'pages.admin.role.status' }), dataIndex: 'status', render: (dom, entity) => { - return ({entity.status?.label}) + return {entity.status?.label}; }, - renderFormItem: (item, {defaultRender, ...rest}, form) => { + renderFormItem: (item, { defaultRender, ...rest }, form) => { return ( { /> ); }, - width: 200 + width: 200, }, { - title: intl.formatMessage({id: 'app.common.data.remark'}), + title: intl.formatMessage({ id: 'app.common.data.remark' }), dataIndex: 'remark', hideInSearch: true, width: 180, }, { - title: intl.formatMessage({id: 'app.common.data.createTime'}), + title: intl.formatMessage({ id: 'app.common.data.createTime' }), dataIndex: 'createTime', hideInSearch: true, width: 180, }, { - title: intl.formatMessage({id: 'app.common.data.updateTime'}), + title: intl.formatMessage({ id: 'app.common.data.updateTime' }), dataIndex: 'updateTime', hideInSearch: true, width: 180, }, { - title: intl.formatMessage({id: 'app.common.operate.label'}), + title: intl.formatMessage({ id: 'app.common.operate.label' }), dataIndex: 'actions', align: 'center', width: 120, @@ -94,34 +112,60 @@ const RoleWeb: React.FC = () => { render: (_, record) => ( <> + {access.canAccess(PRIVILEGE_CODE.datadevProjectEdit) && ( + + )} {access.canAccess(PRIVILEGE_CODE.roleDelete) && ( - + ), access.canAccess(PRIVILEGE_CODE.roleDelete) && ( @@ -170,15 +214,19 @@ const RoleWeb: React.FC = () => { disabled={selectedRows.length < 1} onClick={() => { Modal.confirm({ - title: intl.formatMessage({id: 'app.common.operate.delete.confirm.title'}), - content: intl.formatMessage({id: 'app.common.operate.delete.confirm.content'}), - okText: intl.formatMessage({id: 'app.common.operate.confirm.label'}), - okButtonProps: {danger: true}, - cancelText: intl.formatMessage({id: 'app.common.operate.cancel.label'}), + title: intl.formatMessage({ id: 'app.common.operate.delete.confirm.title' }), + content: intl.formatMessage({ + id: 'app.common.operate.delete.confirm.content', + }), + okText: intl.formatMessage({ id: 'app.common.operate.confirm.label' }), + okButtonProps: { danger: true }, + cancelText: intl.formatMessage({ id: 'app.common.operate.cancel.label' }), onOk() { RoleService.deleteBatch(selectedRows).then((d) => { if (d.success) { - message.success(intl.formatMessage({id: 'app.common.operate.delete.success'})); + message.success( + intl.formatMessage({ id: 'app.common.operate.delete.success' }), + ); actionRef.current?.reload(); } }); @@ -186,12 +234,12 @@ const RoleWeb: React.FC = () => { }); }} > - {intl.formatMessage({id: 'app.common.operate.delete.label'})} + {intl.formatMessage({ id: 'app.common.operate.delete.label' })} ), ], }} - pagination={{showQuickJumper: true, showSizeChanger: true, defaultPageSize: 10}} + pagination={{ showQuickJumper: true, showSizeChanger: true, defaultPageSize: 10 }} rowSelection={{ fixed: true, onChange: (selectedRowKeys, selectedRows, info) => setSelectedRows(selectedRows), @@ -202,17 +250,38 @@ const RoleWeb: React.FC = () => { {roleFormData.visiable && ( setRoleFormData({visiable: false, data: {}})} + onCancel={() => setRoleFormData({ visiable: false, data: {} })} onVisibleChange={(visiable) => { - setRoleFormData({visiable: visiable, data: {}}); + setRoleFormData({ visiable: visiable, data: {} }); actionRef.current?.reload(); }} data={roleFormData.data} /> )} + {webAssignRoles.visiable && ( + setWebAssignRoles({ visiable: false, data: {} })} + onVisibleChange={(visiable) => { + setWebAssignRoles({ visiable: visiable, data: {} }); + actionRef.current?.reload(); + }} + data={webAssignRoles.data} + /> + )} + {resourceWebs.visiable && ( + setResourceWebs({ visiable: false, data: {} })} + onVisibleChange={(visiable) => { + setResourceWebs({ visiable: visiable, data: {} }); + actionRef.current?.reload(); + }} + data={resourceWebs.data} + /> + )} ); - -} +}; export default RoleWeb; diff --git a/scaleph-ui-react/src/pages/Admin/User/components/UserRoles.tsx b/scaleph-ui-react/src/pages/Admin/User/components/UserRoles.tsx new file mode 100644 index 000000000..09f8369fb --- /dev/null +++ b/scaleph-ui-react/src/pages/Admin/User/components/UserRoles.tsx @@ -0,0 +1,172 @@ +import mainHeight from '@/models/useMainSize'; +import TableTransfer from '@/pages/Admin/Resource/Web/components/TransferTable'; +import { SecResourceWeb } from '@/services/admin/typings'; +import { AuthService } from '@/services/auth'; +import { Button, Card, Form, message, Modal, Space } from 'antd'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useIntl } from 'umi'; + +// 定义组件 Props 类型 +interface ModalFormParentProps { + data: T; + visible: boolean; + onVisibleChange?: (visible: boolean) => void; + onCancel: () => void; + onOK?: (values: any) => void; +} + +const WebResourceForm: React.FC> = ({ + data, + visible, + onVisibleChange, + onCancel, +}) => { + const intl = useIntl(); + const [form] = Form.useForm(); + const containerInfo = mainHeight('.ant-layout-content'); + const [roleLists, setRoleLists] = useState([]); + + // 角色表格列配置 + const tableColumns = [ + { + dataIndex: 'name', + title: '角色名称', + width: 300, + }, + { + dataIndex: 'status.label', + title: '角色状态', + width: 300, + render: (text: any, record: { status: { label: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined; }; }) => ( + {record.status.label} + ), + }, + ]; + + // 角色类型定义 + interface Role { + id: string; + name: string; + resourceAuthorized: number; + checkOut?: number; + } + + // 合并数组 + function mergeArrays(array1: any, array2: any): any { + array1.forEach((obj: { checkOut: number; }, index: any) => { + obj.checkOut = 0; + }); + array2.forEach((obj: { checkOut: number; }, index: any) => { + obj.checkOut = 1; + }); + return [...array1, ...array2]; + } + // 异步获取数据 + const fetchData = useCallback(async () => { + try { + const res1 = await AuthService.requestUnauthorizedRoles({ userId: data?.id }); + const res2 = await AuthService.requestUserAuthorizedRoles({ userId: data?.id }); + if (res1 && res2) { + const mergedArray = mergeArrays(res1, res2); + setRoleLists(mergedArray); + } + } catch (error) { + console.error(error); + } + }, [data]); + + useEffect(() => { + if (data) { + fetchData(); + } + }, [data, fetchData]); + + // 页面标题 + const returnTitle = useMemo(() => { + return ( + + {` ${intl.formatMessage({ id: `menu.${data?.userName}` })}-${intl.formatMessage({ id: 'app.common.operate.new.roles' })}`} + + ); + }, [data, intl]); + + // 获取选中角色的 id 数组 + const originTargetKeys = useMemo(() => { + return roleLists?.filter((item) => item.checkOut === 1).map((item) => item.id); + }, [roleLists]); + + // 过滤方法 + const handleFilter = useCallback((inputValue, item) => { + return item?.name.indexOf(inputValue) !== -1; + }, []); + + // 角色转移事件处理 + const handleChange = useCallback( + async (targetKeys, direction, moveKeys) => { + const roleIds = moveKeys.map((item: string | number) => +item); + const params = { + userId: data?.id, + roleIds: roleIds, + }; + if (direction === 'right') { + // 批量为角色绑定用户 + await AuthService.requestUserRoles(params).then((res) => { + if (res?.success) { + message.success(intl.formatMessage({ id: 'app.common.operate.edit.success' }), 2); + } + }); + } else { + // 批量为角色解除用户绑定 + await AuthService.requestDeleteUserRoles(params).then((res) => { + message.success(intl.formatMessage({ id: 'app.common.operate.edit.success' }), 2); + }); + } + fetchData(); + }, + [data, fetchData, intl], + ); + + return ( + + {intl.formatMessage({ id: 'app.common.operate.close.label' })} + , + ]} + > +
+ + record.id} + onChange={handleChange} + filterOption={handleFilter} + listStyle={{ + width: 500, + }} + leftColumns={tableColumns} + rightColumns={tableColumns} + /> + +
+
+ ); +}; + +export default WebResourceForm; diff --git a/scaleph-ui-react/src/pages/Admin/User/index.tsx b/scaleph-ui-react/src/pages/Admin/User/index.tsx index 6dcb4247c..ca7b9b90b 100644 --- a/scaleph-ui-react/src/pages/Admin/User/index.tsx +++ b/scaleph-ui-react/src/pages/Admin/User/index.tsx @@ -1,19 +1,20 @@ -import {useAccess, useIntl} from 'umi'; -import React, {useEffect, useRef, useState} from 'react'; -import {Button, Card, Col, Input, List, message, Modal, Row, Space, Tabs, Tag, Tooltip, Tree, Typography,} from 'antd'; -import {EditOutlined, RedoOutlined, StopOutlined, UserSwitchOutlined,} from '@ant-design/icons'; -import {ActionType, ProColumns, ProFormInstance, ProFormSelect, ProTable} from '@ant-design/pro-components'; -import {TreeNode} from '@/app.d'; -import {DICT_TYPE, PRIVILEGE_CODE} from '@/constant'; -import {DeptService} from '@/services/admin/dept.service'; -import {DictDataService} from '@/services/admin/dictData.service'; -import {RoleService} from '@/services/admin/role.service'; -import {SecDept, SecRole, SecUser} from '@/services/admin/typings'; -import {UserService} from '@/services/admin/user.service'; +import { useAccess, useIntl } from 'umi'; +import React, { useEffect, useRef, useState } from 'react'; +import { Button, Card, Col, Input, List, message, Modal, Row, Space, Tabs, Tag, Tooltip, Tree, Typography, } from 'antd'; +import { EditOutlined, RedoOutlined, StopOutlined, UserSwitchOutlined, FormOutlined } from '@ant-design/icons'; +import { ActionType, ProColumns, ProFormInstance, ProFormSelect, ProTable } from '@ant-design/pro-components'; +import { TreeNode } from '@/app.d'; +import { DICT_TYPE, PRIVILEGE_CODE } from '@/constant'; +import { DeptService } from '@/services/admin/dept.service'; +import { DictDataService } from '@/services/admin/dictData.service'; +import { RoleService } from '@/services/admin/role.service'; +import { SecDept, SecRole, SecUser } from '@/services/admin/typings'; +import { UserService } from '@/services/admin/user.service'; import DeptGrant from './components/DeptGrant'; import RoleForm from './components/RoleForm'; import RoleGrant from './components/RoleGrant'; import UserForm from './components/UserForm'; +import UserRoles from './components/UserRoles'; import styles from './index.less'; const User: React.FC = () => { @@ -47,15 +48,19 @@ const User: React.FC = () => { visible: false, data: {}, }); + const [webAssignRoles, setWebAssignRoles] = useState<{ + visiable: boolean; + data: SecRole; + }>({ visiable: false, parent: {}, data: {} }); const tableColumns: ProColumns[] = [ { - title: intl.formatMessage({id: 'pages.admin.user.type'}), + title: intl.formatMessage({ id: 'pages.admin.user.type' }), dataIndex: 'type', render: (dom, entity) => { return ({entity.type?.label}) }, - renderFormItem: (item, {defaultRender, ...rest}, form) => { + renderFormItem: (item, { defaultRender, ...rest }, form) => { return ( { width: 200 }, { - title: intl.formatMessage({id: 'pages.admin.user.userName'}), + title: intl.formatMessage({ id: 'pages.admin.user.userName' }), dataIndex: 'userName', }, { - title: intl.formatMessage({id: 'pages.admin.user.nickName'}), + title: intl.formatMessage({ id: 'pages.admin.user.nickName' }), dataIndex: 'nickName', }, { - title: intl.formatMessage({id: 'pages.admin.user.email'}), + title: intl.formatMessage({ id: 'pages.admin.user.email' }), dataIndex: 'email', hideInSearch: true }, { - title: intl.formatMessage({id: 'pages.admin.user.phone'}), + title: intl.formatMessage({ id: 'pages.admin.user.phone' }), dataIndex: 'phone', hideInSearch: true }, { - title: intl.formatMessage({id: 'pages.admin.user.gender'}), + title: intl.formatMessage({ id: 'pages.admin.user.gender' }), dataIndex: 'gender', hideInSearch: true, render: (text, record, index) => { @@ -93,12 +98,12 @@ const User: React.FC = () => { }, }, { - title: intl.formatMessage({id: 'pages.admin.user.status'}), + title: intl.formatMessage({ id: 'pages.admin.user.status' }), dataIndex: 'userStatus', render: (text, record, index) => { return ({record.status?.label}) }, - renderFormItem: (item, {defaultRender, ...rest}, form) => { + renderFormItem: (item, { defaultRender, ...rest }, form) => { return ( { } }, { - title: intl.formatMessage({id: 'app.common.operate.label'}), + title: intl.formatMessage({ id: 'app.common.operate.label' }), dataIndex: 'actions', align: 'center', width: 120, @@ -118,15 +123,26 @@ const User: React.FC = () => { render: (_, record) => ( <> + {access.canAccess(PRIVILEGE_CODE.datadevProjectEdit) && ( + + @@ -134,23 +150,23 @@ const User: React.FC = () => { {record.userStatus?.value?.substring(0, 1) != '9' && access.canAccess(PRIVILEGE_CODE.userDelete) && ( - + @@ -293,16 +309,16 @@ const User: React.FC = () => { )} {access.canAccess(PRIVILEGE_CODE.deptSelect) && ( - + { setDeptTreeList([...deptTreeList]); }} > - + {node.title} {node.showOpIcon && ( {access.canAccess(PRIVILEGE_CODE.deptGrant) && ( - + @@ -370,12 +386,12 @@ const User: React.FC = () => {
- headerTitle={intl.formatMessage({id: 'pages.admin.user'})} + headerTitle={intl.formatMessage({ id: 'pages.admin.user' })} search={{ labelWidth: 'auto', - span: {xs: 24, sm: 12, md: 8, lg: 6, xl: 6, xxl: 4}, + span: { xs: 24, sm: 12, md: 8, lg: 6, xl: 6, xxl: 4 }, }} - scroll={{x: 800}} + scroll={{ x: 800 }} rowKey="id" actionRef={actionRef} formRef={formRef} @@ -387,10 +403,10 @@ const User: React.FC = () => { key="new" type="primary" onClick={() => { - setUserFormData({visible: true, data: {}}); + setUserFormData({ visible: true, data: {} }); }} > - {intl.formatMessage({id: 'app.common.operate.new.label'})} + {intl.formatMessage({ id: 'app.common.operate.new.label' })} ), access.canAccess(PRIVILEGE_CODE.userDelete) && ( @@ -400,15 +416,15 @@ const User: React.FC = () => { disabled={selectedRows.length < 1} onClick={() => { Modal.confirm({ - title: intl.formatMessage({id: 'app.common.operate.forbid.confirm.title'}), - content: intl.formatMessage({id: 'app.common.operate.forbid.confirm.content'}), - okText: intl.formatMessage({id: 'app.common.operate.confirm.label'}), - okButtonProps: {danger: true}, - cancelText: intl.formatMessage({id: 'app.common.operate.cancel.label'}), + title: intl.formatMessage({ id: 'app.common.operate.forbid.confirm.title' }), + content: intl.formatMessage({ id: 'app.common.operate.forbid.confirm.content' }), + okText: intl.formatMessage({ id: 'app.common.operate.confirm.label' }), + okButtonProps: { danger: true }, + cancelText: intl.formatMessage({ id: 'app.common.operate.cancel.label' }), onOk() { UserService.deleteUserBatch(selectedRows).then((d) => { if (d.success) { - message.success(intl.formatMessage({id: 'app.common.operate.forbid.success'})); + message.success(intl.formatMessage({ id: 'app.common.operate.forbid.success' })); actionRef.current?.reload(); } }); @@ -416,7 +432,7 @@ const User: React.FC = () => { }); }} > - {intl.formatMessage({id: 'app.common.operate.forbid.label'})} + {intl.formatMessage({ id: 'app.common.operate.forbid.label' })} ), ], @@ -429,7 +445,7 @@ const User: React.FC = () => { roleId: selectRole, }); }} - pagination={{showQuickJumper: true, showSizeChanger: true, defaultPageSize: 10}} + pagination={{ showQuickJumper: true, showSizeChanger: true, defaultPageSize: 10 }} rowSelection={{ fixed: true, onChange(selectedRowKeys, selectedRows, info) { @@ -444,10 +460,10 @@ const User: React.FC = () => { { - setRoleFormData({visible: false, data: {}}); + setRoleFormData({ visible: false, data: {} }); }} onVisibleChange={(visible) => { - setRoleFormData({visible: visible, data: {}}); + setRoleFormData({ visible: visible, data: {} }); refreshRoles(); }} data={roleFormData.data} @@ -457,10 +473,10 @@ const User: React.FC = () => { { - setRoleGrantData({visible: false, data: {}}); + setRoleGrantData({ visible: false, data: {} }); }} onVisibleChange={(visible) => { - setRoleGrantData({visible: visible, data: {}}); + setRoleGrantData({ visible: visible, data: {} }); }} data={roleGrantData.data} > @@ -469,10 +485,10 @@ const User: React.FC = () => { { - setDeptGrantData({visible: false, data: {}}); + setDeptGrantData({ visible: false, data: {} }); }} onVisibleChange={(visible) => { - setDeptGrantData({visible: visible, data: {}}); + setDeptGrantData({ visible: visible, data: {} }); }} data={deptGrantData.data} /> @@ -481,15 +497,26 @@ const User: React.FC = () => { { - setUserFormData({visible: false, data: {}}); + setUserFormData({ visible: false, data: {} }); }} onVisibleChange={(visible) => { - setUserFormData({visible: visible, data: {}}); + setUserFormData({ visible: visible, data: {} }); actionRef.current?.reload(); }} data={userFormData.data} > ) : null} + {webAssignRoles.visiable && ( + setWebAssignRoles({ visiable: false, data: {} })} + onVisibleChange={(visiable) => { + setWebAssignRoles({ visiable: visiable, data: {} }); + actionRef.current?.reload(); + }} + data={webAssignRoles.data} + /> + )} ); }; diff --git a/scaleph-ui-react/src/pages/Project/Workspace/Kubernetes/Template/DeploymentTemplateForm.tsx b/scaleph-ui-react/src/pages/Project/Workspace/Kubernetes/Template/DeploymentTemplateForm.tsx index 42719af7a..b4a73f8e6 100644 --- a/scaleph-ui-react/src/pages/Project/Workspace/Kubernetes/Template/DeploymentTemplateForm.tsx +++ b/scaleph-ui-react/src/pages/Project/Workspace/Kubernetes/Template/DeploymentTemplateForm.tsx @@ -1,21 +1,19 @@ import {useIntl} from "umi"; import React from "react"; import {Form, message, Modal} from "antd"; -import {ProForm, ProFormDigit, ProFormRadio, ProFormSelect, ProFormText} from "@ant-design/pro-components"; +import {ProForm, ProFormDigit, ProFormRadio, ProFormText, ProFormTextArea} from "@ant-design/pro-components"; import {ModalFormProps} from '@/app.d'; import {WsFlinkKubernetesTemplate} from "@/services/project/typings"; -import { - WsFlinkKubernetesTemplateService -} from "@/services/project/WsFlinkKubernetesTemplateService"; +import {WsFlinkKubernetesTemplateService} from "@/services/project/WsFlinkKubernetesTemplateService"; import {DICT_TYPE, WORKSPACE_CONF} from "@/constant"; import {DictDataService} from "@/services/admin/dictData.service"; const DeploymentTemplateForm: React.FC> = ({ - data, - visible, - onVisibleChange, - onCancel - }) => { + data, + visible, + onVisibleChange, + onCancel + }) => { const intl = useIntl(); const [form] = Form.useForm(); const projectId = localStorage.getItem(WORKSPACE_CONF.projectId); @@ -26,9 +24,9 @@ const DeploymentTemplateForm: React.FC title={ data.id ? intl.formatMessage({id: 'app.common.operate.edit.label'}) + - intl.formatMessage({id: 'pages.project.flink.kubernetes.deployment.template'}) + intl.formatMessage({id: 'pages.project.flink.kubernetes.template'}) : intl.formatMessage({id: 'app.common.operate.new.label'}) + - intl.formatMessage({id: 'pages.project.flink.kubernetes.deployment.template'}) + intl.formatMessage({id: 'pages.project.flink.kubernetes.template'}) } width={580} destroyOnClose={true} @@ -72,22 +70,22 @@ const DeploymentTemplateForm: React.FC