🚀 feat(agent): 全局脚本库

This commit is contained in:
小吾立 2024-06-14 18:12:25 +08:00
parent fade185dad
commit 87d73b583f
12 changed files with 187 additions and 353 deletions

View File

@ -2,6 +2,10 @@
## 2.11.6.3-beta
### 🐣 新增功能
1. 【agent】新增 全局脚本库
### 🐞 解决BUG、优化功能
1. 【server】修复 gogs 仓库令牌导入异常(感谢@张飞鸿)

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
/**
* 版本

View File

@ -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")),

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,版本,

1 tableName name type len defaultValue notNull primaryKey comment tableComment
28 STATIC_FILE_STORAGE level Integer false false 层级
29 STATIC_FILE_STORAGE triggerToken String 100 false false 触发器token
30 SCRIPT_LIBRARY id String 50 true true id 静态文件管理
31 SCRIPT_LIBRARY createTimeMillis Long false false 数据创建时间
32 SCRIPT_LIBRARY modifyTimeMillis Long false false 数据修改时间
33 SCRIPT_LIBRARY modifyUser String 50 false false 修改人
34 SCRIPT_LIBRARY tag String 50 true false 标签
35 SCRIPT_LIBRARY description String 255 false false 描述
36 SCRIPT_LIBRARY script text false false 描述
37 SCRIPT_LIBRARY machineIds text false false 关联的机器节点
38 SCRIPT_LIBRARY version String 50 true false 版本
39
40

View File

@ -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
})
}

View File

@ -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']
}
}

View File

@ -63,7 +63,8 @@ const i18n = createI18n<I18nLocaleType>({
const { local } = lang[key]
pre[key] = local
return pre
}, {})
}, {}),
warnHtmlMessage: false
})
export default i18n

View File

@ -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: {
// choosemanage
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
}
})
}
}
}