🚀 feat(server): 服务端脚本支持引用全局脚本库

This commit is contained in:
bwcx_jzy 2024-06-16 17:52:12 +08:00
parent 1f920d19b2
commit 3cfc8e8c05
31 changed files with 389 additions and 494 deletions

View File

@ -2,6 +2,10 @@
## 2.11.6.5-beta
### 🐣 新增功能
1. 【server】新增 服务端脚本支持引用全局脚本库(`G@("xx")` xx 为脚本标记)
### 🐞 解决BUG、优化功能
1. 【agent】修复 不同工作空间下同一个机器节点相同的项目ID的项目数据被覆盖感谢@小朱)

View File

@ -978,7 +978,7 @@ public class BuildExecuteManage implements Runnable {
final String[] lastMsg = new String[1];
try {
// 创建执行器
scriptFile = scriptModel.scriptFile();
scriptFile = scriptExecuteLogServer.toExecLogFile(scriptModel);
scriptExecuteLogServer.updateStatus(logModel.getId(), CommandExecLogModel.Status.ING);
int waitFor = JpomApplication.getInstance().execScript(scriptModel.getContext(), file -> {
try {

View File

@ -11,6 +11,7 @@ package org.dromara.jpom.controller.node;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
@ -31,7 +32,6 @@ import org.dromara.jpom.permission.Feature;
import org.dromara.jpom.permission.MethodFeature;
import org.dromara.jpom.permission.SystemPermission;
import org.dromara.jpom.service.system.SystemParametersServer;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.system.ServerConfig;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
@ -180,7 +180,8 @@ public class NodeUpdateController extends BaseServerController {
@GetMapping(value = "fast_install.json", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<JSONObject> fastInstall(HttpServletRequest request) {
boolean beta = RemoteVersion.betaRelease();
InputStream inputStream = ExtConfigBean.getConfigResourceInputStream(beta ? "/fast-install-beta.json" : "/fast-install-release.json");
String language = I18nMessageUtil.tryGetLanguage();
InputStream inputStream = ResourceUtil.getStream("classpath:/fast-install/" + language + (beta ? "/beta.json" : "/release.json"));
String json = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8);
JSONObject jsonObject = new JSONObject();
JpomManifest instance = JpomManifest.getInstance();

View File

@ -51,6 +51,8 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* 服务端脚本
*
* @author bwcx_jzy
* @since 2022/1/19
*/

View File

@ -0,0 +1,41 @@
package org.dromara.jpom.func.assets.controller;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.func.assets.model.ScriptLibraryModel;
import org.dromara.jpom.func.assets.server.ScriptLibraryServer;
import org.dromara.jpom.model.PageResultDto;
import org.dromara.jpom.permission.ClassFeature;
import org.dromara.jpom.permission.Feature;
import org.dromara.jpom.permission.MethodFeature;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author bwcx_jzy
* @since 2024/6/16
*/
@RestController
@RequestMapping(value = "/system/assets/script-library")
@Feature(cls = ClassFeature.SYSTEM_ASSETS_GLOBAL_SCRIPT)
@Slf4j
public class ScriptLibraryNoPermissionController {
private final ScriptLibraryServer scriptLibraryServer;
public ScriptLibraryNoPermissionController(ScriptLibraryServer scriptLibraryServer) {
this.scriptLibraryServer = scriptLibraryServer;
}
@PostMapping(value = "list-data-no-permission", produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.LIST)
public IJsonMessage<PageResultDto<ScriptLibraryModel>> listJson(HttpServletRequest request) {
PageResultDto<ScriptLibraryModel> pageResultDto = scriptLibraryServer.listPage(request);
return JsonMessage.success("", pageResultDto);
}
}

View File

@ -1,9 +1,19 @@
package org.dromara.jpom.func.assets.server;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.func.assets.model.ScriptLibraryModel;
import org.dromara.jpom.service.h2db.BaseDbService;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author bwcx_jzy1
@ -12,4 +22,38 @@ import org.springframework.stereotype.Service;
@Service
@Slf4j
public class ScriptLibraryServer extends BaseDbService<ScriptLibraryModel> {
private final Pattern pattern = PatternPool.get("G@\\(\"(.*?)\"\\)", Pattern.DOTALL);
/**
* 引用替换
*
* @param script 脚本
* @return 替换后的脚本
*/
public String referenceReplace(String script) {
if (StrUtil.isEmpty(script)) {
return script;
}
Map<String, ScriptLibraryModel> map = new HashMap<>(3);
Matcher matcher = pattern.matcher(script);
StringBuffer modified = new StringBuffer();
while (matcher.find()) {
String tag = matcher.group(1);
ScriptLibraryModel scriptLibraryModel = map.get(tag);
if (scriptLibraryModel == null) {
ScriptLibraryModel where = new ScriptLibraryModel();
where.setTag(tag);
List<ScriptLibraryModel> libraryModels = this.listByBean(where);
scriptLibraryModel = CollUtil.getFirst(libraryModels);
if (scriptLibraryModel != null) {
map.put(tag, scriptLibraryModel);
}
}
Assert.notNull(scriptLibraryModel, StrUtil.format("未找到脚本库信息:{},请检查引用标记是否正确或者脚本是否被删除", tag));
matcher.appendReplacement(modified, scriptLibraryModel.getScript());
}
matcher.appendTail(modified);
return modified.toString();
}
}

View File

@ -10,23 +10,16 @@
package org.dromara.jpom.model.script;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.i18n.I18nMessageUtil;
import org.dromara.jpom.db.TableName;
import org.dromara.jpom.model.BaseWorkspaceModel;
import org.dromara.jpom.script.CommandParam;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import java.io.File;
import java.io.InputStream;
/**
* @author bwcx_jzy
@ -95,22 +88,6 @@ public class ScriptModel extends BaseWorkspaceModel {
return FileUtil.file(path, "log", executeId + ".log");
}
/**
* 加载脚本文件
*
* @return file
*/
public File scriptFile() {
InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX);
String defaultTemplate = IoUtil.readUtf8(templateInputStream);
String dataPath = JpomApplication.getInstance().getDataPath();
File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX));
FileUtils.writeScript(defaultTemplate + this.getContext(), scriptFile, ExtConfigBean.getConsoleLogCharset());
return scriptFile;
}
@Override
protected boolean hasCreateUser() {
return true;

View File

@ -9,14 +9,24 @@
*/
package org.dromara.jpom.service.script;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.func.assets.server.ScriptLibraryServer;
import org.dromara.jpom.model.data.CommandExecLogModel;
import org.dromara.jpom.model.script.ScriptExecuteLogModel;
import org.dromara.jpom.model.script.ScriptModel;
import org.dromara.jpom.service.h2db.BaseGlobalOrWorkspaceService;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.InputStream;
/**
* @author bwcx_jzy
@ -25,6 +35,15 @@ import java.io.File;
@Service
public class ScriptExecuteLogServer extends BaseGlobalOrWorkspaceService<ScriptExecuteLogModel> {
private final ScriptLibraryServer scriptLibraryServer;
private final JpomApplication jpomApplication;
public ScriptExecuteLogServer(ScriptLibraryServer scriptLibraryServer,
JpomApplication jpomApplication) {
this.scriptLibraryServer = scriptLibraryServer;
this.jpomApplication = jpomApplication;
}
/**
* 创建执行记录
*
@ -79,6 +98,24 @@ public class ScriptExecuteLogServer extends BaseGlobalOrWorkspaceService<ScriptE
this.updateById(model);
}
/**
* 加载脚本文件
*
* @param scriptModel 脚本对象
* @return file
*/
public File toExecLogFile(ScriptModel scriptModel) {
InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX);
String defaultTemplate = IoUtil.readUtf8(templateInputStream);
String context = defaultTemplate + scriptModel.getContext();
context = scriptLibraryServer.referenceReplace(context);
//
String dataPath = jpomApplication.getDataPath();
File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX));
FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset());
return scriptFile;
}
@Override
protected void executeClearImpl(int h2DbLogStorageCount) {

View File

@ -118,14 +118,19 @@ public abstract class BaseProxyHandler extends BaseHandler {
JSONObject json = JSONObject.parseObject(msg);
String op = json.getString("op");
ConsoleCommandOp consoleCommandOp = StrUtil.isNotEmpty(op) ? ConsoleCommandOp.valueOf(op) : null;
String textMessage;
if (proxySession != null) {
textMessage = this.handleTextMessage(attributes, session, proxySession, json, consoleCommandOp);
} else {
textMessage = this.handleTextMessage(attributes, session, json, consoleCommandOp);
}
if (textMessage != null) {
this.sendMsg(session, textMessage);
try {
String textMessage;
if (proxySession != null) {
textMessage = this.handleTextMessage(attributes, session, proxySession, json, consoleCommandOp);
} else {
textMessage = this.handleTextMessage(attributes, session, json, consoleCommandOp);
}
if (textMessage != null) {
this.sendMsg(session, textMessage);
}
} catch (Exception e) {
log.error("处理消息异常", e);
this.sendMsg(session, "处理消息异常:" + e.getMessage());
}
}

View File

@ -63,12 +63,16 @@ public class ServerScriptProcessBuilder extends BaseRunScript implements Runnabl
private ServerScriptProcessBuilder(ScriptModel nodeScriptModel, String executeId, String args, Map<String, String> paramMap) {
super(nodeScriptModel.logFile(executeId), CharsetUtil.CHARSET_UTF_8);
//
if (scriptExecuteLogServer == null) {
scriptExecuteLogServer = SpringUtil.getBean(ScriptExecuteLogServer.class);
}
this.executeId = executeId;
//
WorkspaceEnvVarService workspaceEnvVarService = SpringUtil.getBean(WorkspaceEnvVarService.class);
environmentMapBuilder = workspaceEnvVarService.getEnv(nodeScriptModel.getWorkspaceId());
environmentMapBuilder.putStr(paramMap);
scriptFile = nodeScriptModel.scriptFile();
scriptFile = scriptExecuteLogServer.toExecLogFile(nodeScriptModel);
//
String script = FileUtil.getAbsolutePath(scriptFile);
processBuilder = new ProcessBuilder();
@ -81,10 +85,6 @@ public class ServerScriptProcessBuilder extends BaseRunScript implements Runnabl
Map<String, String> environment = processBuilder.environment();
environment.putAll(environmentMapBuilder.environment());
processBuilder.directory(scriptFile.getParentFile());
//
if (scriptExecuteLogServer == null) {
scriptExecuteLogServer = SpringUtil.getBean(ScriptExecuteLogServer.class);
}
}
/**

View File

@ -0,0 +1,18 @@
[
{
"name": "Primary address (default installation)",
"url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default+beta"
},
{
"name": "Alternative address (default installation)",
"url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default+beta"
},
{
"name": "Primary address (custom installation)",
"url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk+beta"
},
{
"name": "Alternative address (custom installation)",
"url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk+beta"
}
]

View File

@ -0,0 +1,18 @@
[
{
"name": "Primary address (default installation)",
"url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default"
},
{
"name": "Alternative address (default installation)",
"url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default"
},
{
"name": "Primary address (custom installation)",
"url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk"
},
{
"name": "Alternative address (custom installation)",
"url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk"
}
]

View File

@ -0,0 +1,42 @@
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.ReUtil;
import org.junit.Test;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author bwcx_jzy
* @since 2024/6/16
*/
public class TestScriptLink {
@Test
public void test() {
String content = "test\n" +
"\n" +
"echo \"test -12\"\n" +
"\n" +
"echo $buildNumberId\n" +
"G@(\"xxxx\")\n" +
"\n" +
"G@(\"xxxx\")\n" +
"\n" +
"G@(\"ttttt\")";
String reg = "G@\\(\"(.*?)\"\\)";
Pattern pattern = PatternPool.get("G@\\(\"(.*?)\"\\)", Pattern.DOTALL);
List<String> all = ReUtil.findAll(reg, content, 1);
all.forEach(System.out::println);
Matcher matcher = pattern.matcher(content);
StringBuffer modifiedLine = new StringBuffer();
while (matcher.find()) {
String group = matcher.group(1);
matcher.appendReplacement(modifiedLine, String.format("\"%s\"", group));
}
matcher.appendTail(modifiedLine);
System.out.println(modifiedLine);
}
}

View File

@ -1,6 +1,9 @@
<template>
<a-modal v-bind="props" :body-style="bodyStyle">
<a-modal v-bind="props" :body-style="bodyStyle" :z-index="zIndex">
<slot name="default"></slot>
<template v-if="slots.footer" #footer>
<slot name="footer"></slot>
</template>
</a-modal>
</template>
<script lang="ts">
@ -8,6 +11,7 @@ import { modalProps } from 'ant-design-vue/es/modal/Modal'
import { initDefaultProps } from 'ant-design-vue/es/_util/props-util'
import { CustomSlotsType } from 'ant-design-vue/es/_util/type'
import { CSSProperties, defineComponent } from 'vue'
import { increaseZIndex } from '@/utils/utils'
export default defineComponent({
name: 'CustomModal',
@ -16,17 +20,20 @@ export default defineComponent({
confirmLoading: false,
okType: 'primary'
}),
slots: Object as CustomSlotsType<{ default: any }>,
setup(props, { emit }) {
slots: Object as CustomSlotsType<{ default: any; footer?: any }>,
setup(props, { emit, slots }) {
const bodyStyle: CSSProperties = {
maxHeight: 'calc(100vh - 196px )',
overflowY: 'auto',
...props.bodyStyle
}
return {
props,
zIndex: increaseZIndex(),
bodyStyle,
emit
emit,
slots
}
}
})

View File

@ -19,9 +19,9 @@
>
<!-- 编辑区 -->
<a-modal
<CustomModal
v-if="editVisible"
v-model:open="editVisible"
:z-index="1009"
destroy-on-close
:title="$t('components.parameterWidget.index.77ecbd27')"
:mask-closable="false"
@ -39,7 +39,7 @@
<a-input v-model:value="temp.value" :placeholder="$t('components.parameterWidget.index.8485924b')" />
</a-form-item>
</a-form>
</a-modal>
</CustomModal>
</div>
</template>
<script lang="ts" setup>

View File

@ -105,7 +105,9 @@ declare module 'vue' {
ColumnHeightOutlined: typeof import('@ant-design/icons-vue')['ColumnHeightOutlined']
CompositionTransfer: typeof import('./../components/compositionTransfer/composition-transfer.vue')['default']
CompressOutlined: typeof import('@ant-design/icons-vue')['CompressOutlined']
copy: typeof import('./../components/customModal copy/index.vue')['default']
CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined']
CustomDrawer: typeof import('./../components/customDrawer/index.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']

View File

@ -1165,13 +1165,13 @@
</a-card>
</a-spin>
<!-- 选择仓库 -->
<a-drawer
<CustomDrawer
v-if="repositoryisible"
destroy-on-close
:title="`${$t('pages.build.edit.e95bc1a3')}`"
placement="right"
:open="repositoryisible"
width="85vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -1226,15 +1226,15 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
<!-- 选择脚本 -->
<a-drawer
<CustomDrawer
v-if="chooseScriptVisible != 0"
destroy-on-close
:title="`${$t('pages.build.edit.2fcc9ae3')}`"
placement="right"
:open="chooseScriptVisible != 0"
width="70vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -1249,9 +1249,9 @@
:choose-val="
chooseScriptVisible === 1
? tempExtraData.noticeScriptId
: temp.script && temp.script.indexOf('$ref.script.') != -1
? temp.script.replace('$ref.script.')
: ''
: temp.script?.indexOf('$ref.script.') != -1
? temp.script.replace('$ref.script.', '')
: ''
"
mode="choose"
@confirm="
@ -1293,15 +1293,15 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
<!-- 查看容器 -->
<a-drawer
<CustomDrawer
v-if="dockerListVisible != 0"
destroy-on-close
:title="`${$t('pages.build.edit.eed6df5e')}`"
placement="right"
:open="dockerListVisible != 0"
width="70vw"
:z-index="1009"
@close="
() => {
dockerListVisible = 0
@ -1309,7 +1309,7 @@
"
>
<docker-list v-if="dockerListVisible" ref="dockerlist"></docker-list>
</a-drawer>
</CustomDrawer>
<!-- 查看命令示例 -->
<a-modal

View File

@ -459,11 +459,11 @@
</a-form>
</a-modal>
<!-- 创建/编辑分发项目 -->
<a-modal
<CustomModal
v-if="editDispatchVisible"
v-model:open="editDispatchVisible"
destroy-on-close
:confirm-loading="confirmLoading"
:z-index="900"
width="60vw"
:title="temp.type === 'edit' ? $t('pages.dispatch.list.7d755f9b') : $t('pages.dispatch.list.898758b9')"
:mask-closable="false"
@ -965,7 +965,7 @@
</a-collapse>
</a-form>
</a-spin>
</a-modal>
</CustomModal>
<!-- 分发项目 -->
<start-dispatch
v-if="dispatchVisible"
@ -1014,13 +1014,13 @@
></whiteList>
</a-modal>
<!-- 查看服务端脚本 -->
<a-drawer
<CustomDrawer
v-if="viewScriptVisible"
destroy-on-close
:title="`${$t('pages.dispatch.list.9bc942bf')}`"
placement="right"
:open="viewScriptVisible"
width="70vw"
:z-index="1109"
@close="
() => {
viewScriptVisible = false
@ -1036,7 +1036,7 @@
}
"
></scriptPage>
</a-drawer>
</CustomDrawer>
</div>
</template>
<script>

View File

@ -181,13 +181,13 @@
</a-form>
</a-modal>
<!-- 选择构建 -->
<a-drawer
<CustomDrawer
v-if="chooseVisible === 1"
destroy-on-close
:title="`${$t('pages.dispatch.start.afb1ec30')}`"
placement="right"
:open="chooseVisible === 1"
width="80vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -239,15 +239,15 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
<!-- 选择构建产物 -->
<a-drawer
<CustomDrawer
v-if="chooseVisible === 2"
destroy-on-close
:title="`${$t('pages.dispatch.start.a2d28d82')}`"
placement="right"
:open="chooseVisible === 2"
width="80vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -300,15 +300,15 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
<!-- 选择文件 -->
<a-drawer
<CustomDrawer
v-if="chooseVisible === 3"
destroy-on-close
:title="`${$t('pages.dispatch.start.2a688d49')}`"
placement="right"
:open="chooseVisible === 3"
width="80vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -357,15 +357,15 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
<!-- 选择静态文件 -->
<a-drawer
<CustomDrawer
v-if="chooseVisible === 4"
destroy-on-close
:title="`${$t('pages.dispatch.start.e5aa6b98')}`"
placement="right"
:open="chooseVisible === 4"
width="80vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -414,7 +414,7 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
</div>
</template>
<script>

View File

@ -237,14 +237,14 @@
"
></whiteList>
</a-modal>
<a-drawer
<!-- 选择脚本 -->
<CustomDrawer
v-if="chooseScriptVisible != 0"
destroy-on-close
:title="$t('pages.file-manager.fileStorage.releaseFile.952117a8')"
placement="right"
:open="chooseScriptVisible != 0"
width="70vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -259,11 +259,11 @@
:choose-val="
chooseScriptVisible === 1
? temp.beforeScript?.indexOf('$ref.script.') !== -1
? temp.beforeScript?.replace('$ref.script.')
? temp.beforeScript?.replace('$ref.script.', '')
: ''
: temp.afterScript?.indexOf('$ref.script.') !== -1
? temp.afterScript?.replace('$ref.script.')
: ''
? temp.afterScript?.replace('$ref.script.', '')
: ''
"
mode="choose"
@confirm="
@ -303,7 +303,7 @@
>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
</div>
</template>
<script>

View File

@ -92,7 +92,7 @@
<a-card size="small">
<template #title> {{ $t('pages.layout.overview.b75360fe') }} </template>
<template #extra>
<a href="#" @click="handleUserlog(2)">more</a>
<a href="#" @click="handleUserlog(2)">更多</a>
</template>
<a-timeline v-if="loginLog && loginLog.length">
<a-timeline-item v-for="(item, index) in loginLog" :key="index" :color="item.success ? 'green' : 'red'">
@ -115,7 +115,7 @@
<a-tooltip :title="$t('pages.layout.overview.a53992b3')"><QuestionCircleOutlined /></a-tooltip>
</template>
<template #extra>
<a href="#" @click="handleUserlog(1)">more</a>
<a href="#" @click="handleUserlog(1)">更多</a>
</template>
<a-timeline v-if="operateLog && operateLog.length">
<a-timeline-item

View File

@ -189,9 +189,9 @@
</template>
</CustomTable>
<!-- 编辑区 -->
<a-modal
<CustomModal
v-if="editVisible"
v-model:open="editVisible"
:z-index="1009"
destroy-on-close
:confirm-loading="confirmLoading"
:title="$t('pages.repository.repository-list.b3885a87')"
@ -413,10 +413,10 @@
/>
</a-form-item>
</a-form>
</a-modal>
<a-modal
</CustomModal>
<CustomModal
v-if="giteeImportVisible"
v-model:open="giteeImportVisible"
:z-index="1009"
destroy-on-close
:title="$t('pages.repository.repository-list.3f4ed4fb')"
width="80%"
@ -526,7 +526,7 @@
</template>
</template>
</a-table>
</a-modal>
</CustomModal>
<!-- 选择仓库确认区域 -->
<!-- <div style="padding-top: 50px" v-if="this.choose">
<div

View File

@ -154,10 +154,10 @@
</template>
</CustomTable>
<!-- 编辑区 -->
<a-modal
<CustomModal
v-if="editScriptVisible"
v-model:open="editScriptVisible"
destroy-on-close
:z-index="1009"
:title="$t('pages.script.script-list.c05890d1')"
:mask-closable="false"
width="80vw"
@ -173,13 +173,19 @@
</a-form-item>
<a-form-item :label="$t('pages.script.script-list.709314dd')" name="context">
<a-form-item-rest>
<code-editor v-model:content="temp.context" height="40vh" :options="{ mode: 'shell', tabSize: 2 }">
<code-editor
v-model:content="temp.context"
height="40vh"
:show-tool="true"
:options="{ mode: 'shell', tabSize: 2 }"
>
<template #tool_before>
<a-button type="link" @click="scriptLibraryVisible = true">脚本库 </a-button>
</template>
</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="$t('pages.script.script-list.74765338')">
<a-space direction="vertical" style="width: 100%">
<a-row v-for="(item, index) in commandParams" :key="item.key">
@ -271,7 +277,7 @@
</a-select>
</a-form-item>
</a-form>
</a-modal>
</CustomModal>
<!-- 脚本控制台组件 -->
<a-drawer
destroy-on-close
@ -398,6 +404,72 @@
>
<script-log v-if="drawerLogVisible" :script-id="temp.id" />
</a-drawer>
<!-- 查看脚本库 -->
<CustomDrawer
v-if="scriptLibraryVisible"
destroy-on-close
title="查看脚本库"
placement="right"
:open="scriptLibraryVisible"
width="85vw"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
scriptLibraryVisible = false
}
"
>
<ScriptLibraryNoPermission
v-if="scriptLibraryVisible"
ref="scriptLibraryRef"
@script-confirm="
(script) => {
temp = { ...temp, context: script }
scriptLibraryVisible = false
}
"
@tag-confirm="
(tag) => {
temp = { ...temp, context: temp.context + `\nG@(\&quot;${tag}\&quot;)` }
scriptLibraryVisible = false
}
"
></ScriptLibraryNoPermission>
<template #footer>
<a-space>
<a-button
@click="
() => {
scriptLibraryVisible = false
}
"
>
取消
</a-button>
<a-button
type="primary"
@click="
() => {
$refs['scriptLibraryRef'].handerScriptConfirm()
}
"
>
替换引用
</a-button>
<a-button
type="primary"
@click="
() => {
$refs['scriptLibraryRef'].handerTagConfirm()
}
"
>
标记引用
</a-button>
</a-space>
</template>
</CustomDrawer>
<!-- <div style="padding-top: 50px" v-if="mode === 'choose'">
<div
:style="{
@ -442,17 +514,20 @@ import codeEditor from '@/components/codeEditor'
import { getNodeListAll } from '@/api/node'
import ScriptConsole from '@/pages/script/script-console'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
import { CRON_DATA_SOURCE } from '@/utils/const-i18n'
import { getWorkSpaceListAll } from '@/api/workspace'
import ScriptLog from '@/pages/script/script-log'
import ScriptLibraryNoPermission from '@/pages/system/assets/script-library/no-permission'
import { mapState } from 'pinia'
import { useAppStore } from '@/stores/app'
export default {
components: {
ScriptConsole,
codeEditor,
ScriptLog
ScriptLog,
ScriptLibraryNoPermission
},
props: {
choose: {
@ -588,7 +663,8 @@ export default {
triggerVisible: false,
commandParams: [],
drawerLogVisible: false,
confirmLoading: false
confirmLoading: false,
scriptLibraryVisible: false
}
},
computed: {
@ -599,6 +675,7 @@ export default {
activePage() {
return this.$attrs.routerUrl === this.$route.path
},
rowSelection() {
return {
onChange: (selectedRowKeys) => {

View File

@ -587,14 +587,13 @@
</a-space>
</a-modal>
<!-- 选择证书文件 -->
<a-drawer
<CustomDrawer
v-if="certificateVisible"
destroy-on-close
:title="`${$t('pages.system.assets.docker.list.5540289f')}`"
placement="right"
:open="certificateVisible"
width="85vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"
@close="
() => {
@ -641,7 +640,7 @@
</a-button>
</a-space>
</template>
</a-drawer>
</CustomDrawer>
</div>
</template>
<script>

View File

@ -629,7 +629,7 @@
</a-form>
</a-modal>
<!-- 分发机器配置 -->
<a-modal
<CustomModal
v-model:open="nodeConfigVisible"
destroy-on-close
:confirm-loading="confirmLoading"
@ -686,7 +686,7 @@
></code-editor>
</a-form-item>
</a-form>
</a-modal>
</CustomModal>
</div>
</template>
<script>

View File

@ -1,386 +0,0 @@
<template>
<div>
<!-- 数据表格 -->
<CustomTable
is-show-tools
default-auto-refresh
:auto-refresh-time="30"
:active-page="activePage"
table-name="script-library"
:empty-description="$t('pages.system.assets.script-library.65418a5e')"
:data-source="list"
size="middle"
:columns="columns"
:pagination="pagination"
bordered
row-key="id"
:scroll="{
x: 'max-content'
}"
@change="changePage"
@refresh="loadData"
>
<template #title>
<a-space wrap class="search-box">
<a-input
v-model:value="listQuery['%tag%']"
:placeholder="$t('pages.system.assets.script-library.95547f9')"
allow-clear
class="search-input-item"
@press-enter="loadData"
/>
<a-input
v-model:value="listQuery['%version%']"
:placeholder="$t('pages.system.assets.script-library.81634069')"
allow-clear
class="search-input-item"
@press-enter="loadData"
/>
<a-input
v-model:value="listQuery['%description%']"
:placeholder="$t('pages.system.assets.script-library.f89e58f1')"
class="search-input-item"
@press-enter="loadData"
/>
<a-tooltip :title="$t('pages.system.assets.script-library.986e8dc2')">
<a-button :loading="loading" type="primary" @click="loadData">{{
$t('pages.system.assets.script-library.43934f6d')
}}</a-button>
</a-tooltip>
<a-button type="primary" @click="createScript">{{
$t('pages.system.assets.script-library.9f3089ce')
}}</a-button>
</a-space>
</template>
<template #tableHelp>
<a-tooltip>
<template #title>
<div>{{ $t('pages.system.assets.script-library.41c0cbe5') }}</div>
<div>
<ul>
<li>{{ $t('pages.system.assets.script-library.423e1405') }}</li>
</ul>
</div>
</template>
<QuestionCircleOutlined />
</a-tooltip>
</template>
<template #tableBodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'nodeId'">
<a-tooltip placement="topLeft" :title="text">
<span>{{ nodeMap[text] }}</span>
</a-tooltip>
</template>
<template v-else-if="column.tooltip">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button size="small" type="primary" @click="handleEdit(record)">{{
$t('pages.system.assets.script-library.ad207008')
}}</a-button>
<a-button size="small" type="primary" danger @click="handleDelete(record)">{{
$t('pages.system.assets.script-library.ecbd7449')
}}</a-button>
</a-space>
</template>
</template>
</CustomTable>
<!-- pages.system.assets.script-library.ad207008区 -->
<a-modal
v-model:open="editScriptVisible"
destroy-on-close
:z-index="1009"
:title="$t('pages.system.assets.script-library.16a6aab6')"
: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="$t('pages.system.assets.script-library.81634069')" name="id">
<a-input v-model:value="temp.version" disabled read-only />
</a-form-item>
<a-form-item :label="$t('pages.system.assets.script-library.2d62ebdb')" name="tag">
<a-input
v-model:value="temp.tag"
:max-length="50"
:placeholder="$t('pages.system.assets.script-library.e37b1ac9')"
:disabled="!!temp.id"
/>
</a-form-item>
<a-form-item :label="$t('pages.system.assets.script-library.3e7aa0ad')" name="script">
<a-form-item-rest>
<code-editor
v-model:content="temp.script"
:show-tool="true"
height="40vh"
:options="{ mode: 'shell', tabSize: 2 }"
>
</code-editor>
</a-form-item-rest>
</a-form-item>
<a-form-item :label="$t('pages.system.assets.script-library.f89e58f1')" name="description">
<a-textarea
v-model:value="temp.description"
:max-length="200"
:rows="3"
style="resize: none"
:placeholder="$t('pages.system.assets.script-library.43075dd9')"
/>
</a-form-item>
<a-form-item>
<template #label>
<a-tooltip
>{{ $t('pages.system.assets.script-library.4f5ca5e3')
}}<template #title>{{ $t('pages.system.assets.script-library.33437c9b') }}</template>
<QuestionCircleOutlined v-show="!temp.id" />
</a-tooltip>
</template>
<template #help>{{ $t('pages.system.assets.script-library.5251812f') }}</template>
<a-select
v-model:value="temp.chooseNode"
show-search
:filter-option="false"
:placeholder="$t('pages.system.assets.script-library.4722ff63')"
mode="multiple"
@search="searchMachineList"
>
<a-select-option v-for="item in nodeList" :key="item.id" :value="item.id">
{{ item.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { getScriptLibraryList, editScriptLibrary, delScriptLibrary } from '@/api/system/script-library'
import codeEditor from '@/components/codeEditor'
import { machineSearch } from '@/api/system/assets-machine'
import { CRON_DATA_SOURCE } from '@/utils/const-i18n'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
// import { getWorkSpaceListAll } from '@/api/workspace'
export default {
components: {
codeEditor
},
props: {},
data() {
return {
// choose: this.choose,
loading: false,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
CRON_DATA_SOURCE,
list: [],
temp: {},
nodeList: [],
editScriptVisible: false,
drawerTitle: '',
drawerConsoleVisible: false,
columns: [
{
title: this.$t('pages.system.assets.script-library.2d62ebdb'),
dataIndex: 'tag',
ellipsis: true,
sorter: true,
width: 150
},
{
title: this.$t('pages.system.assets.script-library.81634069'),
dataIndex: 'version',
ellipsis: true,
sorter: true,
width: '100px',
tooltip: true
},
{
title: this.$t('pages.system.assets.script-library.f89e58f1'),
dataIndex: 'description',
ellipsis: true,
width: 200,
tooltip: true
},
{
title: this.$t('pages.system.assets.script-library.d3b29478'),
dataIndex: 'modifyTimeMillis',
sorter: true,
width: '170px',
ellipsis: true,
customRender: ({ text }) => parseTime(text)
},
{
title: this.$t('pages.system.assets.script-library.efaf9956'),
dataIndex: 'createTimeMillis',
sorter: true,
width: '170px',
ellipsis: true,
customRender: ({ text }) => parseTime(text)
},
{
title: this.$t('pages.system.assets.script-library.339d15b5'),
dataIndex: 'createUser',
ellipsis: true,
tooltip: true,
width: '120px'
},
{
title: this.$t('pages.system.assets.script-library.8605b4f2'),
dataIndex: 'modifyUser',
ellipsis: true,
tooltip: true,
width: '120px'
},
{
title: this.$t('pages.system.assets.script-library.fe731dfc'),
dataIndex: 'operation',
align: 'center',
fixed: 'right',
width: '140px'
}
],
rules: {
// name: [{ required: true, message: this.$tl('p.inputScriptName'), trigger: 'blur' }],
// context: [{ required: true, message: this.$tl('p.inputScriptContent'), trigger: 'blur' }]
},
confirmLoading: false
}
},
computed: {
pagination() {
return COMPUTED_PAGINATION(this.listQuery)
},
activePage() {
return this.$attrs.routerUrl === this.$route.path
}
},
watch: {},
created() {
// this.columns.push(
// );
},
mounted() {
// this.calcTableHeight();
this.loadData()
},
methods: {
//
loadData(pointerEvent) {
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page
this.loading = true
getScriptLibraryList(this.listQuery).then((res) => {
if (res.code === 200) {
this.list = res.data.result
this.listQuery.total = res.data.total
}
this.loading = false
})
},
parseTime,
//
searchMachineList(name) {
machineSearch({
name: name,
limit: 10,
appendIds: this.temp.machineIds || ''
}).then((res) => {
this.nodeList = res.data || []
})
},
createScript() {
this.temp = {}
this.editScriptVisible = true
this.searchMachineList()
},
//
handleEdit(record) {
this.temp = Object.assign({}, record)
//this.commandParams = data?.defArgs ? JSON.parse(data.defArgs) : []
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(() => {
//
this.temp.machineIds = this.temp?.chooseNode?.join(',')
delete this.temp.nodeList
this.confirmLoading = true
editScriptLibrary(this.temp)
.then((res) => {
if (res.code === 200) {
//
$notification.success({
message: res.msg
})
this.editScriptVisible = false
this.loadData()
this.$refs['editScriptForm'].resetFields()
}
})
.finally(() => {
this.confirmLoading = false
})
})
},
handleDelete(record) {
$confirm({
title: this.$t('pages.system.assets.script-library.3875bf60'),
content: this.$t('pages.system.assets.script-library.72df294d'),
zIndex: 1009,
okText: this.$t('pages.system.assets.script-library.d507abff'),
cancelText: this.$t('pages.system.assets.script-library.a0451c97'),
onOk: () => {
return delScriptLibrary({
id: record.id
}).then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
this.loadData()
}
})
}
})
},
//
changePage(pagination, filters, sorter) {
this.listQuery = CHANGE_PAGE(this.listQuery, { pagination, sorter })
this.loadData()
}
}
}
</script>

View File

@ -190,7 +190,7 @@ const management = [
{
path: '/system/assets/script-library',
name: 'system-script-library',
component: () => import('../pages/system/assets/script-library.vue')
component: () => import('../pages/system/assets/script-library/script-library.vue')
},
{
path: '/user/permission-group',

View File

@ -291,7 +291,7 @@ export function formatUnits(value: any, base: number, unitArr: string[], default
*/
declare global {
interface Array<T> {
groupBy(fn: (ix: T) => T): T[]
groupBy(fn: (ix: T) => any): any[]
}
}
if (!Array.prototype.groupBy) {
@ -299,7 +299,7 @@ if (!Array.prototype.groupBy) {
enumerable: false,
writable: false,
configurable: false,
value: function groupBy<T>(this: T[], group: (ix: T) => T): T[] {
value: function groupBy<T>(this: T[], group: (ix: T) => any): any[] {
return this.reduce(function (c: any, v: any) {
const k = group(v)
c[k] = v

View File

@ -16,3 +16,10 @@ export function getHashQuery() {
})
return querys
}
const zIndexStart = 1009
let incCount = 0
export function increaseZIndex() {
return zIndexStart + incCount++
}