diff options
| author | zyh <[email protected]> | 2024-09-19 16:13:43 +0800 |
|---|---|---|
| committer | zyh <[email protected]> | 2024-09-19 16:13:43 +0800 |
| commit | 3cdd54de65a323bcaace75ef32c0fe3028128999 (patch) | |
| tree | a630611598f57adba0dc7d324d0e786a9cb62a5e | |
| parent | 61949349cce42dddb8e9a19a53c3f25b9480a9ea (diff) | |
ASW-78 feat: Environment ACL界面开发
| -rw-r--r-- | src/axios/api/environment.js | 43 | ||||
| -rw-r--r-- | src/components/mind/index.vue | 2 | ||||
| -rw-r--r-- | src/i18n/en.js | 7 | ||||
| -rw-r--r-- | src/i18n/zh.js | 7 | ||||
| -rw-r--r-- | src/styles/common.scss | 36 | ||||
| -rw-r--r-- | src/utils/validator.js | 20 | ||||
| -rw-r--r-- | src/views/environments/start.vue | 500 |
7 files changed, 496 insertions, 119 deletions
diff --git a/src/axios/api/environment.js b/src/axios/api/environment.js index 3770fb3..9e8681b 100644 --- a/src/axios/api/environment.js +++ b/src/axios/api/environment.js @@ -192,3 +192,46 @@ export const environmentFileDownloadApi = async ( return err; } }; + +// acl规则列表 +export const environmentAclListApi = async (envId, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/acl`, + method: 'GET', + params: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + +// 添加acl规则 +export const environmentAclAddApi = async (envId, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/acl`, + method: 'POST', + data: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + +// 删除acl规则 +export const environmentAclDeleteApi = async (envId, sessionId, data) => { + console.log(data) + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/acl`, + method: 'DELETE', + data: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; diff --git a/src/components/mind/index.vue b/src/components/mind/index.vue index 97afcc0..de97c12 100644 --- a/src/components/mind/index.vue +++ b/src/components/mind/index.vue @@ -556,7 +556,7 @@ const nameValidator = (rule, value, callback) => { if (validatorIp && validatorPort) { callback(); } else { - callback(t('validator.invalid_IP')); + callback(t('validator.invalid_ip')); } } }; diff --git a/src/i18n/en.js b/src/i18n/en.js index 84195ee..302b4a9 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -93,6 +93,7 @@ export default { about: 'About', type: 'Type', ip: 'IP', + port: 'Port', size: 'Size', permission: 'Permission', creator: 'Creator' @@ -196,6 +197,7 @@ export default { end: 'End', tcpdump: 'Tcpdump', acl: 'ACL', + terminal: 'Terminal', upload_application_package: 'Upload application package', installed: 'Installed', uninstall: 'Uninstall', @@ -214,6 +216,8 @@ export default { fileName: 'File name', quality: 'Quality', compression_level: 'Compression level', + new_acl: 'New ACL', + protocol: 'Protocol', }, job: { create_job: 'Create Job', @@ -226,7 +230,8 @@ export default { }, validator: { required: 'Required', - invalid_IP: 'Invalid IP address', + invalid_ip: 'Invalid IP address', + invalid_port: 'Invalid Port', at_least_eight: 'At Least eight', password_error: "Only English letters, numbers, and special characters ~!{'@'}#$%^&*.?", password_two_types: 'Must contain two or more types', diff --git a/src/i18n/zh.js b/src/i18n/zh.js index 08cd555..5079e4f 100644 --- a/src/i18n/zh.js +++ b/src/i18n/zh.js @@ -93,6 +93,7 @@ export default { about: '关于', type: '类型', ip: 'IP', + port: '端口', size: '大小', permission: '权限', creator: '创建者' @@ -196,6 +197,7 @@ export default { end: '结束', tcpdump: 'Tcpdump', acl: 'ACL', + terminal: '终端', upload_application_package: '上传应用包', installed: '已安装', uninstall: '卸载', @@ -214,6 +216,8 @@ export default { fileName: '文件名', quality: '画质', compression_level: '压缩等级', + new_acl: '新ACL', + protocol: '协议', }, job: { create_job: '创建职位', @@ -226,7 +230,8 @@ export default { }, validator: { required: '必填项', - invalid_IP: '无效的IP地址', + invalid_ip: '无效的IP地址', + invalid_port: '无效的端口', at_least_eight: '至少八位', password_error: "仅限英文字母、数字和特殊字符~!{'@'}#$%^&*.?", password_two_types: '必须包含两种或以上的类型', diff --git a/src/styles/common.scss b/src/styles/common.scss index 39947a0..cc4d2d9 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -8,7 +8,7 @@ .disabled { cursor: not-allowed; - color:var(--el-text-color-disabled) !important; + color: var(--el-text-color-disabled) !important; } .flex-center { @@ -61,8 +61,9 @@ background-color: var(--el-dropdown-menuItem-hover-fill); color: var(--el-dropdown-menuItem-hover-color); } -.el-upload-dragger.is-dragover{ - padding: var(--el-upload-dragger-padding-horizontal) var(--el-upload-dragger-padding-vertical); +.el-upload-dragger.is-dragover { + padding: var(--el-upload-dragger-padding-horizontal) + var(--el-upload-dragger-padding-vertical); border: 1px dashed var(--el-color-primary); } @@ -99,9 +100,9 @@ height: 50px; width: 20px; margin-right: 20px; - .el-icon svg{ + .el-icon svg { color: var(--el-messagebox-title-color); - transform: scale(1.5,1.5); + transform: scale(1.5, 1.5); } } } @@ -123,12 +124,29 @@ height: 40px; } .el-button--primary { - background: #DD1718; - border: #DD1718; + background: #dd1718; + border: #dd1718; } .el-button--primary:hover { - background: #F89898; - border: #F89898; + background: #f89898; + border: #f89898; } } } + +.asw-dialog { + padding: 0; + .el-dialog__header { + padding: 12px 20px; + font-size: 16px; + color: var(--text); + font-weight: 600; + border-bottom: 1px solid var(--border); + } + .el-dialog__body { + padding: 0 20px; + } + .el-dialog__footer { + padding: 20px; + } +} diff --git a/src/utils/validator.js b/src/utils/validator.js index fe733de..fd9ba5b 100644 --- a/src/utils/validator.js +++ b/src/utils/validator.js @@ -1,3 +1,6 @@ +import i18n from '@/i18n/index'; +const t = i18n.global.t; + export const IPv4_reg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/; @@ -18,3 +21,20 @@ export const IPv6Range_reg = export const port_reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/; + +// ip校验 +export const ipValidator = (rule, value, callback) => { + if (IPv4_reg.test(value) || IPv6_reg.test(value)) { + callback(); + } else { + callback(t('validator.invalid_ip')); + } +}; +// port校验 +export const portValidator = (rule, value, callback) => { + if (port_reg.test(value)) { + callback(); + } else { + callback(t('validator.invalid_port')); + } +}; diff --git a/src/views/environments/start.vue b/src/views/environments/start.vue index 32c77dd..ef05479 100644 --- a/src/views/environments/start.vue +++ b/src/views/environments/start.vue @@ -129,6 +129,61 @@ {{ t('environment.capture') }} </el-button> </el-tab-pane> + <!-- ACL --> + <el-tab-pane :label="t('environment.acl')" class="acl-tab"> + <div class="acl-header"> + <el-button type="primary" size="large" @click="newAcl"> + {{ t('environment.new_acl') }} + </el-button> + </div> + <el-table v-loading="aclLoading" :data="aclData" class="acl-table"> + <el-table-column + v-for="item in aclTableTitle" + :key="item.prop" + :min-width="`${item.minWidth}`" + :width="`${item.width}`" + :label="item.label" + :prop="item.prop" + :sortable="item.sortable" + > + <template #default="scope"> + <template v-if="item.prop === 'protocol'"> + <span style="text-transform: capitalize"> + {{ scope.row.protocol }} + </span> + </template> + <template v-else-if="scope.row[item.prop]"> + {{ scope.row[item.prop] }} + </template> + <template v-else>-</template> + </template> + </el-table-column> + <el-table-column + width="105" + fixed="right" + align="center" + :label="t('overall.actions')" + > + <template #default="scope"> + <el-tooltip + effect="dark" + :content="t('overall.delete')" + placement="top" + > + <i + class="asw-icon icon-Delete cp" + style="font-size: 18px" + @click="aclDelete(scope.row)" + ></i> + </el-tooltip> + </template> + </el-table-column> + </el-table> + </el-tab-pane> + <!-- Terminal --> + <el-tab-pane :label="t('environment.terminal')" class="terminal-tab"> + 999 + </el-tab-pane> <!-- file explorer --> <el-tab-pane :label="t('environment.file_explorer')" class="files-tab"> <div class="directory-header"> @@ -238,7 +293,7 @@ align-center width="480px" :title="t('environment.attribute')" - class="directory-dialog" + class="asw-dialog directory-dialog" > <div class="directory-dialog-name"> <svg v-if="directoryInfo.isDir"> @@ -299,13 +354,81 @@ </div> </div> </el-dialog> + <!-- acl对话框 --> + <el-dialog + v-if="aclVisible" + v-model="aclVisible" + align-center + width="580px" + :title="t('environment.new_acl')" + class="asw-dialog acl-dialog" + > + <el-form + ref="aclFormRef" + :model="aclForm" + :rules="aclRules" + label-width="auto" + label-position="top" + > + <!-- Protocol --> + <el-form-item + :label="t('environment.protocol') + ':'" + prop="protocol" + size="default" + > + <el-select + v-model="aclForm.protocol" + placeholder="Select" + size="default" + > + <el-option + v-for="item in protocolOptions" + :key="item.value" + :label="item.name" + :value="item.value" + /> + </el-select> + </el-form-item> + <!-- ip --> + <el-form-item :label="t('overall.ip') + ':'" prop="ip" size="default"> + <el-input + v-model="aclForm.ip" + :placeholder="t('overall.please_input')" + size="default" + /> + </el-form-item> + <!-- port --> + <el-form-item :label="t('overall.port') + ':'" prop="port" size="default"> + <el-input + v-model="aclForm.port" + :placeholder="t('overall.please_input')" + size="default" + /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button size="large" @click="cancelAcl"> + {{ t('overall.cancel') }} + </el-button> + <el-button + size="large" + type="primary" + :disabled="aclSubmitting" + @click="saveAcl" + > + {{ t('overall.save') }} + </el-button> + </div> + </template> + </el-dialog> <!-- 停止捕包对话框 --> <el-dialog v-model="captureVisible" align-center width="480px" :title="t('environment.stop_capture')" - class="capture-dialog" + class="asw-dialog capture-dialog" > <div class="capture-dialog-switch"> <span>{{ t('environment.save_pcap') }}</span> @@ -338,7 +461,7 @@ import { useI18n } from 'vue-i18n'; import RFB from '@novnc/novnc/lib/rfb'; import mitter from '@/utils/mitter'; import { debounce } from 'lodash'; -import { bytes, downloadFile } from '@/utils'; +import { bytes, downloadFile, ipValidator, portValidator } from '@/utils'; import moment from 'moment-timezone'; import { get } from 'lodash'; import { useSystemStore } from '@/store/index'; @@ -353,6 +476,9 @@ import { environmentFileUploadApi, environmentFileListApi, environmentFileDownloadApi, + environmentAclListApi, + environmentAclAddApi, + environmentAclDeleteApi, } from '@/axios/api'; import { ElMessage, ElMessageBox } from 'element-plus'; import axiosInstance from '@/axios/index.js'; @@ -373,6 +499,99 @@ const disposeRoute = () => { }; disposeRoute(); +// 获取设备信息 +const loading = ref(false); +const environmentInfo = ref({}); +const getData = async () => { + loading.value = true; + const res = await environmentDetailApi(routeParams.id); + if (res.code == 200) { + environmentInfo.value = res.data.record; + } else { + ElMessage.error(res.msg || res.error); + } + loading.value = false; +}; + +// 连接novnc +const novncRef = ref(null); +let rfb = null; +const connect = () => { + // 获取baseURL + let baseURL = axiosInstance.defaults.baseURL; + console.log(baseURL); + const protocol = + window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws'; + let url = ''; + if (baseURL.startsWith('/')) { + url = `${protocol}://${window.location.host}${baseURL}`; + } else { + url = baseURL.replace('http://', 'ws://').replace('https://', 'wss://'); + } + if (!url.endsWith('/')) { + url += '/'; + } + + // 获取token + let userInfo = localStorage.getItem('asg-userInfo'); + let tokenValue; + try { + userInfo = JSON.parse(userInfo); + tokenValue = userInfo.tokenValue; + } catch (error) {} + url += `api/v1/env/${routeParams.id}/session/${routeParams.sessionId}/novnc?token=${tokenValue}`; + rfb = new RFB(novncRef.value, url); + rfb.scaleViewport = true; // 远程会话是否应在本地缩放以适合其容器 + rfb.clipViewport = true; // 远程会话是否应被剪裁到其容器,禁用时将显示滚动条以处理由此产生的溢出 + rfb.resizeSession = false; // 每当容器尺寸调整是否发送调整远程会话大小的请求 + rfb.showDotCursor = true; // 如果服务器设置了这种不可见光标,是否应显示点光标而不是零大小或完全透明的光标 + rfb.viewOnly = false; // 是否应阻止将任何事件(例如按键或鼠标移动)发送到服务器 + rfb.qualityLevel = quality.value; // 范围为[0-9]的整数,控制所需的JPEG质量。0表示低质量,9表示高质量。 + rfb.compressionLevel = compressionLevel.value; // 范围为[0-9]的整数,用于控制所需的压缩级别。0表示不压缩。级别1使用最少的CPU资源,实现较弱的压缩比,而级别9提供最好的压缩,但在服务器端的CPU消耗方面很慢。 + + rfb._sock._websocket.onclose = (err) => { + if (err.reason) { + ElMessage.error(err.reason); + } + desktopEnd(); + }; +}; + +// novnc调整画质 +const qualityVisible = ref(false); +const quality = ref(5); +const qualityChange = () => { + rfb.qualityLevel = quality.value; +}; +const compressionLevel = ref(5); +const compressionLevelChange = () => { + rfb.compressionLevel = compressionLevel.value; +}; + +// back +const environmentBack = (e) => { + rfb.sendKey('0xff1b', 'Escape', true); +}; +// home +const environmentHome = () => { + rfb.sendKey('0xff50', 'Home', true); +}; +// task +const environmentTask = () => { + rfb.sendKey('0xffe3', 'ControlLeft', true); + rfb.sendKey('0xffe1', 'ShiftLeft', true); + rfb.sendKey('0xff1b', 'Escape', true); + rfb.sendKey('0xffe3', 'ControlLeft', false); + rfb.sendKey('0xffe1', 'ShiftLeft', false); + rfb.sendKey('0xff1b', 'Escape', false); +}; + +const zmkm = () => { + if (packageName.value == 'zmkm') { + rfb.sendCtrlAltDel(); + } +}; + // 已下载列表 const appLoading = ref(false); const appList = ref([]); @@ -596,81 +815,148 @@ const directoryDownload = async (data) => { }; // 删除文件 const directoryDelete = async (data) => {}; - const fileSize = (number) => { const data = bytes(number, -1, 1); return data.value + ' ' + data.unit; }; -// 获取设备信息 -const loading = ref(false); -const environmentInfo = ref({}); -const getData = async () => { - loading.value = true; - const res = await environmentDetailApi(routeParams.id); +const aclTableTitle = ref([ + { + minWidth: 100, + prop: 'protocol', + label: t('environment.protocol'), + sortable: false, + }, + { + minWidth: 200, + prop: 'ip', + label: t('overall.ip'), + sortable: false, + }, + { + minWidth: 200, + prop: 'port', + label: t('overall.port'), + sortable: false, + }, +]); +const aclLoading = ref(false); +const aclData = ref([]); +const getAclData = async () => { + aclLoading.value = true; + const res = await environmentAclListApi( + routeParams.id, + routeParams.sessionId + ); if (res.code == 200) { - environmentInfo.value = res.data.record; + const records = get(res, 'data.records', []); + aclData.value = records; } else { ElMessage.error(res.msg || res.error); } - loading.value = false; + aclLoading.value = false; }; - -// 连接novnc -const novncRef = ref(null); -let rfb = null; -const connect = () => { - // 获取baseURL - let baseURL = axiosInstance.defaults.baseURL; - console.log(baseURL); - const protocol = - window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws'; - let url = ''; - if (baseURL.startsWith('/')) { - url = `${protocol}://${window.location.host}${baseURL}`; - } else { - url = baseURL.replace('http://', 'ws://').replace('https://', 'wss://'); - } - if (!url.endsWith('/')) { - url += '/'; +const aclVisible = ref(false); +const aclFormRef = ref(null); +const aclRules = reactive({ + ip: [ + { required: true, message: t('validator.required'), trigger: 'blur' }, + { validator: ipValidator, trigger: 'blur' }, + ], + port: [ + { required: true, message: t('validator.required'), trigger: 'blur' }, + { validator: portValidator, trigger: 'blur' }, + ], +}); +const protocolOptions = reactive([ + { name: 'All', value: 'all' }, + { name: 'Tcp', value: 'tcp' }, + { name: 'Udp', value: 'udp' }, +]); +const aclForm = ref({ + protocol: 'all', + ip: '', + port: '', +}); +// 显示新增弹窗 +const newAcl = () => { + aclForm.value = { + protocol: 'all', + ip: '', + port: '', + }; + aclVisible.value = true; +}; +const aclSubmitting = ref(false); +// 新增acl +const saveAcl = async () => { + if (aclSubmitting.value) { + return; } - - // 获取token - let userInfo = localStorage.getItem('asg-userInfo'); - let tokenValue; - try { - userInfo = JSON.parse(userInfo); - tokenValue = userInfo.tokenValue; - } catch (error) {} - url += `api/v1/env/${routeParams.id}/session/${routeParams.sessionId}/novnc?token=${tokenValue}`; - rfb = new RFB(novncRef.value, url); - rfb.scaleViewport = true; // 远程会话是否应在本地缩放以适合其容器 - rfb.clipViewport = true; // 远程会话是否应被剪裁到其容器,禁用时将显示滚动条以处理由此产生的溢出 - rfb.resizeSession = false; // 每当容器尺寸调整是否发送调整远程会话大小的请求 - rfb.showDotCursor = true; // 如果服务器设置了这种不可见光标,是否应显示点光标而不是零大小或完全透明的光标 - rfb.viewOnly = false; // 是否应阻止将任何事件(例如按键或鼠标移动)发送到服务器 - rfb.qualityLevel = quality.value; // 范围为[0-9]的整数,控制所需的JPEG质量。0表示低质量,9表示高质量。 - rfb.compressionLevel = compressionLevel.value; // 范围为[0-9]的整数,用于控制所需的压缩级别。0表示不压缩。级别1使用最少的CPU资源,实现较弱的压缩比,而级别9提供最好的压缩,但在服务器端的CPU消耗方面很慢。 - - rfb._sock._websocket.onclose = (err) => { - if (err.reason) { - ElMessage.error(err.reason); + aclSubmitting.value = true; + await aclFormRef.value.validate(async (valid) => { + if (valid) { + const params = { + protocol: aclForm.value.protocol, + ip: aclForm.value.ip, + port: aclForm.value.port, + }; + const res = await environmentAclAddApi( + routeParams.id, + routeParams.sessionId, + params + ); + if (res.code == 200) { + aclVisible.value = false; + ElMessage.success(t('message.save_success')); + getAclData(); + } else { + ElMessage.error(res.msg || res.error); + } } - desktopEnd(); - }; + }); + aclSubmitting.value = false; }; - -// novnc调整画质 -const qualityVisible = ref(false); -const quality = ref(5); -const qualityChange = () => { - rfb.qualityLevel = quality.value; +const cancelAcl = () => { + aclVisible.value = false; }; -const compressionLevel = ref(5); -const compressionLevelChange = () => { - rfb.compressionLevel = compressionLevel.value; +// 删除acl +const aclDelete = async (row) => { + ElMessageBox.confirm( + t('env_mgt.delete_hint_message', { + environment: t('environment.acl'), + }), + t('env_mgt.delete_hint_title'), + { + confirmButtonText: t('overall.delete'), + cancelButtonText: t('overall.cancel'), + iconClass: 'width:0px;height:0px;', + customClass: 'delete-box', + } + ) + .then(async () => { + const params = { + protocol: row.protocol, + ip: row.ip, + port: row.port, + }; + const res = await environmentAclDeleteApi( + routeParams.id, + routeParams.sessionId, + params + ); + console.log(res); + if (res.code == 200) { + getAclData(); + ElMessage.success(t('message.save_success')); + } else { + ElMessage.error(res.msg || res.error); + } + }) + .catch((e) => {}); }; +// 结束操作 const desktopEnd = () => { childEnd(); window.close(); @@ -682,7 +968,6 @@ const desktopEnd = () => { }, }); }; - // 结束会话 mitter.on('desktopEnd', async () => { const params = { @@ -699,7 +984,6 @@ mitter.on('desktopEnd', async () => { ElMessage.error(res.msg || res.error); } }); - // 向父页面发送end消息 const childEnd = () => { if (window.opener) { @@ -709,32 +993,9 @@ const childEnd = () => { } }; -// back -const environmentBack = (e) => { - rfb.sendKey('0xff1b', 'Escape', true); -}; -// home -const environmentHome = () => { - rfb.sendKey('0xff50', 'Home', true); -}; -// task -const environmentTask = () => { - rfb.sendKey('0xffe3', 'ControlLeft', true); - rfb.sendKey('0xffe1', 'ShiftLeft', true); - rfb.sendKey('0xff1b', 'Escape', true); - rfb.sendKey('0xffe3', 'ControlLeft', false); - rfb.sendKey('0xffe1', 'ShiftLeft', false); - rfb.sendKey('0xff1b', 'Escape', false); -}; - -const zmkm = () => { - if (packageName.value == 'zmkm') { - rfb.sendCtrlAltDel(); - } -}; - onMounted(async () => { getAppList(); + getAclData(); getDirectory(directoryPath.value); getData(); connect(); @@ -845,6 +1106,7 @@ onBeforeUnmount(() => { } .el-tabs__content { height: 100%; + .application-tab { height: 100%; padding: 20px 0; @@ -925,6 +1187,36 @@ onBeforeUnmount(() => { } } } + + .acl-tab { + height: 100%; + padding: 20px; + padding-top: 10px; + .acl-header { + display: flex; + justify-content: flex-end; + padding-bottom: 10px; + height: 50px; + } + .acl-table { + height: calc(100% - 50px); + .el-table__header { + .el-table__cell { + padding: 6px 0; + } + } + .el-table__body { + .el-table__cell { + padding: 8px 0; + } + } + } + } + + .terminal-tab { + padding: 20px; + } + .tcpdump-tab { height: 100%; padding: 50px 80px; @@ -1042,14 +1334,6 @@ onBeforeUnmount(() => { .environment_start-page { padding: 0 !important; .directory-dialog { - padding: 0; - .el-dialog__header { - padding: 12px 20px; - font-size: 16px; - color: var(--text); - font-weight: 600; - border-bottom: 1px solid var(--border); - } .el-dialog__body { padding: 0 20px; .directory-dialog-name { @@ -1081,16 +1365,7 @@ onBeforeUnmount(() => { } .capture-dialog { - padding: 0; - .el-dialog__header { - padding: 12px 20px; - font-size: 16px; - color: var(--text); - font-weight: 600; - border-bottom: 1px solid var(--border); - } .el-dialog__body { - padding: 0 20px; padding-top: 20px; .capture-dialog-switch { display: flex; @@ -1112,8 +1387,19 @@ onBeforeUnmount(() => { } } } - .el-dialog__footer { - padding: 20px; + } + + .acl-dialog { + .el-dialog__body { + padding-top: 15px; + .el-form-item__label { + margin-bottom: 6px; + font-weight: 600; + color: var(--text); + } + .el-form-item { + margin-bottom: 15px; + } } } } |
