mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-12-01 19:38:09 +08:00
🚀 feat(agent): 全局脚本库
This commit is contained in:
parent
fade185dad
commit
87d73b583f
@ -2,6 +2,10 @@
|
||||
|
||||
## 2.11.6.3-beta
|
||||
|
||||
### 🐣 新增功能
|
||||
|
||||
1. 【agent】新增 全局脚本库
|
||||
|
||||
### 🐞 解决BUG、优化功能
|
||||
|
||||
1. 【server】修复 gogs 仓库令牌导入异常(感谢@张飞鸿)
|
||||
|
@ -10,6 +10,7 @@
|
||||
package org.dromara.jpom.func.assets.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.keepbx.jpom.IJsonMessage;
|
||||
import cn.keepbx.jpom.model.JsonMessage;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
@ -41,6 +42,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 机器节点
|
||||
@ -78,6 +80,30 @@ public class MachineNodeController extends BaseGroupNameController {
|
||||
return JsonMessage.success("", pageResultDto);
|
||||
}
|
||||
|
||||
@GetMapping(value = "search", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public IJsonMessage<List<MachineNodeModel>> search(String name, String appendIds, int limit) {
|
||||
Entity entity = new Entity();
|
||||
if (StrUtil.isNotEmpty(name)) {
|
||||
entity.set("name", StrUtil.format(" like '%{}%'", name));
|
||||
}
|
||||
limit = Math.max(limit, 1);
|
||||
List<String> appendIdList = StrUtil.splitTrim(appendIds, StrUtil.COMMA);
|
||||
List<MachineNodeModel> machineNodeModels = machineNodeServer.queryList(entity, limit, machineNodeServer.defaultOrders());
|
||||
appendIdList = appendIdList.stream()
|
||||
.filter(s -> machineNodeModels.stream()
|
||||
.noneMatch(machineNodeModel -> StrUtil.equals(s, machineNodeModel.getId())))
|
||||
.collect(Collectors.toList());
|
||||
for (String s : appendIdList) {
|
||||
MachineNodeModel nodeModel = machineNodeServer.getByKey(s);
|
||||
if (nodeModel == null) {
|
||||
continue;
|
||||
}
|
||||
machineNodeModels.add(nodeModel);
|
||||
}
|
||||
return JsonMessage.success("", machineNodeModels);
|
||||
}
|
||||
|
||||
@PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.EDIT)
|
||||
public IJsonMessage<String> save(HttpServletRequest request) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.dromara.jpom.func.assets.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.keepbx.jpom.IJsonMessage;
|
||||
@ -36,7 +39,7 @@ import java.util.List;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/system/assets/script-library")
|
||||
@Feature(cls = ClassFeature.SYSTEM_ASSETS_MACHINE_DOCKER)
|
||||
@Feature(cls = ClassFeature.SYSTEM_ASSETS_GLOBAL_SCRIPT)
|
||||
@SystemPermission
|
||||
@Slf4j
|
||||
public class ScriptLibraryController extends BaseServerController {
|
||||
@ -58,24 +61,38 @@ public class ScriptLibraryController extends BaseServerController {
|
||||
@Feature(method = MethodFeature.EDIT)
|
||||
public IJsonMessage<String> save(HttpServletRequest request) {
|
||||
ScriptLibraryModel scriptLibraryModel = ServletUtil.toBean(request, ScriptLibraryModel.class, true);
|
||||
Assert.hasText(scriptLibraryModel.getTag(), "标签不能为空");
|
||||
String tag = scriptLibraryModel.getTag();
|
||||
Assert.hasText(tag, "标签不能为空");
|
||||
Validator.validateGeneral(tag, 4, 20, "标签只能包含字母、数字、下划线");
|
||||
Assert.hasText(scriptLibraryModel.getScript(), "脚本不能为空");
|
||||
//
|
||||
Entity entity = Entity.create();
|
||||
entity.set("tag", scriptLibraryModel.getTag());
|
||||
entity.set("tag", tag);
|
||||
String id = scriptLibraryModel.getId();
|
||||
if (StrUtil.isNotEmpty(id)) {
|
||||
entity.set("id", StrUtil.format(" <> {}", id));
|
||||
}
|
||||
Assert.state(!scriptLibraryServer.exists(entity), "标签已存在");
|
||||
String oldIds = StrUtil.EMPTY;
|
||||
String version = StrUtil.sub(SecureUtil.md5(scriptLibraryModel.getScript()), 0, 6);
|
||||
if (StrUtil.isNotEmpty(id)) {
|
||||
scriptLibraryServer.updateById(scriptLibraryModel);
|
||||
} else {
|
||||
ScriptLibraryModel libraryModel = scriptLibraryServer.getByKey(id);
|
||||
Assert.notNull(libraryModel, "数据不存在");
|
||||
Assert.state(StrUtil.equals(libraryModel.getTag(), scriptLibraryModel.getTag()), "脚本标签不能修改");
|
||||
Assert.state(StrUtil.equals(libraryModel.getTag(), tag), "脚本标签不能修改");
|
||||
oldIds = libraryModel.getMachineIds();
|
||||
if (StrUtil.equals(libraryModel.getScript(), scriptLibraryModel.getScript())) {
|
||||
// 内容没有变化不
|
||||
scriptLibraryModel.setVersion(null);
|
||||
} else {
|
||||
// 自动生成版本号
|
||||
String libraryModelVersion = libraryModel.getVersion();
|
||||
List<String> list = StrUtil.splitTrim(libraryModelVersion, "#");
|
||||
int nextIncVersion = Convert.toInt(list.get(0), -2) + 1;
|
||||
scriptLibraryModel.setVersion(StrUtil.format("{}#{}", nextIncVersion, version));
|
||||
}
|
||||
scriptLibraryServer.updateById(scriptLibraryModel);
|
||||
} else {
|
||||
scriptLibraryModel.setVersion(StrUtil.format("1#{}", version));
|
||||
scriptLibraryServer.insert(scriptLibraryModel);
|
||||
}
|
||||
// 同步到机器节点
|
||||
@ -94,17 +111,18 @@ public class ScriptLibraryController extends BaseServerController {
|
||||
MachineNodeModel byKey = machineNodeServer.getByKey(machineId);
|
||||
Assert.notNull(byKey, "没有找到对应的节点");
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", scriptModel.getId());
|
||||
jsonObject.put("id", scriptModel.getTag());
|
||||
jsonObject.put("description", scriptModel.getDescription());
|
||||
jsonObject.put("tag", scriptModel.getTag());
|
||||
jsonObject.put("script", scriptModel.getScript());
|
||||
jsonObject.put("version", scriptModel.getVersion());
|
||||
JsonMessage<String> jsonMessage = NodeForward.request(byKey, NodeUrl.SCRIPT_LIBRARY_SAVE, jsonObject);
|
||||
Assert.state(jsonMessage.success(), "处理 " + byKey.getName() + " 节点同步脚本库失败" + jsonMessage.getMsg());
|
||||
String message = StrUtil.format("处理 {} 节点同步脚本库失败 {}", byKey.getName(), jsonMessage.getMsg());
|
||||
Assert.state(jsonMessage.success(), message);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDelMachineNodeScriptLibrary(String id, Collection<String> delNode) {
|
||||
private void syncDelMachineNodeScriptLibrary(String tag, Collection<String> delNode) {
|
||||
for (String machineId : delNode) {
|
||||
MachineNodeModel byKey = machineNodeServer.getByKey(machineId);
|
||||
if (byKey == null) {
|
||||
@ -113,9 +131,10 @@ public class ScriptLibraryController extends BaseServerController {
|
||||
continue;
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", id);
|
||||
jsonObject.put("id", tag);
|
||||
JsonMessage<String> request = NodeForward.request(byKey, NodeUrl.SCRIPT_LIBRARY_DEL, jsonObject);
|
||||
Assert.state(request.getCode() == 200, "处理 " + byKey.getName() + " 节点删除脚本库失败" + request.getMsg());
|
||||
String message = StrUtil.format("处理 {} 节点删除脚本库失败 {}", byKey.getName(), request.getMsg());
|
||||
Assert.state(request.getCode() == 200, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,20 @@ import org.dromara.jpom.model.BaseUserModifyDbModel;
|
||||
* @since 2024/6/1
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "SCRIPT_LIBRARY", name = "脚本库信息")
|
||||
@TableName(value = "SCRIPT_LIBRARY", nameKey = "脚本库信息")
|
||||
@Data
|
||||
public class ScriptLibraryModel extends BaseUserModifyDbModel {
|
||||
/**
|
||||
* 脚本唯一的标签
|
||||
*/
|
||||
private String tag;
|
||||
|
||||
/**
|
||||
* 脚本内容
|
||||
*/
|
||||
private String script;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 版本
|
||||
|
@ -14,6 +14,7 @@ import org.dromara.jpom.common.i18n.I18nMessageUtil;
|
||||
import org.dromara.jpom.func.assets.server.MachineDockerServer;
|
||||
import org.dromara.jpom.func.assets.server.MachineNodeServer;
|
||||
import org.dromara.jpom.func.assets.server.MachineSshServer;
|
||||
import org.dromara.jpom.func.assets.server.ScriptLibraryServer;
|
||||
import org.dromara.jpom.func.cert.service.CertificateInfoService;
|
||||
import org.dromara.jpom.func.files.service.FileReleaseTaskService;
|
||||
import org.dromara.jpom.func.files.service.FileStorageService;
|
||||
@ -98,6 +99,7 @@ public enum ClassFeature {
|
||||
SYSTEM_ASSETS_MACHINE(() -> I18nMessageUtil.get("i18n.machine_asset_management.36ea"), MachineNodeServer.class),
|
||||
SYSTEM_ASSETS_MACHINE_SSH(() -> I18nMessageUtil.get("i18n.ssh_asset_management.3b6c"), MachineSshServer.class),
|
||||
SYSTEM_ASSETS_MACHINE_DOCKER(() -> I18nMessageUtil.get("i18n.docker_asset_management.96d9"), MachineDockerServer.class),
|
||||
SYSTEM_ASSETS_GLOBAL_SCRIPT(() -> "脚本库", ScriptLibraryServer.class),
|
||||
SYSTEM_CONFIG(() -> I18nMessageUtil.get("i18n.server_system_config.3181")),
|
||||
SYSTEM_EXT_CONFIG(() -> I18nMessageUtil.get("i18n.system_configuration_directory.0f82")),
|
||||
SYSTEM_CONFIG_IP(() -> I18nMessageUtil.get("i18n.system_IP_authorization.9c08")),
|
||||
|
@ -657,7 +657,7 @@ public abstract class BaseDbService<T extends BaseDbModel> extends BaseDbCommonS
|
||||
return this.listPageDb(where, page, fill);
|
||||
}
|
||||
|
||||
protected Order[] defaultOrders() {
|
||||
public Order[] defaultOrders() {
|
||||
return DEFAULT_ORDERS;
|
||||
}
|
||||
|
||||
|
@ -292,7 +292,7 @@ public abstract class BaseWorkspaceService<T extends BaseWorkspaceModel> extends
|
||||
|
||||
|
||||
@Override
|
||||
protected Order[] defaultOrders() {
|
||||
public Order[] defaultOrders() {
|
||||
if (canSort) {
|
||||
return SORT_DEFAULT_ORDERS;
|
||||
}
|
||||
|
@ -28,3 +28,13 @@ STATIC_FILE_STORAGE,lastModified,Long,,,false,false,最后修改时间,
|
||||
STATIC_FILE_STORAGE,size,Long,,,false,false,文件大小,
|
||||
STATIC_FILE_STORAGE,level,Integer,,,false,false,"层级",
|
||||
STATIC_FILE_STORAGE,triggerToken,String,100,,false,false,触发器token,
|
||||
|
||||
SCRIPT_LIBRARY,id,String,50,,true,true,id,静态文件管理
|
||||
SCRIPT_LIBRARY,createTimeMillis,Long,,,false,false,数据创建时间,
|
||||
SCRIPT_LIBRARY,modifyTimeMillis,Long,,,false,false,数据修改时间,
|
||||
SCRIPT_LIBRARY,modifyUser,String,50,,false,false,修改人,
|
||||
SCRIPT_LIBRARY,tag,String,50,,true,false,标签,
|
||||
SCRIPT_LIBRARY,description,String,255,,false,false,描述,
|
||||
SCRIPT_LIBRARY,script,text,,,false,false,描述,
|
||||
SCRIPT_LIBRARY,machineIds,text,,,false,false,关联的机器节点,
|
||||
SCRIPT_LIBRARY,version,String,50,,true,false,版本,
|
||||
|
|
@ -125,3 +125,11 @@ export function machineMonitorConfig(data) {
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
export function machineSearch(data) {
|
||||
return axios({
|
||||
url: '/system/assets/machine/search',
|
||||
method: 'get',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
19
web-vue/src/d.ts/components.d.ts
vendored
19
web-vue/src/d.ts/components.d.ts
vendored
@ -44,16 +44,13 @@ declare module 'vue' {
|
||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||
AList: typeof import('ant-design-vue/es')['List']
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
ApartmentOutlined: typeof import('@ant-design/icons-vue')['ApartmentOutlined']
|
||||
ApiOutlined: typeof import('@ant-design/icons-vue')['ApiOutlined']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
||||
@ -67,14 +64,11 @@ declare module 'vue' {
|
||||
ArrowRightOutlined: typeof import('@ant-design/icons-vue')['ArrowRightOutlined']
|
||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
AStatisticCountdown: typeof import('ant-design-vue/es')['StatisticCountdown']
|
||||
AStep: typeof import('ant-design-vue/es')['Step']
|
||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
@ -93,10 +87,7 @@ declare module 'vue' {
|
||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||
BarsOutlined: typeof import('@ant-design/icons-vue')['BarsOutlined']
|
||||
BlockOutlined: typeof import('@ant-design/icons-vue')['BlockOutlined']
|
||||
CheckCircleFilled: typeof import('@ant-design/icons-vue')['CheckCircleFilled']
|
||||
CheckCircleOutlined: typeof import('@ant-design/icons-vue')['CheckCircleOutlined']
|
||||
CheckOutlined: typeof import('@ant-design/icons-vue')['CheckOutlined']
|
||||
CloseOutlined: typeof import('@ant-design/icons-vue')['CloseOutlined']
|
||||
CloudDownloadOutlined: typeof import('@ant-design/icons-vue')['CloudDownloadOutlined']
|
||||
CloudOutlined: typeof import('@ant-design/icons-vue')['CloudOutlined']
|
||||
CloudServerOutlined: typeof import('@ant-design/icons-vue')['CloudServerOutlined']
|
||||
@ -105,7 +96,6 @@ declare module 'vue' {
|
||||
CodeOutlined: typeof import('@ant-design/icons-vue')['CodeOutlined']
|
||||
ColumnHeightOutlined: typeof import('@ant-design/icons-vue')['ColumnHeightOutlined']
|
||||
CompositionTransfer: typeof import('./../components/compositionTransfer/composition-transfer.vue')['default']
|
||||
copy: typeof import('./../components/customTable/index copy.vue')['default']
|
||||
CustomInput: typeof import('./../components/customInput/index.vue')['default']
|
||||
CustomModal: typeof import('./../components/customModal/index.vue')['default']
|
||||
CustomSelect: typeof import('./../components/customSelect/index.vue')['default']
|
||||
@ -117,17 +107,13 @@ declare module 'vue' {
|
||||
EditOutlined: typeof import('@ant-design/icons-vue')['EditOutlined']
|
||||
EllipsisOutlined: typeof import('@ant-design/icons-vue')['EllipsisOutlined']
|
||||
ExclamationCircleOutlined: typeof import('@ant-design/icons-vue')['ExclamationCircleOutlined']
|
||||
EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined']
|
||||
EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined']
|
||||
FileAddOutlined: typeof import('@ant-design/icons-vue')['FileAddOutlined']
|
||||
FileOutlined: typeof import('@ant-design/icons-vue')['FileOutlined']
|
||||
FileTextOutlined: typeof import('@ant-design/icons-vue')['FileTextOutlined']
|
||||
FileZipOutlined: typeof import('@ant-design/icons-vue')['FileZipOutlined']
|
||||
FolderAddOutlined: typeof import('@ant-design/icons-vue')['FolderAddOutlined']
|
||||
FullscreenOutlined: typeof import('@ant-design/icons-vue')['FullscreenOutlined']
|
||||
HighlightOutlined: typeof import('@ant-design/icons-vue')['HighlightOutlined']
|
||||
HolderOutlined: typeof import('@ant-design/icons-vue')['HolderOutlined']
|
||||
HomeOutlined: typeof import('@ant-design/icons-vue')['HomeOutlined']
|
||||
Index2: typeof import('./../components/logView/index2.vue')['default']
|
||||
InfoCircleOutlined: typeof import('@ant-design/icons-vue')['InfoCircleOutlined']
|
||||
InfoOutlined: typeof import('@ant-design/icons-vue')['InfoOutlined']
|
||||
@ -138,7 +124,6 @@ declare module 'vue' {
|
||||
LoginOutlined: typeof import('@ant-design/icons-vue')['LoginOutlined']
|
||||
LogoutOutlined: typeof import('@ant-design/icons-vue')['LogoutOutlined']
|
||||
LogView: typeof import('./../components/logView/index.vue')['default']
|
||||
MenuOutlined: typeof import('@ant-design/icons-vue')['MenuOutlined']
|
||||
MessageOutlined: typeof import('@ant-design/icons-vue')['MessageOutlined']
|
||||
MinusCircleOutlined: typeof import('@ant-design/icons-vue')['MinusCircleOutlined']
|
||||
MoreOutlined: typeof import('@ant-design/icons-vue')['MoreOutlined']
|
||||
@ -160,14 +145,11 @@ declare module 'vue' {
|
||||
SelectOutlined: typeof import('@ant-design/icons-vue')['SelectOutlined']
|
||||
SettingOutlined: typeof import('@ant-design/icons-vue')['SettingOutlined']
|
||||
SkinOutlined: typeof import('@ant-design/icons-vue')['SkinOutlined']
|
||||
SolutionOutlined: typeof import('@ant-design/icons-vue')['SolutionOutlined']
|
||||
SortAscendingOutlined: typeof import('@ant-design/icons-vue')['SortAscendingOutlined']
|
||||
SortDescendingOutlined: typeof import('@ant-design/icons-vue')['SortDescendingOutlined']
|
||||
StopFilled: typeof import('@ant-design/icons-vue')['StopFilled']
|
||||
StopOutlined: typeof import('@ant-design/icons-vue')['StopOutlined']
|
||||
SwapOutlined: typeof import('@ant-design/icons-vue')['SwapOutlined']
|
||||
SwitcherOutlined: typeof import('@ant-design/icons-vue')['SwitcherOutlined']
|
||||
SyncOutlined: typeof import('@ant-design/icons-vue')['SyncOutlined']
|
||||
TableOutlined: typeof import('@ant-design/icons-vue')['TableOutlined']
|
||||
TagOutlined: typeof import('@ant-design/icons-vue')['TagOutlined']
|
||||
TagsOutlined: typeof import('@ant-design/icons-vue')['TagsOutlined']
|
||||
@ -179,6 +161,5 @@ declare module 'vue' {
|
||||
VerticalLeftOutlined: typeof import('@ant-design/icons-vue')['VerticalLeftOutlined']
|
||||
ViewPre: typeof import('./../components/logView/view-pre.vue')['default']
|
||||
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
|
||||
WarningTwoTone: typeof import('@ant-design/icons-vue')['WarningTwoTone']
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,8 @@ const i18n = createI18n<I18nLocaleType>({
|
||||
const { local } = lang[key]
|
||||
pre[key] = local
|
||||
return pre
|
||||
}, {})
|
||||
}, {}),
|
||||
warnHtmlMessage: false
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
@ -7,14 +7,13 @@
|
||||
:auto-refresh-time="30"
|
||||
:active-page="activePage"
|
||||
table-name="script-library"
|
||||
:empty-description="$tl('p.noScript')"
|
||||
empty-description="没有任何的脚本库"
|
||||
:data-source="list"
|
||||
size="middle"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
bordered
|
||||
row-key="id"
|
||||
:row-selection="rowSelection"
|
||||
:scroll="{
|
||||
x: 'max-content'
|
||||
}"
|
||||
@ -24,54 +23,40 @@
|
||||
<template #title>
|
||||
<a-space wrap class="search-box">
|
||||
<a-input
|
||||
v-model:value="listQuery['id']"
|
||||
:placeholder="$tl('p.scriptId')"
|
||||
v-model:value="listQuery['%tag%']"
|
||||
placeholder="脚本标记"
|
||||
allow-clear
|
||||
class="search-input-item"
|
||||
@press-enter="loadData"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="listQuery['%name%']"
|
||||
:placeholder="$tl('c.name')"
|
||||
v-model:value="listQuery['%version%']"
|
||||
placeholder="版本"
|
||||
allow-clear
|
||||
class="search-input-item"
|
||||
@press-enter="loadData"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="listQuery['%description%']"
|
||||
:placeholder="$tl('c.description')"
|
||||
placeholder="描述"
|
||||
class="search-input-item"
|
||||
@press-enter="loadData"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="listQuery['%autoExecCron%']"
|
||||
:placeholder="$tl('c.scheduleExecution')"
|
||||
class="search-input-item"
|
||||
@press-enter="loadData"
|
||||
/>
|
||||
<a-tooltip :title="$tl('p.backToFirstPage')">
|
||||
<a-button :loading="loading" type="primary" @click="loadData">{{ $tl('p.search') }}</a-button>
|
||||
|
||||
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
|
||||
<a-button :loading="loading" type="primary" @click="loadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
<a-button type="primary" @click="createScript">{{ $tl('p.add') }}</a-button>
|
||||
<a-button
|
||||
v-if="mode === 'manage'"
|
||||
type="primary"
|
||||
:disabled="!tableSelections || !tableSelections.length"
|
||||
@click="syncToWorkspaceShow"
|
||||
>{{ $tl('p.workspaceSync') }}</a-button
|
||||
>
|
||||
<a-button type="primary" @click="createScript">创建</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #tableHelp>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
<div>{{ $tl('p.scriptTemplateDescription') }}</div>
|
||||
<div>脚本库用于存储管理通用的脚本,脚本库中的脚本不能直接执行。</div>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li>{{ $tl('p.executionEnvNote') }}</li>
|
||||
<li>{{ $tl('p.commandFilePath') }}</li>
|
||||
<li>{{ $tl('p.distributionNodeDescription') }}</li>
|
||||
<li>可以将脚本分发到机器节点中在 DSL 项目中引用,达到多个项目共用相同脚本</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@ -89,56 +74,11 @@
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'name'">
|
||||
<a-tooltip placement="topLeft" :title="text" @click="handleEdit(record)">
|
||||
<a-button type="link" style="padding: 0" size="small">{{ text }}</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'workspaceId'">
|
||||
<a-tag v-if="text === 'GLOBAL'">{{ $tl('c.global') }}</a-tag>
|
||||
<a-tag v-else>{{ $tl('p.workspace') }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.dataIndex === 'operation'">
|
||||
<a-space>
|
||||
<template v-if="mode === 'manage'">
|
||||
<a-button size="small" type="primary" @click="handleExec(record)">{{ $tl('c.execute') }}</a-button>
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">{{ $tl('c.edit') }}</a-button>
|
||||
<a-button size="small" type="primary" @click="handleLog(record)">{{ $tl('p.log') }}</a-button>
|
||||
<a-dropdown>
|
||||
<a @click="(e) => e.preventDefault()">
|
||||
{{ $tl('p.more') }}
|
||||
<DownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a-button size="small" type="primary" @click="handleTrigger(record)">{{
|
||||
$tl('c.trigger')
|
||||
}}</a-button>
|
||||
</a-menu-item>
|
||||
|
||||
<a-menu-item>
|
||||
<a-button size="small" type="primary" danger @click="handleDelete(record)">{{
|
||||
$tl('p.delete')
|
||||
}}</a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
:disabled="!record.nodeIds"
|
||||
@click="handleUnbind(record)"
|
||||
>{{ $tl('p.unbind') }}</a-button
|
||||
>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">{{ $tl('c.edit') }}</a-button>
|
||||
</template>
|
||||
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
@ -148,109 +88,56 @@
|
||||
v-model:open="editScriptVisible"
|
||||
destroy-on-close
|
||||
:z-index="1009"
|
||||
:title="$tl('p.editScript')"
|
||||
title="编辑脚本"
|
||||
:mask-closable="false"
|
||||
width="80vw"
|
||||
:confirm-loading="confirmLoading"
|
||||
@ok="handleEditScriptOk"
|
||||
>
|
||||
<a-form ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 19 }">
|
||||
<a-form-item v-if="temp.id" label="ScriptId" name="id">
|
||||
<a-input v-model:value="temp.id" disabled read-only />
|
||||
<a-form-item v-if="temp.id" label="版本" name="id">
|
||||
<a-input v-model:value="temp.version" disabled read-only />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$tl('p.scriptName')" name="name">
|
||||
<a-input v-model:value="temp.name" :max-length="50" :placeholder="$tl('c.name')" />
|
||||
<a-form-item label="标记" name="tag">
|
||||
<a-input
|
||||
v-model:value="temp.tag"
|
||||
:max-length="50"
|
||||
placeholder="请输入脚本标记,标记只能是字母或者数字长度需要小于 20 并且全局唯一"
|
||||
:disabled="!!temp.id"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$tl('p.scriptContent')" name="context">
|
||||
<a-form-item label="内容" name="script">
|
||||
<a-form-item-rest>
|
||||
<code-editor v-model:content="temp.context" height="40vh" :options="{ mode: 'shell', tabSize: 2 }">
|
||||
<code-editor v-model:content="temp.script" height="40vh" :options="{ mode: 'shell', tabSize: 2 }">
|
||||
</code-editor>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="默认参数" name="defArgs">
|
||||
<a-input v-model="temp.defArgs" placeholder="默认参数" />
|
||||
</a-form-item> -->
|
||||
<a-form-item :label="$tl('p.defaultParam')">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-row v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-col :span="22">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-input
|
||||
v-model:value="item.desc"
|
||||
:addon-before="$tl('p.parameterContent', { count: index + 1 })"
|
||||
:placeholder="$tl('p.content1')" />
|
||||
<a-input
|
||||
v-model:value="item.value"
|
||||
:addon-before="$tl('p.parameterContent', { count: index + 1 })"
|
||||
:placeholder="$tl('p.content2')"
|
||||
/></a-space>
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<MinusCircleOutlined style="color: #ff0000" @click="() => commandParams.splice(index, 1)" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
</a-space>
|
||||
|
||||
<a-button type="primary" @click="() => commandParams.push({})">{{ $tl('p.addParam') }}</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$tl('c.scheduleExecution')" name="autoExecCron">
|
||||
<a-auto-complete
|
||||
v-model:value="temp.autoExecCron"
|
||||
:placeholder="$tl('p.cronExpression')"
|
||||
:options="CRON_DATA_SOURCE"
|
||||
>
|
||||
<template #option="item"> {{ item.title }} {{ item.value }} </template>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$tl('c.description')" name="description">
|
||||
<a-form-item label="描述" name="description">
|
||||
<a-textarea
|
||||
v-model:value="temp.description"
|
||||
:max-length="200"
|
||||
:rows="3"
|
||||
style="resize: none"
|
||||
:placeholder="$tl('p.detailedDescription')"
|
||||
placeholder="请输入脚本描述"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$tl('c.share')" name="global">
|
||||
<a-radio-group v-model:value="temp.global">
|
||||
<a-radio :value="true"> {{ $tl('c.global') }}</a-radio>
|
||||
<a-radio :value="false"> {{ $tl('p.currentWorkspace') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="temp.prohibitSync" :label="$tl('p.disableDistributionNode')">
|
||||
<template #help>{{ $tl('p.controlNodeDistribution') }}</template>
|
||||
<a-tag v-for="(item, index) in temp.nodeList" :key="index"
|
||||
>{{ $tl('p.nodeName') }}{{ item.nodeName }} {{ $tl('p.selectedWorkspace') }}{{ item.workspaceName }}</a-tag
|
||||
>
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<a-tooltip>
|
||||
{{ $tl('p.distributionNodeLabel') }}
|
||||
<template #title> {{ $tl('p.content3') }} </template>
|
||||
分发机器
|
||||
<template #title> 将脚本分发到对应的机器节点中,对应的机器节点可以引用对应的脚本 </template>
|
||||
<QuestionCircleOutlined v-show="!temp.id" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="temp.chooseNode"
|
||||
show-search
|
||||
:filter-option="
|
||||
(input, option) => {
|
||||
const children = option.children && option.children()
|
||||
return (
|
||||
children &&
|
||||
children[0].children &&
|
||||
children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
)
|
||||
}
|
||||
"
|
||||
:placeholder="$tl('p.distributeToNode')"
|
||||
:filter-option="false"
|
||||
placeholder="请选择要分发到的机器节点"
|
||||
mode="multiple"
|
||||
@search="searchMachineList"
|
||||
>
|
||||
<a-select-option v-for="item in nodeList" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
@ -265,32 +152,17 @@
|
||||
<script>
|
||||
import { getScriptLibraryList, editScriptLibrary, delScriptLibrary } from '@/api/system/script-library'
|
||||
import codeEditor from '@/components/codeEditor'
|
||||
import { getNodeListAll } from '@/api/node'
|
||||
import { machineSearch } from '@/api/system/assets-machine'
|
||||
|
||||
import { CHANGE_PAGE, COMPUTED_PAGINATION, CRON_DATA_SOURCE, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
|
||||
|
||||
import { getWorkSpaceListAll } from '@/api/workspace'
|
||||
// import { getWorkSpaceListAll } from '@/api/workspace'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
codeEditor
|
||||
},
|
||||
props: {
|
||||
choose: {
|
||||
type: String,
|
||||
default: 'checkbox'
|
||||
// "radio" ,"checkbox"
|
||||
},
|
||||
mode: {
|
||||
// choose、manage
|
||||
type: String,
|
||||
default: 'manage'
|
||||
},
|
||||
chooseVal: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
props: {},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@ -306,45 +178,29 @@ export default {
|
||||
drawerConsoleVisible: false,
|
||||
columns: [
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: 'id',
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
width: 50,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: this.$tl('c.name'),
|
||||
dataIndex: 'name',
|
||||
title: '标记',
|
||||
dataIndex: 'tag',
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: this.$tl('c.share'),
|
||||
dataIndex: 'workspaceId',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
|
||||
width: '90px'
|
||||
},
|
||||
{
|
||||
title: this.$tl('c.description'),
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: this.$tl('c.scheduleExecution'),
|
||||
dataIndex: 'autoExecCron',
|
||||
title: '版本',
|
||||
dataIndex: 'version',
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
width: '100px',
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: this.$tl('p.modifyTime'),
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
tooltip: true
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'modifyTimeMillis',
|
||||
sorter: true,
|
||||
width: '170px',
|
||||
@ -352,7 +208,7 @@ export default {
|
||||
customRender: ({ text }) => parseTime(text)
|
||||
},
|
||||
{
|
||||
title: this.$tl('p.createTime'),
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTimeMillis',
|
||||
sorter: true,
|
||||
width: '170px',
|
||||
@ -360,54 +216,34 @@ export default {
|
||||
customRender: ({ text }) => parseTime(text)
|
||||
},
|
||||
{
|
||||
title: this.$tl('p.creator'),
|
||||
title: '创建人',
|
||||
dataIndex: 'createUser',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
width: '120px'
|
||||
},
|
||||
{
|
||||
title: this.$tl('p.modifier'),
|
||||
title: '修改人',
|
||||
dataIndex: 'modifyUser',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
width: '120px'
|
||||
},
|
||||
|
||||
{
|
||||
title: this.$tl('p.lastExecutor'),
|
||||
dataIndex: 'lastRunUser',
|
||||
ellipsis: true,
|
||||
width: '120px',
|
||||
tooltip: true
|
||||
},
|
||||
this.mode === 'manage'
|
||||
? {
|
||||
title: this.$tl('c.operation'),
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
|
||||
fixed: 'right',
|
||||
width: '240px'
|
||||
}
|
||||
: {
|
||||
title: this.$tl('c.operation'),
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
|
||||
fixed: 'right',
|
||||
width: '100px'
|
||||
}
|
||||
fixed: 'right',
|
||||
width: '240px'
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
name: [{ required: true, message: this.$tl('p.inputScriptName'), trigger: 'blur' }],
|
||||
context: [{ required: true, message: this.$tl('p.inputScriptContent'), trigger: 'blur' }]
|
||||
// name: [{ required: true, message: this.$tl('p.inputScriptName'), trigger: 'blur' }],
|
||||
// context: [{ required: true, message: this.$tl('p.inputScriptContent'), trigger: 'blur' }]
|
||||
},
|
||||
tableSelections: [],
|
||||
syncToWorkspaceVisible: false,
|
||||
workspaceList: [],
|
||||
triggerVisible: false,
|
||||
commandParams: [],
|
||||
drawerLogVisible: false,
|
||||
|
||||
confirmLoading: false
|
||||
}
|
||||
},
|
||||
@ -417,32 +253,9 @@ export default {
|
||||
},
|
||||
activePage() {
|
||||
return this.$attrs.routerUrl === this.$route.path
|
||||
},
|
||||
rowSelection() {
|
||||
return {
|
||||
onChange: (selectedRowKeys) => {
|
||||
this.tableSelections = selectedRowKeys
|
||||
},
|
||||
selectedRowKeys: this.tableSelections,
|
||||
type: this.choose
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chooseVal: {
|
||||
deep: true,
|
||||
|
||||
handler(v) {
|
||||
if (v) {
|
||||
this.tableSelections = v.split(',')
|
||||
} else {
|
||||
this.tableSelections = []
|
||||
}
|
||||
},
|
||||
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
// this.columns.push(
|
||||
// );
|
||||
@ -453,9 +266,6 @@ export default {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
$tl(key, ...args) {
|
||||
return this.$t(`pages.script.scriptList.${key}`, ...args)
|
||||
},
|
||||
// 加载数据
|
||||
loadData(pointerEvent) {
|
||||
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page
|
||||
@ -470,63 +280,50 @@ export default {
|
||||
},
|
||||
parseTime,
|
||||
// 获取所有节点
|
||||
getAllNodeList() {
|
||||
getNodeListAll().then((res) => {
|
||||
searchMachineList(name) {
|
||||
machineSearch({
|
||||
name: name,
|
||||
limit: 10,
|
||||
appendIds: this.temp.machineIds || ''
|
||||
}).then((res) => {
|
||||
this.nodeList = res.data || []
|
||||
})
|
||||
},
|
||||
createScript() {
|
||||
this.temp = {}
|
||||
this.commandParams = []
|
||||
|
||||
this.editScriptVisible = true
|
||||
this.getAllNodeList()
|
||||
this.searchMachineList()
|
||||
},
|
||||
// 修改
|
||||
handleEdit(record) {
|
||||
getScriptItem({
|
||||
id: record.id
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
const data = res.data.data
|
||||
this.temp = Object.assign({}, data)
|
||||
this.temp = Object.assign({}, record)
|
||||
|
||||
this.commandParams = data?.defArgs ? JSON.parse(data.defArgs) : []
|
||||
//this.commandParams = data?.defArgs ? JSON.parse(data.defArgs) : []
|
||||
|
||||
this.temp = {
|
||||
...this.temp,
|
||||
prohibitSync: res.data.prohibitSync,
|
||||
nodeList: res.data.nodeList,
|
||||
chooseNode: data?.nodeIds ? data.nodeIds.split(',') : [],
|
||||
global: data.workspaceId === 'GLOBAL',
|
||||
workspaceId: ''
|
||||
}
|
||||
this.editScriptVisible = true
|
||||
this.getAllNodeList()
|
||||
}
|
||||
})
|
||||
this.temp = {
|
||||
...this.temp,
|
||||
chooseNode: record?.machineIds ? record.machineIds.split(',') : []
|
||||
}
|
||||
this.editScriptVisible = true
|
||||
this.searchMachineList()
|
||||
// getScriptItem({
|
||||
// id: record.id
|
||||
// }).then((res) => {
|
||||
// if (res.code === 200) {
|
||||
// const data = res.data.data
|
||||
// }
|
||||
// })
|
||||
},
|
||||
// 提交 Script 数据
|
||||
handleEditScriptOk() {
|
||||
// 检验表单
|
||||
this.$refs['editScriptForm'].validate().then(() => {
|
||||
if (this.commandParams && this.commandParams.length > 0) {
|
||||
for (let i = 0; i < this.commandParams.length; i++) {
|
||||
if (!this.commandParams[i].desc) {
|
||||
$notification.error({
|
||||
message: this.$tl('p.paramDescriptionPrefix') + (i + 1) + this.$tl('p.paramDescriptionSuffix')
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.temp.defArgs = JSON.stringify(this.commandParams)
|
||||
} else {
|
||||
this.temp.defArgs = ''
|
||||
}
|
||||
// 提交数据
|
||||
this.temp.nodeIds = this.temp?.chooseNode?.join(',')
|
||||
this.temp.machineIds = this.temp?.chooseNode?.join(',')
|
||||
delete this.temp.nodeList
|
||||
this.confirmLoading = true
|
||||
editScript(this.temp)
|
||||
editScriptLibrary(this.temp)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
// 成功
|
||||
@ -546,13 +343,13 @@ export default {
|
||||
},
|
||||
handleDelete(record) {
|
||||
$confirm({
|
||||
title: this.$tl('p.systemTip'),
|
||||
content: this.$tl('p.confirmDeleteScript'),
|
||||
title: '系统提示',
|
||||
content: '确定要删除此脚本库吗?',
|
||||
zIndex: 1009,
|
||||
okText: this.$tl('c.confirm'),
|
||||
cancelText: this.$tl('c.cancel'),
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return deleteScript({
|
||||
return delScriptLibrary({
|
||||
id: record.id
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
@ -565,29 +362,11 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
// 执行 Script
|
||||
handleExec(record) {
|
||||
this.temp = Object.assign(record)
|
||||
this.drawerTitle = `${$tl('p.console')}(${this.temp.name})`
|
||||
this.drawerConsoleVisible = true
|
||||
},
|
||||
// 关闭 console
|
||||
onConsoleClose() {
|
||||
this.drawerConsoleVisible = false
|
||||
},
|
||||
|
||||
// 分页、排序、筛选变化时触发
|
||||
changePage(pagination, filters, sorter) {
|
||||
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 加载工作空间数据
|
||||
loadWorkSpaceListAll() {
|
||||
getWorkSpaceListAll().then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.workspaceList = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user