!242 i18n: pages dispatch

Merge pull request !242 from a20070322/task/i18n
This commit is contained in:
蒋小小 2024-04-28 10:41:09 +00:00 committed by Gitee
commit bcead8ba41
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
73 changed files with 2029 additions and 554 deletions

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 16.x ]
node-version: [ 18.x ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2

View File

@ -2,10 +2,18 @@
## 2.11.5.0
### 🐣 新增功能
1. 【all】新增 自由脚本方便调试机器节点
### 🐞 解决BUG、优化功能
1. 【server】修复 资产管理 SSH 配置禁用命令无法回显(感谢@zhangw
2. 【server】修复 资产管理 SSH 未配置授权目录时 NPE (感谢[@Anley](https://gitee.com/MrAnley) [Gitee issues I9J17G](https://gitee.com/dromara/Jpom/issues/I9J17G)
2. 【server】修复 资产管理 SSH 未配置授权目录时 NPE (感谢[@Anley](https://gitee.com/MrAnley) [Gitee issues I9J17G](https://gitee.com/dromara/Jpom/issues/I9J17G)
3. 【agent】优化 监控机器网络流程支持配置排除网卡或者仅统计对应的网卡
4. 【server】修复 退出登录时页面会提示需要登录相关信息
5. 【server】优化 页面检测新版本判断是否加入 beta
6. 【agent】优化 添加数据记录修改人(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I9JSY7](https://gitee.com/dromara/Jpom/issues/I9JSY7)
------

View File

@ -40,6 +40,9 @@ public class HttpTransportServer implements TransportServer {
UrlBuilder urlBuilder = UrlBuilder.of(url).addPath(urlItem.path());
HttpRequest httpRequest = HttpRequest.of(urlBuilder);
httpRequest.setMethod(method);
// 添加请求头
Map<String, String> header = urlItem.header();
httpRequest.headerMap(header, true);
Optional.ofNullable(urlItem.timeout()).ifPresent(integer -> httpRequest.timeout(integer * 1000));

View File

@ -8,7 +8,7 @@
# See the Mulan PSL v2 for more details.
#
FROM maven:3.8.5-jdk-8-slim as builder
FROM maven:3.9.6-eclipse-temurin-8 as builder
WORKDIR /target/dependency
COPY . .

View File

@ -37,7 +37,7 @@ import java.util.Optional;
@Configuration
@ConfigurationProperties("jpom")
@Data
@EnableConfigurationProperties({ProjectConfig.class, ProjectLogConfig.class, SystemConfig.class, AgentAuthorize.class})
@EnableConfigurationProperties({ProjectConfig.class, ProjectLogConfig.class, SystemConfig.class, AgentAuthorize.class, MonitorConfig.class, MonitorConfig.NetworkConfig.class})
public class AgentConfig implements ILoadEvent, InitializingBean {
private final JpomApplication jpomApplication;
@ -59,6 +59,11 @@ public class AgentConfig implements ILoadEvent, InitializingBean {
* 系统配置参数
*/
private SystemConfig system;
/**
* 监控配置
*/
private MonitorConfig monitor;
/**
* 数据目录
*/

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 Of Him Code Technology Studio
* Jpom is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.jpom.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author bwcx_jzy
* @since 2024/4/24
*/
@Data
@ConfigurationProperties("jpom.monitor")
public class MonitorConfig {
private NetworkConfig network;
@Data
@ConfigurationProperties("jpom.monitor.network")
public static class NetworkConfig {
/**
* 忽略的网卡,多个使用逗号分隔
*/
private String statExcludeNames;
/**
* 仅统计的网卡
* ,多个使用逗号分隔
*/
private String statContainsOnlyNames;
}
}

View File

@ -23,6 +23,8 @@ import org.dromara.jpom.common.RemoteVersion;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.common.commander.SystemCommander;
import org.dromara.jpom.common.interceptor.NotAuthorize;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.configuration.MonitorConfig;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.data.NodeScriptModel;
import org.dromara.jpom.plugin.PluginFactory;
@ -39,6 +41,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
@ -55,15 +58,18 @@ public class IndexController extends BaseAgentController {
private final NodeScriptServer nodeScriptServer;
private final SystemCommander systemCommander;
private final ProjectCommander projectCommander;
private final AgentConfig agentConfig;
public IndexController(ProjectInfoService projectInfoService,
NodeScriptServer nodeScriptServer,
SystemCommander systemCommander,
ProjectCommander projectCommander) {
ProjectCommander projectCommander,
AgentConfig agentConfig) {
this.projectInfoService = projectInfoService;
this.nodeScriptServer = nodeScriptServer;
this.systemCommander = systemCommander;
this.projectCommander = projectCommander;
this.agentConfig = agentConfig;
}
@RequestMapping(value = {"index", "", "index.html", "/"}, produces = MediaType.TEXT_PLAIN_VALUE)
@ -83,6 +89,7 @@ public class IndexController extends BaseAgentController {
jsonObject.put("remoteVersion", remoteVersion);
jsonObject.put("pluginSize", PluginFactory.size());
jsonObject.put("joinBetaRelease", RemoteVersion.betaRelease());
jsonObject.put("monitor", agentConfig.getMonitor());
return JsonMessage.success("", jsonObject);
}
@ -95,7 +102,9 @@ public class IndexController extends BaseAgentController {
public IJsonMessage<JSONObject> getDirectTop() {
JSONObject jsonObject = new JSONObject();
try {
JSONObject topInfo = org.dromara.jpom.util.OshiUtils.getSimpleInfo();
Optional<MonitorConfig> monitorConfig = Optional.ofNullable(agentConfig).map(AgentConfig::getMonitor);
JSONObject topInfo = org.dromara.jpom.util.OshiUtils.getSimpleInfo(monitorConfig.orElse(null));
jsonObject.put("simpleStatus", topInfo);
// 系统固定休眠时间
jsonObject.put("systemSleep", org.dromara.jpom.util.OshiUtils.NET_STAT_SLEEP + org.dromara.jpom.util.OshiUtils.CPU_STAT_SLEEP);

View File

@ -30,6 +30,7 @@ public abstract class BaseWorkspaceOptService<T extends BaseWorkspaceModel> exte
String userName = BaseAgentController.getNowUserName();
if (!StrUtil.DASHED.equals(userName)) {
t.setCreateUser(userName);
t.setModifyUser(userName);
}
super.addItem(t);
}
@ -52,6 +53,10 @@ public abstract class BaseWorkspaceOptService<T extends BaseWorkspaceModel> exte
@Override
public void updateById(T updateData, String id) {
updateData.setModifyTime(DateUtil.now());
String userName = BaseAgentController.getNowUserName();
if (!StrUtil.DASHED.equals(userName)) {
updateData.setModifyUser(userName);
}
super.updateById(updateData, id);
}
}

View File

@ -90,6 +90,13 @@ public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle {
return;
}
JSONObject json = JSONObject.parseObject(message);
String type = json.getString("type");
if (StrUtil.equals(type, "close")) {
// 关闭停止脚本执行
IoUtil.close(CACHE.remove(session.getId()));
session.close();
return;
}
String path = json.getString("path");
String tag = json.getString("tag");
JSONObject environment = json.getJSONObject("environment");
@ -172,10 +179,12 @@ public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle {
processBuilder.redirectErrorStream(true);
processBuilder.command(command);
//
File directory = FileUtil.file(path).getAbsoluteFile();
// 需要创建目录
FileUtil.mkdir(directory);
processBuilder.directory(directory);
if (StrUtil.isNotEmpty(path)) {
File directory = FileUtil.file(path).getAbsoluteFile();
// 需要创建目录
FileUtil.mkdir(directory);
processBuilder.directory(directory);
}
//
process = processBuilder.start();
inputStream = process.getInputStream();

View File

@ -9,6 +9,7 @@
*/
package org.dromara.jpom.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
@ -16,6 +17,9 @@ import cn.hutool.system.oshi.CpuInfo;
import cn.hutool.system.oshi.OshiUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import org.dromara.jpom.configuration.MonitorConfig;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import oshi.hardware.*;
import oshi.software.os.FileSystem;
import oshi.software.os.NetworkParams;
@ -41,6 +45,8 @@ public class OshiUtils {
public static final int NET_STAT_SLEEP = 1000;
public static final int CPU_STAT_SLEEP = 1000;
private static final PathMatcher pathMatcher = new AntPathMatcher();
static {
// 解决Oshi获取CPU使用率与Windows任务管理器显示不匹配的问题
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true);
@ -97,7 +103,7 @@ public class OshiUtils {
*
* @return json
*/
public static JSONObject getSimpleInfo() {
public static JSONObject getSimpleInfo(MonitorConfig monitorConfig) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", SystemClock.now());
CpuInfo cpuInfo = OshiUtil.getCpuInfo(CPU_STAT_SLEEP);
@ -129,33 +135,55 @@ public class OshiUtils {
}
jsonObject.put("disk", NumberUtil.div(used, total, 2) * 100);
//
NetIoInfo startNetInfo = getNetInfo();
MonitorConfig.NetworkConfig networkConfig1 = Optional.ofNullable(monitorConfig)
.map(MonitorConfig::getNetwork).orElse(null);
NetIoInfo startNetInfo = getNetInfo(networkConfig1);
//暂停1秒
Util.sleep(NET_STAT_SLEEP);
NetIoInfo endNetInfo = getNetInfo();
NetIoInfo endNetInfo = getNetInfo(networkConfig1);
jsonObject.put("netTxBytes", endNetInfo.getTxbyt() - startNetInfo.getTxbyt());
jsonObject.put("netRxBytes", endNetInfo.getRxbyt() - startNetInfo.getRxbyt());
jsonObject.put("monitorIfsNames", endNetInfo.getIfsNames());
return jsonObject;
}
private static NetIoInfo getNetInfo() {
private static NetIoInfo getNetInfo(MonitorConfig.NetworkConfig networkConfig) {
//
List<String> statExcludeNames = Optional.ofNullable(networkConfig)
.map(MonitorConfig.NetworkConfig::getStatExcludeNames)
.map(s -> StrUtil.splitTrim(s, StrUtil.COMMA))
.orElse(CollUtil.newArrayList());
List<String> statContainsOnlyNames = Optional.ofNullable(networkConfig)
.map(MonitorConfig.NetworkConfig::getStatContainsOnlyNames)
.map(s -> StrUtil.splitTrim(s, StrUtil.COMMA))
.orElse(CollUtil.newArrayList());
long rxBytesBegin = 0;
long txBytesBegin = 0;
long rxPacketsBegin = 0;
long txPacketsBegin = 0;
List<NetworkIF> listBegin = OshiUtil.getNetworkIFs();
for (NetworkIF net : listBegin) {
rxBytesBegin += net.getBytesRecv();
txBytesBegin += net.getBytesSent();
rxPacketsBegin += net.getPacketsRecv();
txPacketsBegin += net.getPacketsSent();
StringBuilder ifsNames = new StringBuilder(StrUtil.EMPTY);
if (listBegin != null) {
listBegin = listBegin.stream()
.filter(networkIF -> CollUtil.isEmpty(statExcludeNames) || !isMatch(statExcludeNames, networkIF.getName()))
.filter(networkIF -> CollUtil.isEmpty(statContainsOnlyNames) || isMatch(statContainsOnlyNames, networkIF.getName()))
.collect(Collectors.toList());
for (int i = 0; i < listBegin.size(); i++) {
NetworkIF net = listBegin.get(i);
rxBytesBegin += net.getBytesRecv();
txBytesBegin += net.getBytesSent();
rxPacketsBegin += net.getPacketsRecv();
txPacketsBegin += net.getPacketsSent();
ifsNames.append(i == 0 ? net.getName() : "," + net.getName());
}
}
NetIoInfo netIoInfo = new NetIoInfo();
netIoInfo.setRxbyt(rxBytesBegin);
netIoInfo.setTxbyt(txBytesBegin);
netIoInfo.setRxpck(rxPacketsBegin);
netIoInfo.setTxpck(txPacketsBegin);
netIoInfo.setIfsNames(ifsNames.toString());
return netIoInfo;
}
@ -307,6 +335,15 @@ public class OshiUtils {
.orElse(new ArrayList<>());
}
public static boolean isMatch(List<String> list, String keyword) {
for (String pattern : list) {
if (pathMatcher.match(pattern, keyword)) {
return true;
}
}
return false;
}
@Data
private static class NetIoInfo {
/**
@ -328,5 +365,9 @@ public class OshiUtils {
* 发送的KB数,txbit/s
*/
private Long txbyt;
/**
* ifaceNames
*/
private String ifsNames;
}
}

View File

@ -53,6 +53,12 @@ jpom:
allowed-downgrade: false
# 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密
command-use-sudo: false
monitor:
network:
# 监控网络流量只统计对应的网卡,多个使用逗号分隔. 支持模糊匹配
stat-contains-only-names: en*
# 监控网络流量排除对应的网卡,多个使用逗号分隔. 支持模糊匹配
stat-exclude-names: lo*
server:
#运行端口号
port: 2123

View File

@ -51,6 +51,12 @@ jpom:
allowed-downgrade: false
# 执行系统主要命名是否填充 sudo(sudo xxx) 使用前提需要配置 sudo 免密
command-use-sudo: false
monitor:
network:
# 监控网络流量只统计对应的网卡,多个使用逗号分隔
stat-contains-only-names:
# 监控网络流量排除对应的网卡,多个使用逗号分隔
stat-exclude-names:
server:
#运行端口号
port: 2123

View File

@ -0,0 +1,33 @@
package org.dromara.jpom;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.system.oshi.OshiUtil;
import org.dromara.jpom.util.OshiUtils;
import org.junit.Test;
import oshi.hardware.NetworkIF;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author bwcx_jzy1
* @since 2024/4/25
*/
public class OshiNetworkTest {
@Test
public void test() {
List<NetworkIF> listBegin = OshiUtil.getNetworkIFs();
System.out.println(listBegin.size());
List<String> statExcludeNames = CollUtil.newArrayList("lo*", "ap*");
List<String> statContainsOnlyNames = CollUtil.newArrayList("en*");
listBegin = listBegin.stream()
.filter(networkIF -> CollUtil.isEmpty(statExcludeNames) || !OshiUtils.isMatch(statExcludeNames, networkIF.getName()))
.filter(networkIF -> CollUtil.isEmpty(statContainsOnlyNames) || OshiUtils.isMatch(statContainsOnlyNames, networkIF.getName()))
.collect(Collectors.toList());
for (NetworkIF anIf : listBegin) {
System.out.println(anIf.getName());
}
// System.out.println(listBegin);
}
}

View File

@ -33,6 +33,10 @@ public class ApacheExecUtil {
private static final ShutdownHookProcessDestroyer shutdownHookProcessDestroyer = new ShutdownHookProcessDestroyer();
private static final Map<String, Process> processMap = new SafeConcurrentHashMap<>();
public static void addProcess(Process process) {
shutdownHookProcessDestroyer.add(process);
}
/**
* 关闭 Process
*
@ -87,21 +91,25 @@ public class ApacheExecUtil {
.get();
//
executor.setProcessDestroyer(new ProcessDestroyer() {
private int size = 0;
@Override
public boolean add(Process process) {
processMap.put(execId, process);
size++;
return shutdownHookProcessDestroyer.add(process);
}
@Override
public boolean remove(Process process) {
processMap.remove(execId);
size--;
return shutdownHookProcessDestroyer.remove(process);
}
@Override
public int size() {
return shutdownHookProcessDestroyer.size();
return size;
}
});
pumpStreamHandler.stop();

View File

@ -15,6 +15,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.util.*;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
@ -45,6 +46,9 @@ public class CommandUtil {
* 文件后缀
*/
public static final String SUFFIX;
public static final String SUFFIX_UNIX = "sh";
public static final String SUFFIX_WINDOWS = "bat";
/**
* 执行前缀
*/
@ -59,23 +63,23 @@ public class CommandUtil {
private static final ThreadLocal<Map<String, String>> CACHE_COMMAND_RESULT = new ThreadLocal<>();
static {
if (SystemUtil.getOsInfo().isLinux()) {
OsInfo osInfo = SystemUtil.getOsInfo();
if (osInfo.isLinux() || osInfo.isMac() || osInfo.isMacOsX() || osInfo.isIrix() || osInfo.isHpUx()) {
//执行linux系统命令
COMMAND.add("/bin/bash");
COMMAND.add("-c");
} else if (SystemUtil.getOsInfo().isMac()) {
COMMAND.add("/bin/bash");
COMMAND.add("-c");
} else {
} else if (osInfo.isWindows()) {
COMMAND.add("cmd");
COMMAND.add("/c");
} else {
log.error("不支持的系统类型:{}", osInfo.getName());
}
//
if (SystemUtil.getOsInfo().isWindows()) {
SUFFIX = "bat";
if (osInfo.isWindows()) {
SUFFIX = SUFFIX_WINDOWS;
EXECUTE_PREFIX = StrUtil.EMPTY;
} else {
SUFFIX = "sh";
SUFFIX = SUFFIX_UNIX;
EXECUTE_PREFIX = "bash";
}
}

View File

@ -10,7 +10,7 @@
# syntax = docker/dockerfile:experimental
FROM maven:3.8.5-jdk-8-slim as builder
FROM maven:3.9.6-eclipse-temurin-8 as builder
WORKDIR /target/dependency
COPY . .
@ -46,7 +46,7 @@ RUN --mount=type=cache,target=/target/dependency/web-vue/node_modules cd web-vue
RUN --mount=type=cache,target=/root/.m2 mvn -B -e -T 1C clean package -pl modules/server -am -Dmaven.test.skip=true -Dmaven.compile.fork=true -s script/settings.xml
FROM maven:3.8.5-jdk-8
FROM maven:3.9.6-eclipse-temurin-8
ARG BUILD_DATE
ARG JPOM_VERSION

View File

@ -8,7 +8,7 @@
# See the Mulan PSL v2 for more details.
#
FROM maven:3.8.6-jdk-8
FROM maven:3.9.6-eclipse-temurin-8
ARG BUILD_DATE
LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}"

View File

@ -8,7 +8,7 @@
# See the Mulan PSL v2 for more details.
#
FROM maven:3.8.5-openjdk-17-slim
FROM maven:3.9.6-sapmachine-17
ARG BUILD_DATE
LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}"

View File

@ -8,7 +8,7 @@
# See the Mulan PSL v2 for more details.
#
FROM maven:3.8.6-jdk-8
FROM maven:3.9.6-eclipse-temurin-8
ARG BUILD_DATE
LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}"

View File

@ -8,7 +8,7 @@
# See the Mulan PSL v2 for more details.
#
FROM maven:3.8.6-jdk-8
FROM maven:3.9.6-sapmachine-17
ARG BUILD_DATE
LABEL build_info="dromara/Jpom build-date:- ${BUILD_DATE}"

View File

@ -207,6 +207,8 @@ public class MachineDockerController extends BaseGroupNameController {
throw new IllegalArgumentException("仓库账号或者密码错误:" + e.getMessage());
}
}
// 修改状态为在线
dockerInfoModel.setStatus(1);
}
@GetMapping(value = "try-local-docker", produces = MediaType.APPLICATION_JSON_VALUE)
@ -219,7 +221,7 @@ public class MachineDockerController extends BaseGroupNameController {
entity.set("host", dockerHost);
boolean exists = machineDockerServer.exists(entity);
if (exists) {
return new JsonMessage<>(405, "已经存在本地 docker 信息啦,不要重复添加");
return new JsonMessage<>(405, "已经存在本地 docker 信息啦,不要重复添加" + dockerHost);
}
MachineDockerModel dockerModel = new MachineDockerModel();
dockerModel.setHost(dockerHost);

View File

@ -254,4 +254,16 @@ public class MachineNodeController extends BaseGroupNameController {
}
return JsonMessage.success("修正成功");
}
@GetMapping(value = "monitor-config", produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.LIST)
public IJsonMessage<JSONObject> info(HttpServletRequest request, String id) {
IJsonMessage<JSONObject> message = this.tryRequestMachine(id, request, NodeUrl.Info);
Assert.notNull(message, "没有对应的资产机器");
Assert.state(message.success(), message.getMsg());
//
JSONObject data = message.getData();
JSONObject monitor = Optional.ofNullable(data).map(jsonObject -> jsonObject.getJSONObject("monitor")).orElse(null);
return JsonMessage.success("", monitor);
}
}

View File

@ -194,7 +194,10 @@ public class MachineNodeModel extends BaseGroupNameModel implements INodeInfo {
* 安装 id
*/
private String installId;
/**
* 扩展信息
*/
private String extendInfo;
/**
* 传输加密方式 0 不加密 1 BASE64 2 AES
*/

View File

@ -15,7 +15,6 @@ import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -300,6 +299,8 @@ public class MachineNodeServer extends BaseDbService<MachineNodeModel> implement
MachineNodeStatLogModel machineNodeStatLogModel = new MachineNodeStatLogModel();
machineNodeStatLogModel.setMachineId(machineNodeModel.getId());
machineNodeStatLogModel.setNetworkDelay(networkDelay);
//
JSONObject extendInfo = new JSONObject();
Optional.ofNullable(data.getJSONObject("simpleStatus")).ifPresent(jsonObject -> {
machineNodeModel.setOsOccupyMemory(ObjectUtil.defaultIfNull(jsonObject.getDouble("memory"), -1D));
machineNodeModel.setOsOccupyDisk(ObjectUtil.defaultIfNull(jsonObject.getDouble("disk"), -1D));
@ -313,6 +314,8 @@ public class MachineNodeServer extends BaseDbService<MachineNodeModel> implement
machineNodeStatLogModel.setNetTxBytes(jsonObject.getLong("netTxBytes"));
machineNodeStatLogModel.setNetRxBytes(jsonObject.getLong("netRxBytes"));
machineNodeStatLogModel.setMonitorTime(jsonObject.getLongValue("time"));
//
extendInfo.put("monitorIfsNames", jsonObject.getString("monitorIfsNames"));
});
// 系统信息
Optional.ofNullable(data.getJSONObject("systemInfo")).ifPresent(jsonObject -> {
@ -336,6 +339,7 @@ public class MachineNodeServer extends BaseDbService<MachineNodeModel> implement
machineNodeModel.setOsLoadAverage(CollUtil.join(osLoadAverage, StrUtil.COMMA));
machineNodeModel.setOsFileStoreTotal(jsonObject.getLong("osFileStoreTotal"));
});
machineNodeModel.setExtendInfo(extendInfo.toString());
this.updateById(machineNodeModel);
if (machineNodeStatLogModel.getMonitorTime() != null) {
machineNodeStatLogServer.insert(machineNodeStatLogModel);

View File

@ -117,6 +117,7 @@ public enum ClassFeature {
NODE_SCRIPT("节点脚本模板", ClassFeature.NODE, NodeScriptServer.class),
NODE_SCRIPT_LOG("节点脚本模板日志", ClassFeature.NODE, NodeScriptExecuteLogServer.class),
AGENT_LOG("插件端系统日志", ClassFeature.NODE),
FREE_SCRIPT("自由脚本", ClassFeature.NODE, MachineNodeServer.class),
// TOMCAT_FILE("Tomcat file", ClassFeature.NODE),
// TOMCAT_LOG("Tomcat log", ClassFeature.NODE),

View File

@ -92,7 +92,7 @@ public abstract class BaseProxyHandler extends BaseHandler {
IUrlItem urlItem = NodeForward.parseUrlItem(nodeInfo, workspaceId, this.nodeUrl, DataContentType.FORM_URLENCODED);
IProxyWebSocket proxySession = TransportServerFactory.get().websocket(nodeInfo, urlItem, parameters);
proxySession.onMessage(s -> sendMsg(session, s));
proxySession.onMessage(s -> onProxyMessage(session, s));
if (!proxySession.connectBlocking()) {
this.sendMsg(session, "插件端连接失败");
this.destroy(session);
@ -104,6 +104,10 @@ public abstract class BaseProxyHandler extends BaseHandler {
attributes.put("init", true);
}
protected void onProxyMessage(WebSocketSession session, String msg) {
sendMsg(session, msg);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
@ -115,7 +119,7 @@ public abstract class BaseProxyHandler extends BaseHandler {
ConsoleCommandOp consoleCommandOp = StrUtil.isNotEmpty(op) ? ConsoleCommandOp.valueOf(op) : null;
String textMessage;
if (proxySession != null) {
textMessage = this.handleTextMessage(attributes, proxySession, json, consoleCommandOp);
textMessage = this.handleTextMessage(attributes, session, proxySession, json, consoleCommandOp);
} else {
textMessage = this.handleTextMessage(attributes, session, json, consoleCommandOp);
}
@ -139,6 +143,22 @@ public abstract class BaseProxyHandler extends BaseHandler {
return null;
}
/**
* 消息处理方法
*
* @param attributes 属性
* @param proxySession 代理回话
* @param json 数据
* @param consoleCommandOp 操作类型
*/
protected String handleTextMessage(Map<String, Object> attributes,
WebSocketSession session,
IProxyWebSocket proxySession,
JSONObject json,
ConsoleCommandOp consoleCommandOp) throws IOException {
return this.handleTextMessage(attributes, proxySession, json, consoleCommandOp);
}
/**
* 消息处理方法
*

View File

@ -63,6 +63,7 @@ public enum HandlerType {
* 容器 终端
*/
docker(DockerCliHandler.class, DockerInfoService.class, MachineDockerServer.class, "machineDockerId"),
freeScript(FreeScriptHandler.class, null),
;
final Class<?> handlerClass;

View File

@ -71,5 +71,8 @@ public class ServerWebSocketConfig implements WebSocketConfigurer {
// docker cli
registry.addHandler(new DockerCliHandler(), "/socket/docker_cli")
.addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*");
// free script
registry.addHandler(new FreeScriptHandler(), "/socket/free_script")
.addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*");
}
}

View File

@ -178,6 +178,13 @@ public class ServerWebSocketInterceptor implements HandshakeInterceptor {
break;
case nodeUpdate:
break;
case freeScript:
//
MachineNodeModel machine = (MachineNodeModel) attributes.get("machine");
if (machine == null) {
return false;
}
break;
default:
return false;
}

View File

@ -0,0 +1,90 @@
package org.dromara.jpom.socket.handler;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.forward.NodeUrl;
import org.dromara.jpom.func.assets.model.MachineNodeModel;
import org.dromara.jpom.permission.ClassFeature;
import org.dromara.jpom.permission.Feature;
import org.dromara.jpom.permission.MethodFeature;
import org.dromara.jpom.socket.BaseProxyHandler;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.transport.IProxyWebSocket;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.SocketSessionUtil;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* @author bwcx_jzy1
* @since 2024/4/26
*/
@Feature(cls = ClassFeature.FREE_SCRIPT, method = MethodFeature.EXECUTE)
@Slf4j
public class FreeScriptHandler extends BaseProxyHandler {
public FreeScriptHandler() {
super(NodeUrl.FreeScriptRun);
}
@Override
protected Object[] getParameters(Map<String, Object> attributes) {
return new Object[]{};
}
@Override
protected String handleTextMessage(Map<String, Object> attributes, WebSocketSession session, IProxyWebSocket proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) throws IOException {
String content = json.getString("content");
if (StrUtil.isEmpty(content)) {
SocketSessionUtil.send(session, "没有需要执行的内容");
session.close();
return null;
}
MachineNodeModel machine = (MachineNodeModel) attributes.get("machine");
String osName = machine.getOsName();
String template = StrUtil.EMPTY;
boolean appendTemplate = json.getBooleanValue("appendTemplate");
if (appendTemplate && StrUtil.isNotEmpty(osName)) {
InputStream templateInputStream;
if (osName.startsWith("Windows")) {
templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX_WINDOWS);
} else {
templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX_UNIX);
}
template = IoUtil.readUtf8(templateInputStream);
}
String uuid = IdUtil.fastSimpleUUID();
json.put("tag", uuid);
json.put("content", template + content);
String path = json.getString("path");
json.put("path", StrUtil.emptyToDefault(path, "./"));
json.put("environment", new JSONObject());
attributes.put("uuidTag", uuid);
proxySession.send(json.toString());
return null;
}
@Override
protected void onProxyMessage(WebSocketSession session, String msg) {
if (StrUtil.equals(msg, "JPOM_SYSTEM_TAG:" + session.getAttributes().get("uuidTag"))) {
// 执行结束
try {
session.close();
} catch (IOException e) {
log.error("关闭客户端回话异常", e);
}
return;
}
super.onProxyMessage(session, msg);
}
}

View File

@ -87,7 +87,6 @@ public class InitDb implements DisposableBean, ILoadEvent {
AFTER_CALLBACK.put(name, supplier);
}
@SuppressWarnings("rawtypes")
public void afterPropertiesSet(ApplicationContext applicationContext) {
this.prepareCallback(applicationContext.getEnvironment());
//

View File

@ -15,3 +15,4 @@ ALTER,STATIC_FILE_STORAGE,absolutePath,String,300,,文件路径,false
ALTER,STATIC_FILE_STORAGE,name,String,100,,文件名,false
ADD,TRIGGER_TOKEN_LOG,triggerCount,Integer,,,触发次数
ADD,USER_INFO,source,String,100,,账号来源
ADD,MACHINE_NODE_INFO,extendInfo,TEXT,,,扩展信息

1 alterType,tableName,name,type,len,defaultValue,comment,notNull
15 ALTER,STATIC_FILE_STORAGE,name,String,100,,文件名,false
16 ADD,TRIGGER_TOKEN_LOG,triggerCount,Integer,,,触发次数
17 ADD,USER_INFO,source,String,100,,账号来源
18 ADD,MACHINE_NODE_INFO,extendInfo,TEXT,,,扩展信息

View File

@ -18,7 +18,11 @@
# docker buildx create --use
# 服务端
docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:2.11.5.1 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .
docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/arm64/v8 -t jpomdocker/jpom:2.11.5.1 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .
docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/arm64/v8 -t jpomdocker/jpom:2-test -f ./modules/server/DockerfileBeta --push .
#
#docker buildx build --platform linux/amd64,linux/arm64 -t jpomdocker/jpom:latest -f ./modules/server/DockerfileRelease --push .

View File

@ -7,4 +7,3 @@
/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
/// See the Mulan PSL v2 for more details.
///

View File

@ -116,3 +116,11 @@ export function machineCorrectLonelyData(data) {
data: data
})
}
export function machineMonitorConfig(data) {
return axios({
url: '/system/assets/machine/monitor-config',
method: 'get',
params: data
})
}

View File

@ -1,5 +1,11 @@
<template>
<div :style="`margin-top:${marginTop}`">
<div
:style="{
marginTop: marginTop,
minHeight: height,
height: height
}"
>
<div class="log-filter">
<a-row type="flex" align="middle">
<a-col>
@ -27,7 +33,7 @@
</a-row>
</div>
<!-- <pre class="log-view" :id="`${this.id}`" :style="`height:${this.height}`">{{ defText }}</pre> -->
<viewPre ref="viewPre" :height="height" :config="temp"></viewPre>
<viewPre ref="viewPre" :height="`calc(${height} - 35px - 20px)`" :config="temp"></viewPre>
</div>
</template>

View File

@ -219,7 +219,7 @@ export default {
// res.data.
this.showVersion(false, res.data?.remoteVersion).then((upgrade) => {
//
this.loaclCheckVersion(!upgrade)
this.localCheckVersion(!upgrade)
})
})
})
@ -412,19 +412,23 @@ export default {
this.showVersion(true, res.data).then((upgrade) => {
//
if (!upgrade) {
this.loaclCheckVersion(true)
this.localCheckVersion(true)
}
})
}
})
},
//
loaclCheckVersion(tip) {
localCheckVersion(tip) {
//console.log(compareVersion("1.0.0", "1.0.1"), compareVersion("2.4.3", "2.4.2"));
//console.log(compareVersion("1.0.2", "dev"));
const buildInfo = pageBuildInfo()
executionRequest('https://jpom.top/docs/release-versions.json', {
const url = this.temp?.joinBetaRelease
? 'https://jpom.top/docs/beta-versions.json'
: 'https://jpom.top/docs/release-versions.json'
executionRequest(url, {
...buildInfo,
type: this.nodeId || this.machineId ? 'agent' : 'server'
}).then((data) => {

View File

@ -1,13 +1,3 @@
///
/// Copyright (c) 2019 Of Him Code Technology Studio
/// Jpom is licensed under Mulan PSL v2.
/// You can use this software according to the terms and conditions of the Mulan PSL v2.
/// You may obtain a copy of Mulan PSL v2 at:
/// http://license.coscl.org.cn/MulanPSL2
/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
/// See the Mulan PSL v2 for more details.
///
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck

View File

@ -1,13 +1,3 @@
///
/// Copyright (c) 2019 Of Him Code Technology Studio
/// Jpom is licensed under Mulan PSL v2.
/// You can use this software according to the terms and conditions of the Mulan PSL v2.
/// You may obtain a copy of Mulan PSL v2 at:
/// http://license.coscl.org.cn/MulanPSL2
/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
/// See the Mulan PSL v2 for more details.
///
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck

View File

@ -1,13 +1,3 @@
///
/// Copyright (c) 2019 Of Him Code Technology Studio
/// Jpom is licensed under Mulan PSL v2.
/// You can use this software according to the terms and conditions of the Mulan PSL v2.
/// You may obtain a copy of Mulan PSL v2 at:
/// http://license.coscl.org.cn/MulanPSL2
/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
/// See the Mulan PSL v2 for more details.
///
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck

View File

@ -11,10 +11,12 @@
import page404 from './pages/404/404'
import build from './pages/build'
import certificate from './pages/certificate'
import dispatch from './pages/dispatch'
export default {
pages: {
404: page404,
build,
certificate
certificate,
dispatch
}
}

View File

@ -0,0 +1,17 @@
import log from './log'
import logRead from './logRead'
import start from './start'
import status from './status'
import whiteList from './white-list'
import list from './list'
import logReadView from './logReadView'
export default {
logReadView,
log,
logRead,
start,
status,
whiteList,
list
}

View File

@ -0,0 +1,189 @@
export default {
c: {
distributionType: 'Distribution Type',
independent: 'Independent',
related: 'Related',
unknown: 'Unknown',
yes: 'Yes',
no: 'No',
edit: 'Edit',
distributionID: 'Distribution ID',
distributionIDEqualsProjectID: 'Distribution ID equals Project ID',
cannotModifyAfterCreation: 'Cannot modify after creation',
randomlyGenerated: 'Randomly generated',
distributionName: 'Distribution Name',
groupingName: 'Grouping Name:',
addNewGrouping: 'Add New Grouping',
selectGrouping: 'Select Grouping',
distributionNode: 'Distribution Node',
noProjectInThisNode: 'No project in this node',
independentDistribution: '[Independent Distribution]',
distributionOperationAfter: 'Operation after distribution',
selectOperationAfterPublish: 'Select operation after publish',
intervalTime: 'Interval Time',
ensureProjectRestart:
'Ensure project restart when using sequential restart, full sequential restart during multi-node distribution',
waitPreviousProjectStart: 'Wait for the previous project to start before closing the next project',
configureBasedOnProjectStartTime: 'Please configure based on your project start time',
suggestedIntervalTime: 'It is generally recommended to be more than 10 seconds',
distributionIntervalTimeEffect:
'Distribution interval time (sequential restart, full sequential restart) is effective',
secondaryDirectory: 'Secondary Directory',
publishToRootIfNotFilled: 'Publish to the root of the project if not filled',
clearPublish: 'Clear Publish',
clearPublishDescription:
'Clear publish means that before uploading new files, all files in the project folder directory will be deleted first, and then the new files will be saved',
publishStopBefore:
'Stop before publishing means that when publishing files to the project file, the project will be closed first, and then the file replacement will be carried out. This avoids the situation where files are occupied in the Windows environment',
publishStopBeforeColon: 'Stop before publishing:',
distributionRequestAddress:
'Distribution process request corresponding address, start distribution, distribution completed, distribution failed, cancel distribution',
parametersForDistribution: 'Parameters entered: outGivingId, outGivingName, status, statusMsg, executeTime',
statusValues: 'Values: 1',
distributionStatuses: 'Distribution in progress, 2: Distribution ended, 3: Canceled, 4: Distribution failed',
asynchronousRequest: 'Asynchronous requests cannot guarantee order',
distributionProcessRequest: 'Distribution process request, optional, GET request',
immutableAfterCreation: 'Immutable after creation, Distribution ID equals Project ID',
runProject: 'Run Project',
selectProjectAuthorizationPath: 'Select Project Authorization Path',
configuration: 'Configuration',
configurationExample: 'Configuration Example',
defaultLogDirectory: 'Default is in the plugin data directory /${projectId}/${projectId}.log',
selectProject: 'Select Project',
systemPrompt: 'System Prompt',
confirm: 'Confirm',
cancel: 'Cancel'
},
p: {
distributionID: 'Distribution ID',
name: 'Name',
selectGrouping: 'Select Grouping',
selectStatus: 'Select Status',
quickBackToFirstPage: 'Hold Ctr or Alt/Option and click the button to quickly return to the first page',
search: 'Search',
addRelated: 'Add Related',
addDistribution: 'Add Distribution',
nodeDistributionDescription:
'Node distribution refers to a project that needs to run on multiple nodes (servers). Use node distribution to manage this project uniformly (distributed project management functionality can be achieved)',
addRelatedProjectDescription:
'Adding a related project means associating a project that has already been created in the node as a node distribution project for unified management',
createDistributionProjectDescription:
'Creating a distribution project means creating a new project that belongs to the node distribution. After creation, the project information will be automatically synchronized to the corresponding node. Changes to the node distribution information will also be automatically synchronized to the corresponding node',
distributionFiles: 'Distribution Files',
more: 'More',
cancelDistribution: 'Cancel Distribution',
delete: 'Delete',
release: 'Release',
deleteCompletely: 'Delete Completely',
unbind: 'Unbind',
editRelatedProject: 'Edit Related Project',
addRelatedProject: 'Add Related Project',
noData: 'No data, please add node project data first',
node: 'Node:',
selectNode: 'Select Node',
noNodeInfo: 'No node information',
project: 'Project:',
add: 'Add',
editDistributionProject: 'Edit Distribution Project',
createDistributionProject: 'Create Distribution Project',
loadingData: 'Loading Data',
remind: 'Remind',
noLogicalNode: 'There is no logical node in the current workspace, and node distribution cannot be created',
distributionName: 'Distribution Name (Project Name)',
runningMode: 'Running Mode',
scriptTemplate: 'Custom project management with script template',
staticFolder: 'Project is a static folder',
noProjectStatus: 'No project status and control functions',
selectRunningMode: 'Select Running Mode',
notRecommended: 'Not Recommended',
projectPath: 'Project Path',
authorizationPath: 'Authorization path refers to the folder where the project files are stored in the service',
modifyAuthorizationConfig: 'You can modify it in [Node Distribution] => [Distribution Authorization Configuration]',
projectFolder: 'Project Folder is the actual directory name where the project is stored',
projectFilesStored: 'Project files will be stored in',
authorizationPathProjectFolder: 'Authorization Path + Project Folder',
jarFolder: 'The folder where the project is stored, the folder where the jar package is stored',
configureAuthorizationDirectory: 'You need to configure the authorization directory in advance for the workspace',
configureDirectory: 'Configure Directory',
projectFullDirectory: 'Project Full Directory',
content: 'Content',
yamlConfig:
'Configure in yaml/yml format, scriptId is the relative path of the script file under the project path or the server-side script template ID, which can be viewed in the server-side script template editing dialog box',
variablesInScript: 'Variables supported in the script include: ${PROJECT_ID}, ${PROJECT_NAME}, ${PROJECT_PATH}',
scriptOutput: 'After the process is executed, the script must output: running',
processID: 'The actual process ID of the current project',
incorrectOutputFormat:
'If the last line of the output is not the expected format, the project status will be unrun',
referToConfigExample: 'For configuration details, please refer to the configuration example',
recommendedScriptDistribution: 'It is recommended to use the server-side script distribution to the script:',
viewServerScript: 'View Server Script',
fillProjectDSLConfig:
'Please fill in the project DSL configuration content, you can click the tab above to view the configuration example',
logDirectory: 'Log Directory',
logDirectorySelection: 'Log Directory refers to the console log storage directory',
sameConfigAsAuthorizationDirectory:
'The list of options is consistent with the project authorization directory, that is, the same configuration',
selectLogDirectory: 'Select Log Directory',
mainClass: 'The main class for program execution (can be left blank for jar mode)',
jvmArgs: 'Fill in [xxx',
jvmArgsExample: '-Dext.dirs=xxx: -cp xx : xx]',
selectDistributionNode: 'Select Distribution Node',
jvmParams: 'JVM Parameters',
params: 'Parameters',
nonMandatory: 'Not Mandatory',
jvmParamsExample: 'jvm, e.g.: -Xms512m -Xmx512m',
argsParams: 'Args Parameters',
functionArgsParams: 'Function args parameters, not mandatory',
argsParamsExample: 'e.g.: --server',
autoStart: 'Auto Start',
checkProjectStatus:
'Check project status when the plugin starts, if the project status is unrun, try to start the project',
nonServerAutoStart: 'Non-server auto-start, if you need auto-start at boot, it is recommended to configure',
pluginAutoStart: 'Plugin side auto-start at boot',
turnOnAutoStart: 'And turn on this switch',
on: 'On',
off: 'Off',
pluginAutoCheckProjectOnStart:
'Automatically check the project when the plugin starts, if not started, it will attempt to start',
dslEnvironmentVariables: 'DSL environment variables',
environmentVariables: 'Environment variables',
exampleVariable: 'For example: key1',
projectRequestOnStartStopRestart:
'When the project starts, stops, or restarts, it will request the corresponding address',
parametersForProjectRequest: 'The input parameters are: projectId, projectName, type, result',
valuesForProjectRequest: 'The values are: stop, beforeStop, start, beforeRestart',
projectRequestOnFileChange:
'When the project starts, stops, restarts, or a file changes, it will request the corresponding address, optional, GET request',
configureAuthorizationDirectory1: 'Configure the authorization directory',
viewScript: 'View script',
projectGrouping: 'Project grouping',
distributionStatus: 'After distribution',
status: 'Status',
creationTime: 'Creation time',
modificationTime: 'Modification time',
modifiedBy: 'Modified by',
operations: 'Operations',
enterProjectID: 'Please enter the project ID',
enterProjectName: 'Please enter the project name',
selectProjectRunningMode: 'Please select the project running mode',
enterProjectFolder: 'Please enter the project folder',
selectAtLeastOneNodeProject: 'Select at least 1 node project',
selectAtLeastOneNode: 'Please select at least 1 node',
confirmDeletionOfDistributionInfo:
'Are you sure you want to permanently delete the distribution information? After deletion, the projects under the nodes will also be permanently deleted, and the project will automatically delete the related files (including project logs, log backups, project files)',
confirmDeletionOfDistributionInfoSimple:
'Are you sure you want to delete the distribution information? After deletion, the projects under the nodes will also be deleted',
confirmReleaseOfDistributionInfo:
'Are you sure you want to release the distribution information? After releasing, the project information under the nodes will still be retained, and if you need to delete the project, you will still need to operate in node management',
confirmUnbindingCurrentNodeDistribution: 'Are you sure you want to unbind the current node distribution?',
unbindCheckDataAssociation:
'Unbinding will check data association and will not actually request the node to delete project information',
unbindForUnreachableServer:
'Generally used when the server cannot be connected and it has been determined not to be used anymore',
cautionDueToMistakeOperation: 'Mistakes in operation can result in redundant data!!!',
dangerousOperation: 'Dangerous operation!!!',
noMoreNodeProjects: 'No more node projects available, please create a project first',
selectNodeFirst: 'Please select a node first',
confirmCancellationOfCurrentDistribution: 'Are you sure you want to cancel the current distribution?'
}
}

View File

@ -0,0 +1,29 @@
export default {
c: {
unknown: 'Unknown',
result: 'Distribution result'
},
p: {
noLogs: 'No distribution logs available',
selectNode: 'Please select a node',
distributeProject: 'Distribute project',
selectStatus: 'Please select a status',
goToFirstPage: 'Hold Ctrl or Alt/Option key and click the button to quickly go to the first page',
search: 'Search',
relatedData: 'Related data:',
details: 'Details',
info: 'Detailed information',
projectId: 'Distribution project ID',
nodeName: 'Node name',
distributeId: 'Project ID',
method: 'Distribution method',
duration: 'Distribution duration',
fileSize: 'File size',
startTime: 'Start time',
endTime: 'End time',
statusMessage: 'Distribution status message',
operator: 'Operator',
status: 'Status',
action: 'Action'
}
}

View File

@ -0,0 +1,32 @@
export default {
c: {
logName: 'Log Name',
action: 'Add'
},
p: {
clickButtonToGoBackToFirstPage:
'Click the button while holding Ctrl or Alt/Option to quickly go back to the first page',
search: 'Search',
edit: 'Edit',
view: 'View',
delete: 'Delete',
editLogSearch: 'Edit Log Search',
logItemName: 'Log Item Name',
bindNode: 'Bind Node',
node: 'Node:',
pleaseSelectNode: 'Please select a node',
project: 'Project:',
pleaseSelectProject: 'Please select a project',
searchAndView: 'Search and View',
name: 'Name',
modifier: 'Modifier',
modificationTime: 'Modification Time',
operation: 'Operation',
pleaseFillInLogItemName: 'Please enter a log item name',
atLeastSelectOneNodeAndProject: 'Please select at least one node and project',
systemPrompt: 'System Prompt',
reallyDeleteLogSearch: 'Are you sure you want to delete the log search?',
confirm: 'Confirm',
cancel: 'Cancel'
}
}

View File

@ -0,0 +1,46 @@
export default {
c: {},
p: {
node: 'Node',
chooseNode: 'Please select a node',
searchKeyword: 'Search keyword',
keywordHighlight:
'Keyword highlight, supports regex (using regex may affect performance, please use it appropriately)',
keywordRegex: 'Keyword, supports regex',
showFirstNLines: 'Show first N lines',
showLastNLines: 'Show last N lines',
regexReference: 'Regex syntax reference',
matchLinesWithNumbers: 'Match lines containing numbers',
matchLinesWithAorB: 'Match lines containing a or b',
exception: 'Exception',
matchLinesWithException: 'Match lines containing exceptions',
syntaxReference: 'Syntax reference',
searchMode: 'Search mode',
searchModeDescription:
'Search mode. By default, it views the last N lines of the file. Searching from the start means searching from the specified line downwards. Searching from the end means searching upwards from the end of the file for N lines.',
searchFromEnd: 'Search from the end',
searchFromStart: 'Search from the start',
firstNFileLines: 'First N lines of the file',
lastNFileLines: 'Last N lines of the file',
searchConfigReference: 'Search configuration reference',
searchFromEndExample1: 'Search from the end, first 0 lines of the file, last 3 lines of the file',
searchLastNLines: 'Search within the last 3 lines of the file',
searchFromStartExample1: 'Search from the start, first 0 lines of the file, last 3 lines of the file',
searchLineRange1: 'Search within lines 3 - 2147483647 of the file',
searchFromEndExample2: 'Search from the end, first 2 lines of the file, last 3 lines of the file',
searchLineRange2: 'Search within lines 1 - 2 of the file',
searchFromEndExample3: 'Search from the end, first 100 lines of the file, last 100 lines of the file',
searchLineRange3: 'Search within lines 1 - 100 of the file',
searchFromStartExample2: 'Search from the start, first 2 lines of the file, last 3 lines of the file',
searchLineRange4: 'Search within lines 2 - 2 of the file',
searchFromEndExample4: 'Search from the end, first 20 lines of the file, last 3 lines of the file',
searchLineRange5: 'Search within lines 17 - 20 of the file',
searchFromStartExample3: 'Search from the start, first 20 lines of the file, last 3 lines of the file',
searchLineRange6: 'Search within lines 3 - 20 of the file',
searchReference: 'Search reference',
error: 'Error',
checkWsProxy: 'Please check if the ws proxy is enabled',
sessionClosed: 'The session has been closed',
fileNotReadable: 'The current file is not readable. You need to configure readable file authorization.'
}
}

View File

@ -0,0 +1,47 @@
export default {
c: {
selectFile: 'Select File',
selectBuild: 'Select Build',
selectProduct: 'Select Product',
yes: 'Yes',
no: 'No',
selectPostPublishAction: 'Select Post-Publish Action',
cancel: 'Cancel',
confirm: 'Confirm'
},
p: {
distributeProject: 'Distribute Project -',
method: 'Method',
buildProduct: 'Build Product',
fileCenter: 'File Center',
staticFile: 'Static File',
uploadFile: 'Upload File',
remoteDownload: 'Remote Download',
selectDistributeFile: 'Select Distribute File',
usedTime: 'Used Time',
remoteDownloadURL: 'Remote Download URL',
remoteDownloadAddress: 'Remote Download Address',
clearPublish: 'Clear Publish',
clearPublishDescription:
'Clearing publish means deleting all files in the project folder directory before uploading new files and saving them.',
unZip: 'Unzip',
autoUnZip: 'Whether to automatically unzip uploaded compressed files. Supported compression types include tar.',
excludeFolder: 'Exclude Folder',
excludeFolderDescription: 'Automatically exclude extra folder names inside the compressed file when unzipping.',
postPublishAction: 'Post-Publish Action',
subDirectory: 'Sub-Directory',
subDirectoryDescription: 'If not filled in, it will be published to the root directory of the project.',
filterProject: 'Filter Project',
filterProjectDescription:
'After filtering, only the filtered items will be published for this operation, and it will only be effective for this operation.',
selectPublishProject: 'Please select a project to publish.',
selectBuildProduct: 'Select Build Product',
selectStaticFile: 'Select Static File',
pleaseInputRemoteAddress: 'Please enter the remote address',
pleaseSelectFile: 'Please select a file',
pleaseFillRemoteURL: 'Please fill in the remote URL',
pleaseFillBuildAndProduct: 'Please fill in Build and Product',
pleaseSelectFileCenterFile: 'Please select a file from the File Center',
pleaseSelectStaticFileFile: 'Please select a file from the Static File'
}
}

View File

@ -0,0 +1,51 @@
export default {
c: {
status: 'Status',
unknown: 'Unknown',
console: 'Console'
},
p: {
view: 'View',
currentStatus: 'Current Status:',
statusDescription: 'Status Description:',
refresh: 'Refresh',
seconds: 's seconds',
refreshCountdown: 'Refresh Countdown',
currentProjectDisabled: 'Current Project is Disabled',
running: 'Running',
notRunning: 'Not Running',
processId: 'Process ID:',
portNumber: 'Port Number:',
file: 'File',
configuration: 'Configuration',
nodeName: 'Node Name:',
projectName: 'Project Name:',
enable: 'Enable',
disable: 'Disable',
unbind: 'Unbind',
longPressToDragAndSort: 'Long press to drag and sort',
save: 'Save',
nodeNameLabel: 'Node Name',
projectNameLabel: 'Project Name',
projectStatusLabel: 'Project Status',
processPortLabel: 'Process/Port',
distributionStatusLabel: 'Distribution Status',
distributionResultLabel: 'Distribution Result',
distributionStatusMessageLabel: 'Distribution Status Message',
distributionDurationLabel: 'Distribution Duration',
fileSizeLabel: 'File Size',
lastDistributionTimeLabel: 'Last Distribution Time',
operationLabel: 'Operation',
networkError: 'Network Error',
fileManagement: 'File Management',
trackFile: 'Track File',
reallyReleaseCurrentProject: 'Are you sure you want to release (delete) the current project?',
willNotActuallyRequestNodeToDeleteProjectInfo: 'Will not actually request the node to delete project information',
generallyUsedWhenServerCannotBeConnectedAndIsNoLongerNeeded:
'Generally used when the server cannot be connected and is no longer needed',
willProduceRedundantDataIfMisoperated: 'If misoperated, redundant data will be produced!!!',
dangerousOperation: 'Dangerous operation!!!',
confirm: 'Confirm',
cancel: 'Cancel'
}
}

View File

@ -0,0 +1,23 @@
export default {
c: {},
p: {
warmReminder: 'Warm reminder',
distributionAuthorizationPathConfig: 'Current distribution authorization path configuration for the node',
absolutePathRequired: 'The path requires an absolute path configuration',
authorizationPath: 'Authorization path',
usageForCreatingDistributionProjects:
'Used for creating node distribution projects and publishing files in the file center',
inputAuthorizationPath:
"Please enter the authorization path. Pressing Enter supports entering multiple paths. The system will automatically filter out '../' paths and does not allow entering the root path",
staticDirectory: 'Static directory',
usageForStaticFileBindingAndReading:
'Used for static file binding and reading (it is not recommended to configure large directories to avoid scanning and consuming too many resources)',
inputStaticPaths:
"Please enter the static paths. Pressing Enter supports entering multiple paths. The system will automatically filter out '../' paths and does not allow entering the root path",
remoteDownloadSecureHost: 'Remote download secure HOST',
usageForDownloadingRemoteFiles: 'Used for downloading remote files for node distribution and file upload',
inputRemoteDownloadSecureHosts:
'Please enter the remote download secure HOST. Pressing Enter supports entering multiple paths. Example: https',
submit: 'Submit'
}
}

View File

@ -11,10 +11,12 @@
import page404 from './pages/404/404'
import build from './pages/build'
import certificate from './pages/certificate'
import dispatch from './pages/dispatch'
export default {
pages: {
404: page404,
build,
certificate
certificate,
dispatch
}
}

View File

@ -0,0 +1,17 @@
import log from './log'
import logRead from './logRead'
import start from './start'
import status from './status'
import whiteList from './white-list'
import list from './list'
import logReadView from './logReadView'
export default {
logReadView,
log,
logRead,
start,
status,
whiteList,
list
}

View File

@ -0,0 +1,174 @@
export default {
c: {
distributionType: '分发类型',
independent: '独立',
related: '关联',
unknown: '未知',
yes: '是',
no: '否',
edit: '编辑',
distributionID: '分发 ID',
distributionIDEqualsProjectID: '分发 ID 等同于项目 ID',
cannotModifyAfterCreation: '创建之后不能修改',
randomlyGenerated: '随机生成',
distributionName: '分发名称',
groupingName: '分组名称:',
addNewGrouping: '新增分组',
selectGrouping: '选择分组',
distributionNode: '分发节点',
noProjectInThisNode: '此节点暂无项目',
independentDistribution: '【独立分发】',
distributionOperationAfter: '分发后操作',
selectOperationAfterPublish: '请选择发布后操作',
intervalTime: '间隔时间',
ensureProjectRestart: '在执行多节点分发时候使用 顺序重启、完整顺序重启 时候需要保证项目能正常重启',
waitPreviousProjectStart: '并等待上一个项目启动完成才能关闭下一个项目',
configureBasedOnProjectStartTime: '请根据自身项目启动时间来配置',
suggestedIntervalTime: '一般建议 10 秒以上',
distributionIntervalTimeEffect: '分发间隔时间 (顺序重启、完整顺序重启)方式才生效',
secondaryDirectory: '二级目录',
publishToRootIfNotFilled: '不填写则发布至项目的根目录',
clearPublish: '清空发布',
clearPublishDescription: '清空发布是指在上传新文件前,会将项目文件夹目录里面的所有文件先删除后再保存新文件',
publishStopBefore:
'发布前停止是指在发布文件到项目文件时先将项目关闭,再进行文件替换。避免 windows 环境下出现文件被占用的情况',
publishStopBeforeColon: '发布前停止:',
distributionRequestAddress: '分发过程请求对应的地址,开始分发,分发完成,分发失败,取消分发',
parametersForDistribution: '传入参数有outGivingId、outGivingName、status、statusMsg、executeTime',
statusValues: '的值有1',
distributionStatuses: '分发中、2分发结束、3已取消、4分发失败',
asynchronousRequest: '异步请求不能保证有序性',
distributionProcessRequest: '分发过程请求,非必填GET请求',
immutableAfterCreation: '创建之后不能修改,分发 ID 等同于项目 ID',
runProject: '运行项目',
selectProjectAuthorizationPath: '请选择项目授权路径',
configuration: '配置',
configurationExample: '配置示例',
defaultLogDirectory: '默认是在插件端数据目录/${projectId}/${projectId}.log',
selectProject: '请选择项目',
systemPrompt: '系统提示',
confirm: '确认',
cancel: '取消'
},
p: {
distributionID: '分发id',
name: '名称',
selectGrouping: '请选择分组',
selectStatus: '请选择状态',
quickBackToFirstPage: '按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页',
search: '搜索',
addRelated: '新增关联',
addDistribution: '新增分发',
nodeDistributionDescription:
'节点分发是指,一个项目运行需要在多个节点(服务器)中运行,使用节点分发来统一管理这个项目(可以实现分布式项目管理功能)',
addRelatedProjectDescription: '新增关联项目是指,将已经在节点中创建好的项目关联为节点分发项目来实现统一管理',
createDistributionProjectDescription:
'创建分发项目是指,全新创建一个属于节点分发到项目,创建成功后项目信息将自动同步到对应的节点中,修改节点分发信息也自动同步到对应的节点中',
distributionFiles: '分发文件',
more: '更多',
cancelDistribution: '取消分发',
delete: '删除',
release: '释放',
deleteCompletely: '彻底删除',
unbind: '解绑',
editRelatedProject: '编辑关联项目',
addRelatedProject: '新增关联项目',
noData: '暂无数据,请先新增节点项目数据',
node: '节点:',
selectNode: '请选择节点',
noNodeInfo: '暂无节点信息',
project: '项目:',
add: '新增',
editDistributionProject: '编辑分发项目',
createDistributionProject: '创建分发项目',
loadingData: '加载数据中',
remind: '提醒',
noLogicalNode: '当前工作空间还没有逻辑节点不能创建节点分发奥',
distributionName: '分发名称(项目名称)',
runningMode: '运行方式',
scriptTemplate: '配合脚本模版实现自定义项目管理',
staticFolder: '项目为静态文件夹',
noProjectStatus: '没有项目状态以及控制等功能',
selectRunningMode: '请选择运行方式',
notRecommended: '不推荐',
projectPath: '项目路径',
authorizationPath: '授权路径是指项目文件存放到服务中的文件夹',
modifyAuthorizationConfig: '可以到【节点分发】=>【分发授权配置】修改',
projectFolder: '项目文件夹是项目实际存放的目录名称',
projectFilesStored: '项目文件会存放到',
authorizationPathProjectFolder: '项目授权路径+项目文件夹',
jarFolder: '项目存储的文件夹jar 包存放的文件夹',
configureAuthorizationDirectory: '需要提前为工作空间配置授权目录',
configureDirectory: '配置目录',
projectFullDirectory: '项目完整目录',
content: '内容',
yamlConfig:
'以 yaml/yml 格式配置,scriptId 为项目路径下的脚本文件的相对路径或者服务端脚本模版ID可以到服务端脚本模版编辑弹窗中查看 scriptId',
variablesInScript: '脚本里面支持的变量有:${PROJECT_ID}、${PROJECT_NAME}、${PROJECT_PATH}',
scriptOutput: '流程执行完脚本后输出的内容最后一行必须为running',
processID: '为当前项目实际的进程ID',
incorrectOutputFormat: '。如果输出最后一行不是预期格式项目状态将是未运行',
referToConfigExample: '配置详情请参考配置示例',
recommendedScriptDistribution: '建议使用服务端脚本分发到脚本:',
viewServerScript: '查看服务端脚本',
fillProjectDSLConfig: '请填写项目 DSL 配置内容,可以点击上方切换 tab 查看配置示例',
logDirectory: '日志目录',
logDirectorySelection: '日志目录是指控制台日志存储目录',
sameConfigAsAuthorizationDirectory: '可选择的列表和项目授权目录是一致的,即相同配置',
selectLogDirectory: '请选择日志目录',
mainClass: '程序运行的 main 类(jar 模式运行可以不填)',
jvmArgs: '填写【xxx',
jvmArgsExample: '-Dext.dirs=xxx: -cp xx :xx】',
selectDistributionNode: '请选择分发节点',
jvmParams: 'JVM 参数',
params: '参数',
nonMandatory: '非必填',
jvmParamsExample: 'jvm,.如:-Xms512m -Xmx512m',
argsParams: 'args 参数',
functionArgsParams: '函数 args 参数,非必填',
argsParamsExample: '如:--server',
autoStart: '自启动',
checkProjectStatus: '插件端启动的时候检查项目状态,如果项目状态是未运行则尝试执行启动项目',
nonServerAutoStart: '非服务器开机自启,如需开机自启建议配置',
pluginAutoStart: '插件端开机自启',
turnOnAutoStart: '并开启此开关',
on: '开',
off: '关',
pluginAutoCheckProjectOnStart: '插件端启动时自动检查项目如未启动将尝试启动',
dslEnvironmentVariables: 'DSL环境变量',
environmentVariables: '环境变量',
exampleVariable: '如key1',
projectRequestOnStartStopRestart: '项目启动,停止,重启都将请求对应的地址',
parametersForProjectRequest: '传入参数有projectId、projectName、type、result',
valuesForProjectRequest: '的值有stop、beforeStop、start、beforeRestart',
projectRequestOnFileChange: '项目启动,停止,重启,文件变动都将请求对应的地址,非必填GET请求',
configureAuthorizationDirectory1: '配置授权目录',
viewScript: '查看脚本',
projectGrouping: '项目分组',
distributionStatus: '分发后',
status: '状态',
creationTime: '创建时间',
modificationTime: '修改时间',
modifiedBy: '修改人',
operations: '操作',
enterProjectID: '请输入项目ID',
enterProjectName: '请输入项目名称',
selectProjectRunningMode: '请选择项目运行方式',
enterProjectFolder: '请输入项目文件夹',
selectAtLeastOneNodeProject: '至少选择1个节点项目',
selectAtLeastOneNode: '请至少选择 1 个节点',
confirmDeletionOfDistributionInfo:
'真的要彻底删除分发信息么?删除后节点下面的项目也都将彻底删除,彻底项目会自动删除项目相关文件奥(包含项目日志,日志备份,项目文件)',
confirmDeletionOfDistributionInfoSimple: '真的要删除分发信息么?删除后节点下面的项目也都将删除',
confirmReleaseOfDistributionInfo:
'真的要释放分发信息么?释放之后节点下面的项目信息还会保留,如需删除项目还需要到节点管理中操作',
confirmUnbindingCurrentNodeDistribution: '真的要解绑当前节点分发么?',
unbindCheckDataAssociation: '解绑会检查数据关联性,不会真实请求节点删除项目信息',
unbindForUnreachableServer: '一般用于服务器无法连接且已经确定不再使用',
cautionDueToMistakeOperation: '如果误操作会产生冗余数据!!!',
dangerousOperation: '危险操作!!!',
noMoreNodeProjects: '已无更多节点项目,请先创建项目',
selectNodeFirst: '请先选择节点',
confirmCancellationOfCurrentDistribution: '真的取消当前分发吗?'
}
}

View File

@ -0,0 +1,29 @@
export default {
c: {
unknown: '未知',
result: '分发结果'
},
p: {
noLogs: '没有任何分发日志',
selectNode: '请选择节点',
distributeProject: '分发项目',
selectStatus: '请选择状态',
goToFirstPage: '按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页',
search: '搜索',
relatedData: '关联数据:',
details: '详情',
info: '详情信息',
projectId: '分发项目 ID',
nodeName: '节点名称',
distributeId: '项目 ID',
method: '分发方式',
duration: '分发耗时',
fileSize: '文件大小',
startTime: '开始时间',
endTime: '结束时间',
statusMessage: '分发状态消息',
operator: '操作人',
status: '状态',
action: '操作'
}
}

View File

@ -0,0 +1,31 @@
export default {
c: {
logName: '日志名称',
action: '新增'
},
p: {
clickButtonToGoBackToFirstPage: '按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页',
search: '搜索',
edit: '编辑',
view: '查看',
delete: '删除',
editLogSearch: '编辑日志搜索',
logItemName: '日志项目名称',
bindNode: '绑定节点',
node: '节点:',
pleaseSelectNode: '请选择节点',
project: '项目:',
pleaseSelectProject: '请选择项目',
searchAndView: '搜索查看',
name: '名称',
modifier: '修改人',
modificationTime: '修改时间',
operation: '操作',
pleaseFillInLogItemName: '请填写日志项目名称',
atLeastSelectOneNodeAndProject: '至少选择一个节点和项目',
systemPrompt: '系统提示',
reallyDeleteLogSearch: '真的要删除日志搜索么?',
confirm: '确认',
cancel: '取消'
}
}

View File

@ -0,0 +1,45 @@
export default {
c: {},
p: {
node: '节点',
chooseNode: '请选择节点',
searchKeyword: '搜关键词',
keywordHighlight: '关键词高亮,支持正则(正则可能影响性能请酌情使用)',
keywordRegex: '关键词,支持正则',
showFirstNLines: '显示前N行',
showLastNLines: '显示后N行',
regexReference: '正则语法参考',
matchLinesWithNumbers: '匹配包含数字的行',
matchLinesWithAorB: '匹配包含 a 或者 b 的行',
exception: '异常',
matchLinesWithException: '匹配包含 异常 的行',
syntaxReference: '语法参考',
searchMode: '搜索模式',
searchModeDescription:
'搜索模式,默认查看文件最后多少行,从头搜索指从指定行往下搜索,从尾搜索指从文件尾往上搜索多少行',
searchFromEnd: '从尾搜索',
searchFromStart: '从头搜索',
firstNFileLines: '文件前N行',
lastNFileLines: '文件后N行',
searchConfigReference: '搜索配置参考',
searchFromEndExample1: '从尾搜索、文件前0行、文件后3行',
searchLastNLines: '在文件最后 3 行中搜索',
searchFromStartExample1: '从头搜索、文件前0行、文件后3行',
searchLineRange1: '在文件第 3 - 2147483647 行中搜索',
searchFromEndExample2: '从尾搜索、文件前2行、文件后3行',
searchLineRange2: '在文件第 1 - 2 行中搜索',
searchFromEndExample3: '从尾搜索、文件前100行、文件后100行',
searchLineRange3: '在文件第 1 - 100 行中搜索',
searchFromStartExample2: '从头搜索、文件前2行、文件后3行',
searchLineRange4: '在文件第 2 - 2 行中搜索',
searchFromEndExample4: '从尾搜索、文件前20行、文件后3行',
searchLineRange5: '在文件第 17 - 20 行中搜索',
searchFromStartExample3: '从头搜索、文件前20行、文件后3行',
searchLineRange6: '在文件第 3 - 20 行中搜索',
searchReference: '搜索参考',
error: '错误',
checkWsProxy: '请检查是否开启 ws 代理',
sessionClosed: '会话已经关闭',
fileNotReadable: '当前文件不可读,需要配置可读文件授权'
}
}

View File

@ -0,0 +1,45 @@
export default {
c: {
selectFile: '选择文件',
selectBuild: '选择构建',
selectProduct: '选择产物',
yes: '是',
no: '否',
selectPostPublishAction: '请选择发布后操作',
cancel: '取消',
confirm: '确认'
},
p: {
distributeProject: '分发项目-',
method: '方式',
buildProduct: '构建产物',
fileCenter: '文件中心',
staticFile: '静态文件',
uploadFile: '上传文件',
remoteDownload: '远程下载',
selectDistributeFile: '选择分发文件',
usedTime: '用时',
remoteDownloadURL: '远程下载URL',
remoteDownloadAddress: '远程下载地址',
clearPublish: '清空发布',
clearPublishDescription: '清空发布是指在上传新文件前,会将项目文件夹目录里面的所有文件先删除后再保存新文件',
unZip: '是否解压',
autoUnZip: '如果上传的压缩文件是否自动解压 支持的压缩包类型有 tar',
excludeFolder: '剔除文件夹',
excludeFolderDescription: '解压时候自动剔除压缩包里面多余的文件夹名',
postPublishAction: '分发后操作',
subDirectory: '二级目录',
subDirectoryDescription: '不填写则发布至项目的根目录',
filterProject: '筛选项目',
filterProjectDescription: '筛选之后本次发布操作只发布筛选项,并且只对本次操作生效',
selectPublishProject: '请选择指定发布的项目',
selectBuildProduct: '选择构建产物',
selectStaticFile: '选择静态文件',
pleaseInputRemoteAddress: '请输入远程地址',
pleaseSelectFile: '请选择文件',
pleaseFillRemoteURL: '请填写远程URL',
pleaseFillBuildAndProduct: '请填写构建和产物',
pleaseSelectFileCenterFile: '请选择文件中心的文件',
pleaseSelectStaticFileFile: '请选择静态文件中的文件'
}
}

View File

@ -0,0 +1,50 @@
export default {
c: {
status: '状态',
unknown: '未知',
console: '控制台'
},
p: {
view: '查看',
currentStatus: '当前状态:',
statusDescription: '状态描述:',
refresh: '刷新',
seconds: 's 秒',
refreshCountdown: '刷新倒计时',
currentProjectDisabled: '当前项目被禁用',
running: '运行中',
notRunning: '未运行',
processId: '进程号:',
portNumber: '端口号:',
file: '文件',
configuration: '配置',
nodeName: '节点名:',
projectName: '项目名:',
enable: '启用',
disable: '禁用',
unbind: '解绑',
longPressToDragAndSort: '长按可以拖动排序',
save: '保存',
nodeNameLabel: '节点名称',
projectNameLabel: '项目名称',
projectStatusLabel: '项目状态',
processPortLabel: '进程/端口',
distributionStatusLabel: '分发状态',
distributionResultLabel: '分发结果',
distributionStatusMessageLabel: '分发状态消息',
distributionDurationLabel: '分发耗时',
fileSizeLabel: '文件大小',
lastDistributionTimeLabel: '最后分发时间',
operationLabel: '操作',
networkError: '网络异常',
fileManagement: '文件管理',
trackFile: '跟踪文件',
reallyReleaseCurrentProject: '真的要释放(删除)当前项目么?',
willNotActuallyRequestNodeToDeleteProjectInfo: '不会真实请求节点删除项目信息',
generallyUsedWhenServerCannotBeConnectedAndIsNoLongerNeeded: '一般用于服务器无法连接且已经确定不再使用',
willProduceRedundantDataIfMisoperated: '如果误操作会产生冗余数据!!!',
dangerousOperation: '危险操作!!!',
confirm: '确认',
cancel: '取消'
}
}

View File

@ -0,0 +1,18 @@
export default {
c: {},
p: {
warmReminder: '温馨提醒',
distributionAuthorizationPathConfig: '当前为节点分发的授权路径配置',
absolutePathRequired: '路径需要配置绝对路径',
authorizationPath: '授权路径',
usageForCreatingDistributionProjects: '用于创建节点分发项目、文件中心发布文件',
inputAuthorizationPath: '请输入授权路径,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径',
staticDirectory: '静态目录',
usageForStaticFileBindingAndReading: '用于静态文件绑定和读取(不建议配置大目录,避免扫描消耗过多资源)',
inputStaticPaths: '请输入静态,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径',
remoteDownloadSecureHost: '远程下载安全HOST',
usageForDownloadingRemoteFiles: '用于下载远程文件来进行节点分发和文件上传',
inputRemoteDownloadSecureHosts: '请输入远程下载安全HOST回车支持输入多个路径示例 https',
submit: '提交'
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
:auto-refresh-time="30"
:active-page="activePage"
table-name="dispatch-log-list"
empty-description="没有任何分发日志"
:empty-description="$tl('p.noLogs')"
size="middle"
:data-source="list"
:columns="columns"
@ -21,20 +21,35 @@
>
<template #title>
<a-space wrap class="search-box">
<a-select v-model:value="listQuery.nodeId" allow-clear placeholder="请选择节点" class="search-input-item">
<a-select
v-model:value="listQuery.nodeId"
allow-clear
:placeholder="$tl('p.selectNode')"
class="search-input-item"
>
<a-select-option v-for="node in nodeList" :key="node.id">{{ node.name }}</a-select-option>
</a-select>
<a-select v-model:value="listQuery.outGivingId" allow-clear placeholder="分发项目" class="search-input-item">
<a-select
v-model:value="listQuery.outGivingId"
allow-clear
:placeholder="$tl('p.distributeProject')"
class="search-input-item"
>
<a-select-option v-for="dispatch in dispatchList" :key="dispatch.id">{{ dispatch.name }}</a-select-option>
</a-select>
<a-select v-model:value="listQuery.status" allow-clear placeholder="请选择状态" class="search-input-item">
<a-select
v-model:value="listQuery.status"
allow-clear
:placeholder="$tl('p.selectStatus')"
class="search-input-item"
>
<a-select-option v-for="(item, key) in dispatchStatusMap" :key="key" :value="key">{{
item
}}</a-select-option>
</a-select>
<a-range-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" @change="onchangeTime" />
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
<a-button :loading="loading" type="primary" @click="loadData">搜索</a-button>
<a-tooltip :title="$tl('p.goToFirstPage')">
<a-button :loading="loading" type="primary" @click="loadData">{{ $tl('p.search') }}</a-button>
</a-tooltip>
</a-space>
</template>
@ -67,7 +82,10 @@
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'mode'">
<a-tooltip placement="topLeft" :title="`${dispatchMode[text] || ''} 关联数据:${record.modeData || ''}`">
<a-tooltip
placement="topLeft"
:title="`${dispatchMode[text] || ''} ${$tl('p.relatedData')}${record.modeData || ''}`"
>
<span>{{ dispatchMode[text] || '' }}</span>
</a-tooltip>
</template>
@ -101,22 +119,22 @@
</template>
<template v-else-if="column.dataIndex === 'status'">
<!-- {{ dispatchStatusMap[text] || "未知" }} -->
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || $tl('c.unknown') }}</a-tag>
<a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{
dispatchStatusMap[text] || '未知'
dispatchStatusMap[text] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{
dispatchStatusMap[text] || '未知'
dispatchStatusMap[text] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || $tl('c.unknown') }}</a-tag>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-button type="primary" size="small" @click="handleDetail(record)">详情</a-button>
<a-button type="primary" size="small" @click="handleDetail(record)">{{ $tl('p.details') }}</a-button>
</template>
</template>
</CustomTable>
<!-- 详情区 -->
<a-modal v-model:open="detailVisible" destroy-on-close width="600px" title="详情信息" :footer="null">
<a-modal v-model:open="detailVisible" destroy-on-close width="600px" :title="$tl('p.info')" :footer="null">
<a-list item-layout="horizontal" :data-source="detailData">
<template #renderItem="{ item }">
<a-list-item>
@ -156,49 +174,49 @@ export default {
detailData: [],
columns: [
{
title: '分发项目 ID',
title: this.$tl('p.projectId'),
dataIndex: 'outGivingId',
width: 100,
ellipsis: true
},
{
title: '节点名称',
title: this.$tl('p.nodeName'),
dataIndex: 'nodeName',
ellipsis: true,
width: 150
},
{
title: '项目 ID',
title: this.$tl('p.distributeId'),
dataIndex: 'projectId',
ellipsis: true,
width: 100
},
{
title: '分发方式',
title: this.$tl('p.method'),
dataIndex: 'mode',
ellipsis: true,
width: '100px'
},
{
title: '分发结果',
title: this.$tl('c.result'),
dataIndex: 'outGivingResultMsg',
ellipsis: true,
width: 200
},
{
title: '分发耗时',
title: this.$tl('p.duration'),
dataIndex: 'outGivingResultTime',
width: '120px'
},
{
title: '文件大小',
title: this.$tl('p.fileSize'),
dataIndex: 'outGivingResultSize',
width: '100px'
},
{
title: '开始时间',
title: this.$tl('p.startTime'),
dataIndex: 'startTime',
customRender: ({ text }) => {
return parseTime(text)
@ -207,7 +225,7 @@ export default {
width: '170px'
},
{
title: '结束时间',
title: this.$tl('p.endTime'),
dataIndex: 'endTime',
sorter: true,
customRender: ({ text }) => {
@ -216,26 +234,26 @@ export default {
width: '170px'
},
{
title: '分发状态消息',
title: this.$tl('p.statusMessage'),
dataIndex: 'outGivingResultMsgData',
ellipsis: true,
width: 100
},
{
title: '操作人',
title: this.$tl('p.operator'),
dataIndex: 'modifyUser',
ellipsis: true,
width: 120
},
{
title: '状态',
title: this.$tl('p.status'),
dataIndex: 'status',
width: 100,
ellipsis: true,
fixed: 'right'
},
{ title: '操作', dataIndex: 'operation', align: 'center', width: '100px', fixed: 'right' }
{ title: this.$tl('p.action'), dataIndex: 'operation', align: 'center', width: '100px', fixed: 'right' }
]
}
},
@ -251,6 +269,9 @@ export default {
this.handleFilter()
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.log.${key}`, ...args)
},
readJsonStrField,
//
handleFilter() {
@ -296,7 +317,7 @@ export default {
this.detailVisible = true
this.temp = Object.assign({}, record)
this.detailData.push({ title: '分发结果', description: this.temp.result })
this.detailData.push({ title: this.$tl('c.result'), description: this.temp.result })
},
//
changePage(pagination, filters, sorter) {

View File

@ -16,15 +16,15 @@
<a-space wrap class="search-box">
<a-input
v-model:value="listQuery['%name%']"
placeholder="日志名称"
:placeholder="$tl('c.logName')"
class="search-input-item"
@press-enter="loadData"
/>
<a-tooltip title="按住 Ctr 或者 Alt/Option 键点击按钮快速回到第一页">
<a-button type="primary" :loading="loading" @click="loadData">搜索</a-button>
<a-tooltip :title="$tl('p.clickButtonToGoBackToFirstPage')">
<a-button type="primary" :loading="loading" @click="loadData">{{ $tl('p.search') }}</a-button>
</a-tooltip>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-button type="primary" @click="handleAdd">{{ $tl('c.action') }}</a-button>
</a-space>
</template>
<template #bodyCell="{ column, text, record }">
@ -36,9 +36,9 @@
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button>
<a-button type="primary" size="small" @click="handleLogRead(record)">查看</a-button>
<a-button type="primary" danger size="small" @click="handleDelete(record)">删除</a-button>
<a-button type="primary" size="small" @click="handleEdit(record)">{{ $tl('p.edit') }}</a-button>
<a-button type="primary" size="small" @click="handleLogRead(record)">{{ $tl('p.view') }}</a-button>
<a-button type="primary" danger size="small" @click="handleDelete(record)">{{ $tl('p.delete') }}</a-button>
</a-space>
</template>
</template>
@ -49,23 +49,23 @@
destroy-on-close
:confirm-loading="confirmLoading"
width="60%"
title="编辑日志搜索"
:title="$tl('p.editLogSearch')"
:mask-closable="false"
@ok="handleEditOk"
>
<a-form ref="editForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
<a-form-item label="日志名称" name="name">
<a-input v-model:value="temp.name" :max-length="50" placeholder="日志项目名称" />
<a-form-item :label="$tl('c.logName')" name="name">
<a-input v-model:value="temp.name" :max-length="50" :placeholder="$tl('p.logItemName')" />
</a-form-item>
<a-form-item label="绑定节点" required>
<a-form-item :label="$tl('p.bindNode')" required>
<a-space direction="vertical" style="width: 100%">
<a-row v-for="(item, index) in temp.projectList" :key="index">
<a-col :span="11">
<span>节点: </span>
<span>{{ $tl('p.node') }} </span>
<a-select
v-model:value="item.nodeId"
style="width: 80%"
placeholder="请选择节点"
:placeholder="$tl('p.pleaseSelectNode')"
@change="
() => {
temp = {
@ -94,12 +94,12 @@
</a-select>
</a-col>
<a-col :span="11">
<span>项目: </span>
<span>{{ $tl('p.project') }} </span>
<a-select
v-model:value="item.projectId"
:disabled="!item.nodeId"
style="width: 80%"
:placeholder="`请选择项目`"
:placeholder="`${$tl('p.pleaseSelectProject')}`"
>
<!-- <a-select-option value=""> 请先选择节点</a-select-option> -->
<template v-if="nodeProjectList[item.nodeId]">
@ -126,7 +126,7 @@
</a-col>
</a-row>
<a-button type="primary" @click="() => temp.projectList.push({})">新增</a-button>
<a-button type="primary" @click="() => temp.projectList.push({})">{{ $tl('c.action') }}</a-button>
</a-space>
</a-form-item>
</a-form>
@ -145,7 +145,7 @@
"
>
<template #title>
搜索查看
{{ $tl('p.searchAndView') }}
{{ temp.cacheData && temp.cacheData.logFile ? ':' + temp.cacheData.logFile : '' }}
</template>
<logReadView
@ -187,14 +187,14 @@ export default {
editVisible: false,
columns: [
{
title: '名称',
title: this.$tl('p.name'),
dataIndex: 'name',
ellipsis: true,
tooltip: true
},
{
title: '修改人',
title: this.$tl('p.modifier'),
dataIndex: 'modifyUser',
ellipsis: true,
align: 'center',
@ -202,7 +202,7 @@ export default {
width: 120
},
{
title: '修改时间',
title: this.$tl('p.modificationTime'),
dataIndex: 'modifyTimeMillis',
sorter: true,
customRender: ({ text }) => {
@ -214,7 +214,7 @@ export default {
width: 180
},
{
title: '操作',
title: this.$tl('p.operation'),
dataIndex: 'operation',
ellipsis: true,
@ -223,7 +223,7 @@ export default {
}
],
rules: {
name: [{ required: true, message: '请填写日志项目名称', trigger: 'blur' }]
name: [{ required: true, message: this.$tl('p.pleaseFillInLogItemName'), trigger: 'blur' }]
},
confirmLoading: false
}
@ -239,6 +239,9 @@ export default {
this.loadData()
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.logRead.${key}`, ...args)
},
//
loadData(pointerEvent) {
this.loading = true
@ -314,7 +317,7 @@ export default {
})
if (!temp.projectList || !temp.projectList.length) {
$notification.warn({
message: '至少选择一个节点和项目'
message: this.$tl('p.atLeastSelectOneNodeAndProject')
})
return false
}
@ -340,11 +343,11 @@ export default {
//
handleDelete(record) {
$confirm({
title: '系统提示',
title: this.$tl('p.systemPrompt'),
zIndex: 1009,
content: '真的要删除日志搜索么?',
okText: '确认',
cancelText: '取消',
content: this.$tl('p.reallyDeleteLogSearch'),
okText: this.$tl('p.confirm'),
cancelText: this.$tl('p.cancel'),
onOk: () => {
return deleteLogRead(record.id).then((res) => {
if (res.code === 200) {

View File

@ -7,11 +7,11 @@
<div class="dir-container">
<template v-if="temp.projectList && temp.cacheData">
<a-form layout="inline" autocomplete="off">
<a-form-item label="节点">
<a-form-item :label="$tl('p.node')">
<a-select
:value="`${temp.cacheData.useNodeId},${temp.cacheData.useProjectId}`"
style="width: 200px"
placeholder="请选择节点"
:placeholder="$tl('p.chooseNode')"
@change="nodeChange"
>
<a-select-option v-for="item in temp.projectList" :key="`${item.nodeId},${item.projectId}`">
@ -40,21 +40,21 @@
<a-form layout="inline" autocomplete="off">
<a-space direction="vertical" style="width: 100%">
<a-space>
<a-form-item label="搜关键词">
<a-form-item :label="$tl('p.searchKeyword')">
<!-- 关键词 -->
<!-- ^.*\d+.*$ -->
<!-- .*(0999996|0999995).* .*(a|b).* -->
<a-tooltip placement="right" title="关键词高亮,支持正则(正则可能影响性能请酌情使用)">
<a-tooltip placement="right" :title="$tl('p.keywordHighlight')">
<a-input
v-model:value="temp.cacheData.keyword"
placeholder="关键词,支持正则"
:placeholder="$tl('p.keywordRegex')"
:style="`width: 250px`"
@press-enter="sendSearchLog"
>
</a-input>
</a-tooltip>
</a-form-item>
<a-form-item label="显示前N行">
<a-form-item :label="$tl('p.showFirstNLines')">
<a-input-number
id="inputNumber"
v-model:value="temp.cacheData.beforeCount"
@ -63,7 +63,7 @@
@press-enter="sendSearchLog"
/>
</a-form-item>
<a-form-item label="显示后N行">
<a-form-item :label="$tl('p.showLastNLines')">
<a-input-number
id="inputNumber"
v-model:value="temp.cacheData.afterCount"
@ -72,26 +72,27 @@
@press-enter="sendSearchLog"
/>
</a-form-item>
<a-popover title="正则语法参考">
<a-popover :title="$tl('p.regexReference')">
<template #content>
<ul>
<li><b>^.*\d+.*$</b> - 匹配包含数字的行</li>
<li><b>.*(a|b).*</b> - 匹配包含 a 或者 b 的行</li>
<li><b>.*(异常).*</b> - 匹配包含 异常 的行</li>
<li><b>^.*\d+.*$</b> - {{ $tl('p.matchLinesWithNumbers') }}</li>
<li><b>.*(a|b).*</b> - {{ $tl('p.matchLinesWithAorB') }}</li>
<li>
<b>.*({{ $tl('p.exception') }}).*</b> - {{ $tl('p.matchLinesWithException') }}
</li>
</ul>
</template>
<a-button type="link" style="padding: 0"
><UnorderedListOutlined /><span style="margin-left: 2px">语法参考</span></a-button
><UnorderedListOutlined /><span style="margin-left: 2px">{{
$tl('p.syntaxReference')
}}</span></a-button
>
</a-popover>
</a-space>
<a-space>
<a-form-item label="搜索模式">
<a-form-item :label="$tl('p.searchMode')">
<!-- -->
<a-tooltip
placement="right"
title="搜索模式,默认查看文件最后多少行,从头搜索指从指定行往下搜索,从尾搜索指从文件尾往上搜索多少行"
>
<a-tooltip placement="right" :title="$tl('p.searchModeDescription')">
<a-select
:style="`width: 250px`"
:value="temp.cacheData.first"
@ -103,12 +104,12 @@
}
"
>
<a-select-option value="false">从尾搜索</a-select-option>
<a-select-option value="true">从头搜索 </a-select-option>
<a-select-option value="false">{{ $tl('p.searchFromEnd') }}</a-select-option>
<a-select-option value="true">{{ $tl('p.searchFromStart') }} </a-select-option>
</a-select>
</a-tooltip>
</a-form-item>
<a-form-item label="文件前N行">
<a-form-item :label="$tl('p.firstNFileLines')">
<a-input-number
id="inputNumber"
v-model:value="temp.cacheData.head"
@ -117,7 +118,7 @@
@press-enter="sendSearchLog"
/>
</a-form-item>
<a-form-item label="文件后N行">
<a-form-item :label="$tl('p.lastNFileLines')">
<a-input-number
id="inputNumber"
v-model:value="temp.cacheData.tail"
@ -126,20 +127,36 @@
@press-enter="sendSearchLog"
/>
</a-form-item>
<a-popover title="搜索配置参考">
<a-popover :title="$tl('p.searchConfigReference')">
<template #content>
<ul>
<li><b>从尾搜索文件前0行文件后3行</b> - 在文件最后 3 行中搜索</li>
<li><b>从头搜索文件前0行文件后3行</b> - 在文件第 3 - 2147483647 行中搜索</li>
<li><b>从尾搜索文件前2行文件后3行</b> - 在文件第 1 - 2 行中搜索</li>
<li><b>从尾搜索文件前100行文件后100行</b> - 在文件第 1 - 100 行中搜索</li>
<li><b>从头搜索文件前2行文件后3行</b> - 在文件第 2 - 2 行中搜索</li>
<li><b>从尾搜索文件前20行文件后3行</b> - 在文件第 17 - 20 行中搜索</li>
<li><b>从头搜索文件前20行文件后3行</b> - 在文件第 3 - 20 行中搜索</li>
<li>
<b>{{ $tl('p.searchFromEndExample1') }}</b> - {{ $tl('p.searchLastNLines') }}
</li>
<li>
<b>{{ $tl('p.searchFromStartExample1') }}</b> - {{ $tl('p.searchLineRange1') }}
</li>
<li>
<b>{{ $tl('p.searchFromEndExample2') }}</b> - {{ $tl('p.searchLineRange2') }}
</li>
<li>
<b>{{ $tl('p.searchFromEndExample3') }}</b> - {{ $tl('p.searchLineRange3') }}
</li>
<li>
<b>{{ $tl('p.searchFromStartExample2') }}</b> - {{ $tl('p.searchLineRange4') }}
</li>
<li>
<b>{{ $tl('p.searchFromEndExample4') }}</b> - {{ $tl('p.searchLineRange5') }}
</li>
<li>
<b>{{ $tl('p.searchFromStartExample3') }}</b> - {{ $tl('p.searchLineRange6') }}
</li>
</ul>
</template>
<a-button type="link" style="padding: 0"
><UnorderedListOutlined /><span style="margin-left: 2px">搜索参考</span></a-button
><UnorderedListOutlined /><span style="margin-left: 2px">{{
$tl('p.searchReference')
}}</span></a-button
>
</a-popover>
</a-space></a-space
@ -264,9 +281,7 @@ export default {
})[0]
const socketUrl = getWebSocketUrl(
'/socket/console',
`userId=${this.getLongTermToken()}&id=${itemProjectData?.id}&nodeId=${
item.nodeId
}&type=console&workspaceId=${this.getWorkspaceId()}`
`userId=${this.getLongTermToken()}&id=${itemProjectData?.id}&nodeId=${item.nodeId}&type=console&workspaceId=${this.getWorkspaceId()}`
)
const domId = `pre-dom-${item.nodeId},${item.projectId}`
this.socketCache = { ...this.socketCache, [domId]: {} }
@ -302,6 +317,9 @@ export default {
this.close()
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.logReadView.${key}`, ...args)
},
close() {
Object.keys(this.socketCache).forEach((item) => {
clearInterval(this.socketCache[item].heart)
@ -315,7 +333,7 @@ export default {
console.error(err)
$notification.error({
key: 'log-read-error',
message: 'web socket 错误,请检查是否开启 ws 代理'
message: `web socket ${this.$tl('p.error')},${this.$tl('p.checkWsProxy')}`
})
clearInterval(this.socketCache[id].heart)
}
@ -324,7 +342,9 @@ export default {
console.error(err)
$notification.info({
key: 'log-read-close',
message: ((this.nodeName[item.nodeId] && this.nodeName[item.nodeId].name) || '') + ' 会话已经关闭[tail-log]-'
message:
((this.nodeName[item.nodeId] && this.nodeName[item.nodeId].name) || '') +
` ${this.$tl('p.sessionClosed')}[tail-log]-`
})
clearInterval(this.socketCache[id].heart)
}
@ -421,7 +441,7 @@ export default {
this.sendSearchLog()
} else {
//
$message.error('当前文件不可读,需要配置可读文件授权')
$message.error(this.$tl('p.fileNotReadable'))
}
}
},

View File

@ -8,7 +8,7 @@
:footer="uploading ? null : undefined"
width="50%"
:keyboard="false"
:title="'分发项目-' + data.name"
:title="$tl('p.distributeProject') + data.name"
:mask-closable="false"
@ok="handleDispatchOk"
@cancel="
@ -18,37 +18,39 @@
"
>
<a-form ref="dispatchForm" :rules="rules" :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="方式" name="type">
<a-form-item :label="$tl('p.method')" name="type">
<a-radio-group v-model:value="temp.type" name="type" :disabled="!!percentage" @change="restForm">
<a-radio :value="'use-build'">构建产物</a-radio>
<a-radio :value="'file-storage'">文件中心</a-radio>
<a-radio :value="'static-file-storage'">静态文件</a-radio>
<a-radio :value="'upload'">上传文件</a-radio>
<a-radio :value="'download'">远程下载</a-radio>
<a-radio :value="'use-build'">{{ $tl('p.buildProduct') }}</a-radio>
<a-radio :value="'file-storage'">{{ $tl('p.fileCenter') }}</a-radio>
<a-radio :value="'static-file-storage'">{{ $tl('p.staticFile') }}</a-radio>
<a-radio :value="'upload'">{{ $tl('p.uploadFile') }}</a-radio>
<a-radio :value="'download'">{{ $tl('p.remoteDownload') }}</a-radio>
</a-radio-group>
</a-form-item>
<!-- 手动上传 -->
<a-form-item v-if="temp.type === 'upload'" label="选择分发文件" name="clearOld">
<a-form-item v-if="temp.type === 'upload'" :label="$tl('p.selectDistributeFile')" name="clearOld">
<a-progress v-if="percentage" :percent="percentage">
<template #format="percent">
{{ percent }}%
<template v-if="percentageInfo.total"> ({{ renderSize(percentageInfo.total) }}) </template>
<template v-if="percentageInfo.duration"> 用时:{{ formatDuration(percentageInfo.duration) }} </template>
<template v-if="percentageInfo.duration">
{{ $tl('p.usedTime') }}:{{ formatDuration(percentageInfo.duration) }}
</template>
</template>
</a-progress>
<a-upload :file-list="fileList" :disabled="!!percentage" :before-upload="beforeUpload" @remove="handleRemove">
<LoadingOutlined v-if="percentage" />
<a-button v-else type="primary"><UploadOutlined />选择文件</a-button>
<a-button v-else type="primary"><UploadOutlined />{{ $tl('c.selectFile') }}</a-button>
</a-upload>
</a-form-item>
<!-- 远程下载 -->
<a-form-item v-else-if="temp.type === 'download'" label="远程下载URL" name="url">
<a-input v-model:value="temp.url" placeholder="远程下载地址" />
<a-form-item v-else-if="temp.type === 'download'" :label="$tl('p.remoteDownloadURL')" name="url">
<a-input v-model:value="temp.url" :placeholder="$tl('p.remoteDownloadAddress')" />
</a-form-item>
<!-- 在线构建 -->
<template v-else-if="temp.type == 'use-build'">
<a-form-item label="选择构建">
<a-form-item :label="$tl('c.selectBuild')">
<a-space>
{{ chooseBuildInfo.name }}
<a-button
@ -59,11 +61,11 @@
}
"
>
选择构建
{{ $tl('c.selectBuild') }}
</a-button>
</a-space>
</a-form-item>
<a-form-item label="选择产物">
<a-form-item :label="$tl('c.selectProduct')">
<a-space>
<a-tag v-if="chooseBuildInfo.buildNumberId">#{{ chooseBuildInfo.buildNumberId }}</a-tag>
<a-button
@ -75,14 +77,14 @@
}
"
>
选择产物
{{ $tl('c.selectProduct') }}
</a-button>
</a-space>
</a-form-item>
</template>
<!-- 文件中心 -->
<template v-else-if="temp.type === 'file-storage'">
<a-form-item label="选择文件">
<a-form-item :label="$tl('c.selectFile')">
<a-space>
{{ chooseFileInfo.name }}
<a-button
@ -93,14 +95,14 @@
}
"
>
选择文件
{{ $tl('c.selectFile') }}
</a-button>
</a-space>
</a-form-item>
</template>
<!-- 静态文件 -->
<template v-else-if="temp.type === 'static-file-storage'">
<a-form-item label="选择文件">
<a-form-item :label="$tl('c.selectFile')">
<a-space>
{{ chooseFileInfo.name }}
<a-button
@ -111,54 +113,62 @@
}
"
>
选择文件
{{ $tl('c.selectFile') }}
</a-button>
</a-space>
</a-form-item>
</template>
<a-form-item name="clearOld">
<template #label>
清空发布
{{ $tl('p.clearPublish') }}
<a-tooltip>
<template #title>
清空发布是指在上传新文件前,会将项目文件夹目录里面的所有文件先删除后再保存新文件
</template>
<template #title> {{ $tl('undefined') }},{{ $tl('undefined') }} </template>
<QuestionCircleOutlined />
</a-tooltip>
</template>
<a-switch v-model:checked="temp.clearOld" checked-children="" un-checked-children="" />
<a-switch
v-model:checked="temp.clearOld"
:checked-children="$tl('c.yes')"
:un-checked-children="$tl('c.no')"
/>
</a-form-item>
<a-form-item v-if="temp.type !== 'use-build'" name="unzip">
<template #label>
是否解压
{{ $tl('p.unZip') }}
<a-tooltip>
<template #title>
如果上传的压缩文件是否自动解压 支持的压缩包类型有 tar.bz2, tar.gz, tar, bz2, zip, gz
</template>
<template #title> {{ $tl('p.autoUnZip') }}.bz2, tar.gz, tar, bz2, zip, gz </template>
<QuestionCircleOutlined />
</a-tooltip>
</template>
<a-switch v-model:checked="temp.autoUnzip" checked-children="" un-checked-children="" />
<a-switch
v-model:checked="temp.autoUnzip"
:checked-children="$tl('c.yes')"
:un-checked-children="$tl('c.no')"
/>
</a-form-item>
<a-form-item v-if="temp.autoUnzip" label="剔除文件夹">
<a-form-item v-if="temp.autoUnzip" :label="$tl('p.excludeFolder')">
<a-input-number
v-model:value="temp.stripComponents"
style="width: 100%"
:min="0"
placeholder="解压时候自动剔除压缩包里面多余的文件夹名"
:placeholder="$tl('p.excludeFolderDescription')"
/>
</a-form-item>
<a-form-item label="分发后操作" name="afterOpt">
<a-select v-model:value="temp.afterOpt" placeholder="请选择发布后操作">
<a-form-item :label="$tl('p.postPublishAction')" name="afterOpt">
<a-select v-model:value="temp.afterOpt" :placeholder="$tl('c.selectPostPublishAction')">
<a-select-option v-for="item in afterOptList" :key="item.value">{{ item.title }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="secondaryDirectory" label="二级目录">
<a-input v-model:value="temp.secondaryDirectory" placeholder="不填写则发布至项目的根目录" />
<a-form-item name="secondaryDirectory" :label="$tl('p.subDirectory')">
<a-input v-model:value="temp.secondaryDirectory" :placeholder="$tl('p.subDirectoryDescription')" />
</a-form-item>
<a-form-item name="selectProject" label="筛选项目" help="筛选之后本次发布操作只发布筛选项,并且只对本次操作生效">
<a-select v-model:value="temp.selectProjectArray" mode="multiple" placeholder="请选择指定发布的项目">
<a-form-item name="selectProject" :label="$tl('p.filterProject')" :help="$tl('p.filterProjectDescription')">
<a-select
v-model:value="temp.selectProjectArray"
mode="multiple"
:placeholder="$tl('p.selectPublishProject')"
>
<a-select-option v-for="item in itemProjectList" :key="item.id" :value="`${item.projectId}@${item.nodeId}`">
{{ item.nodeName }}-{{ item.cacheProjectName || item.projectId }}
</a-select-option>
@ -169,7 +179,7 @@
<!-- 选择构建 -->
<a-drawer
destroy-on-close
:title="`选择构建`"
:title="`${$tl('c.selectBuild')}`"
placement="right"
:open="chooseVisible === 1"
width="80vw"
@ -211,7 +221,7 @@
}
"
>
取消
{{ $tl('c.cancel') }}
</a-button>
<a-button
type="primary"
@ -221,7 +231,7 @@
}
"
>
确认
{{ $tl('c.confirm') }}
</a-button>
</a-space>
</template>
@ -229,7 +239,7 @@
<!-- 选择构建产物 -->
<a-drawer
destroy-on-close
:title="`选择构建产物`"
:title="`${$tl('p.selectBuildProduct')}`"
placement="right"
:open="chooseVisible === 2"
width="80vw"
@ -272,7 +282,7 @@
}
"
>
取消
{{ $tl('c.cancel') }}
</a-button>
<a-button
type="primary"
@ -282,7 +292,7 @@
}
"
>
确认
{{ $tl('c.confirm') }}
</a-button>
</a-space>
</template>
@ -290,7 +300,7 @@
<!-- 选择文件 -->
<a-drawer
destroy-on-close
:title="`选择文件`"
:title="`${$tl('c.selectFile')}`"
placement="right"
:open="chooseVisible === 3"
width="80vw"
@ -329,7 +339,7 @@
}
"
>
取消
{{ $tl('c.cancel') }}
</a-button>
<a-button
type="primary"
@ -339,7 +349,7 @@
}
"
>
确认
{{ $tl('c.confirm') }}
</a-button>
</a-space>
</template>
@ -347,7 +357,7 @@
<!-- 选择静态文件 -->
<a-drawer
destroy-on-close
:title="`选择静态文件`"
:title="`${$tl('p.selectStaticFile')}`"
placement="right"
:open="chooseVisible === 4"
width="80vw"
@ -386,7 +396,7 @@
}
"
>
取消
{{ $tl('c.cancel') }}
</a-button>
<a-button
type="primary"
@ -396,7 +406,7 @@
}
"
>
确认
{{ $tl('c.confirm') }}
</a-button>
</a-space>
</template>
@ -448,8 +458,8 @@ export default {
itemProjectList: [],
fileList: [],
rules: {
afterOpt: [{ required: true, message: '请选择发布后操作', trigger: 'blur' }],
url: [{ required: true, message: '请输入远程地址', trigger: 'blur' }]
afterOpt: [{ required: true, message: this.$tl('c.selectPostPublishAction'), trigger: 'blur' }],
url: [{ required: true, message: this.$tl('p.pleaseInputRemoteAddress'), trigger: 'blur' }]
},
temp: { type: 'upload' },
chooseVisible: 0,
@ -518,6 +528,9 @@ export default {
// console.log(this.temp);
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.start.${key}`, ...args)
},
renderSize,
formatDuration,
//
@ -548,7 +561,7 @@ export default {
//
if (this.fileList.length === 0) {
$notification.error({
message: '请选择文件'
message: this.$tl('p.pleaseSelectFile')
})
return false
}
@ -635,7 +648,7 @@ export default {
} else if (this.temp.type == 'download') {
if (!this.temp.url) {
$notification.error({
message: '请填写远程URL'
message: this.$tl('p.pleaseFillRemoteURL')
})
return false
}
@ -658,7 +671,7 @@ export default {
//
if (!this.chooseBuildInfo || !this.chooseBuildInfo.id || !this.chooseBuildInfo.buildNumberId) {
$notification.error({
message: '请填写构建和产物'
message: this.$tl('p.pleaseFillBuildAndProduct')
})
return false
}
@ -685,7 +698,7 @@ export default {
//
if (!this.chooseFileInfo || !this.chooseFileInfo.id) {
$notification.error({
message: '请选择文件中心的文件'
message: this.$tl('p.pleaseSelectFileCenterFile')
})
return false
}
@ -711,7 +724,7 @@ export default {
//
if (!this.chooseFileInfo || !this.chooseFileInfo.id) {
$notification.error({
message: '请选择静态文件中的文件'
message: this.$tl('p.pleaseSelectStaticFileFile')
})
return false
}

View File

@ -2,7 +2,7 @@
<div>
<a-drawer
destroy-on-close
:title="`查看 ${name} 状态`"
:title="`${$tl('p.view')} ${name} ${$tl('c.status')}`"
placement="right"
width="85vw"
:open="true"
@ -13,7 +13,7 @@
"
>
<a-tabs v-model:activeKey="tabKey" tab-position="left">
<a-tab-pane key="1" tab="状态">
<a-tab-pane key="1" :tab="$tl('c.status')">
<!-- 嵌套表格 -->
<a-table
:loading="childLoading"
@ -30,22 +30,24 @@
<template #title>
<a-space>
<div>
当前状态
<a-tag v-if="data.status === 2" color="green">{{ statusMap[data.status] || '未知' }}</a-tag>
{{ $tl('p.currentStatus') }}
<a-tag v-if="data.status === 2" color="green">{{ statusMap[data.status] || $tl('c.unknown') }}</a-tag>
<a-tag v-else-if="data.status === 1 || data.status === 0" color="orange">{{
statusMap[data.status] || '未知'
statusMap[data.status] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else-if="data.status === 3 || data.status === 4" color="red">{{
statusMap[data.status] || '未知'
statusMap[data.status] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else>{{ statusMap[data.status] || '未知' }}</a-tag>
<a-tag v-else>{{ statusMap[data.status] || $tl('c.unknown') }}</a-tag>
</div>
<div>状态描述{{ data.statusMsg || '-' }}</div>
<a-button type="primary" size="small" :loading="childLoading" @click="loadData">刷新</a-button>
<div>{{ $tl('p.statusDescription') }}{{ data.statusMsg || '-' }}</div>
<a-button type="primary" size="small" :loading="childLoading" @click="loadData">{{
$tl('p.refresh')
}}</a-button>
<a-statistic-countdown
format=" s 秒"
title="刷新倒计时"
format=" {{$tl('p.seconds')}}"
:title="$tl('p.refreshCountdown')"
:value="countdownTime"
@finish="silenceLoadData"
/>
@ -63,7 +65,7 @@
<template v-else-if="column.dataIndex === 'projectName'">
<a-tooltip placement="topLeft" :title="text">
<template v-if="record.disabled">
<a-tooltip title="当前项目被禁用">
<a-tooltip :title="$tl('p.currentProjectDisabled')">
<EyeInvisibleOutlined />
</a-tooltip>
</template>
@ -71,14 +73,14 @@
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'outGivingStatus'">
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-if="text === 2" color="green">{{ dispatchStatusMap[text] || $tl('c.unknown') }}</a-tag>
<a-tag v-else-if="text === 1 || text === 0 || text === 5" color="orange">{{
dispatchStatusMap[text] || '未知'
dispatchStatusMap[text] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else-if="text === 3 || text === 4 || text === 6" color="red">{{
dispatchStatusMap[text] || '未知'
dispatchStatusMap[text] || $tl('c.unknown')
}}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || '未知' }}</a-tag>
<a-tag v-else>{{ dispatchStatusMap[text] || $tl('c.unknown') }}</a-tag>
</template>
<template v-else-if="column.dataIndex === 'outGivingResultMsg'">
<a-tooltip placement="topLeft" :title="readJsonStrField(record.outGivingResult, 'msg')">
@ -117,15 +119,15 @@
:checked="text"
:disabled="true"
size="small"
checked-children="运行中"
un-checked-children="未运行"
:checked-children="$tl('p.running')"
:un-checked-children="$tl('p.notRunning')"
/>
</template>
<template v-else-if="column.dataIndex === 'projectPid'">
<a-tooltip
placement="topLeft"
:title="`进程号:${record.projectPid || '-'} / 端口号:${record.projectPort || '-'}`"
:title="`${$tl('p.processId')}${record.projectPid || '-'} / ${$tl('p.portNumber')}${record.projectPort || '-'}`"
>
<span>{{ record.projectPid || '-' }}/{{ record.projectPort || '-' }}</span>
</a-tooltip>
@ -133,18 +135,22 @@
<template v-else-if="column.dataIndex === 'child-operation'">
<a-space>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleFile(record)"
>文件</a-button
>
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleConsole(record)"
>控制台</a-button
<a-button size="small" :disabled="!record.projectName" type="primary" @click="handleFile(record)">{{
$tl('p.file')
}}</a-button>
<a-button
size="small"
:disabled="!record.projectName"
type="primary"
@click="handleConsole(record)"
>{{ $tl('c.console') }}</a-button
>
</a-space>
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="2" tab="配置">
<a-tab-pane key="2" :tab="$tl('p.configuration')">
<!-- 配置分发 -->
<div style="width: 50vw">
<!-- list -->
@ -152,14 +158,14 @@
<Draggable v-for="(element, index) in list" :key="index">
<a-row class="item-row">
<a-col :span="18">
<span> 节点名 {{ element.nodeName }} </span>
<span> 项目名 {{ element.cacheProjectName }} </span>
<span> {{ $tl('p.nodeName') }} {{ element.nodeName }} </span>
<span> {{ $tl('p.projectName') }} {{ element.cacheProjectName }} </span>
</a-col>
<a-col :span="6">
<a-space>
<a-switch
checked-children="启用"
un-checked-children="禁用"
:checked-children="$tl('p.enable')"
:un-checked-children="$tl('p.disable')"
:checked="element.disabled ? false : true"
@change="
(checked) => {
@ -180,9 +186,9 @@
:disabled="!list || list.length <= 1"
@click="handleRemoveProject(element)"
>
解绑
{{ $tl('p.unbind') }}
</a-button>
<a-tooltip placement="left" :title="`长按可以拖动排序`" class="move">
<a-tooltip placement="left" :title="`${$tl('p.longPressToDragAndSort')}`" class="move">
<MenuOutlined />
</a-tooltip>
</a-space>
@ -192,7 +198,7 @@
</Container>
<a-col style="margin-top: 10px">
<a-space>
<a-button type="primary" size="small" @click="viewDispatchManagerOk">保存</a-button>
<a-button type="primary" size="small" @click="viewDispatchManagerOk">{{ $tl('p.save') }}</a-button>
</a-space>
</a-col>
</div>
@ -316,65 +322,65 @@ export default {
childColumns: [
{
title: '节点名称',
title: this.$tl('p.nodeNameLabel'),
dataIndex: 'nodeId',
width: 120,
ellipsis: true
},
{
title: '项目名称',
title: this.$tl('p.projectNameLabel'),
dataIndex: 'projectName',
width: 120,
ellipsis: true
},
{
title: '项目状态',
title: this.$tl('p.projectStatusLabel'),
dataIndex: 'projectStatus',
width: 120,
ellipsis: true
},
{
title: '进程/端口',
title: this.$tl('p.processPortLabel'),
dataIndex: 'projectPid',
width: '120px',
ellipsis: true
},
{
title: '分发状态',
title: this.$tl('p.distributionStatusLabel'),
dataIndex: 'outGivingStatus',
width: '120px'
},
{
title: '分发结果',
title: this.$tl('p.distributionResultLabel'),
dataIndex: 'outGivingResultMsg',
ellipsis: true,
width: 120
},
{
title: '分发状态消息',
title: this.$tl('p.distributionStatusMessageLabel'),
dataIndex: 'outGivingResultMsgData',
ellipsis: true,
width: 120
},
{
title: '分发耗时',
title: this.$tl('p.distributionDurationLabel'),
dataIndex: 'outGivingResultTime',
width: '120px'
},
{
title: '文件大小',
title: this.$tl('p.fileSizeLabel'),
dataIndex: 'outGivingResultSize',
width: '100px'
},
{
title: '最后分发时间',
title: this.$tl('p.lastDistributionTimeLabel'),
dataIndex: 'lastTime',
width: '170px',
ellipsis: true,
customRender: ({ text }) => parseTime(text)
},
{
title: '操作',
title: this.$tl('p.operationLabel'),
dataIndex: 'child-operation',
fixed: 'right',
@ -395,6 +401,9 @@ export default {
this.loadNodeList()
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.status.${key}`, ...args)
},
readJsonStrField,
renderSize,
formatDuration,
@ -540,7 +549,7 @@ export default {
...element,
projectStatus: false,
projectPid: '-',
errorMsg: '网络异常'
errorMsg: this.$tl('p.networkError')
}
}
return element
@ -555,7 +564,7 @@ export default {
//
handleFile(record) {
this.temp = Object.assign({}, record)
this.drawerTitle = `文件管理(${this.temp.projectId})`
this.drawerTitle = `${this.$tl('p.fileManagement')}(${this.temp.projectId})`
this.drawerFileVisible = true
},
//
@ -565,7 +574,7 @@ export default {
//
handleConsole(record) {
this.temp = Object.assign({}, record)
this.drawerTitle = `控制台(${this.temp.projectId})`
this.drawerTitle = `${this.$tl('c.console')}(${this.temp.projectId})`
this.drawerConsoleVisible = true
},
//
@ -590,7 +599,7 @@ export default {
this.onFileClose()
this.drawerReadFileVisible = true
this.temp.readFilePath = (path + '/' + filename).replace(new RegExp('//', 'gm'), '/')
this.drawerTitle = `跟踪文件(${filename})`
this.drawerTitle = `${this.$tl('p.trackFile')}(${filename})`
},
onReadFileClose() {
this.drawerReadFileVisible = false
@ -609,21 +618,24 @@ export default {
},
//
handleRemoveProject(item) {
const html =
"<b style='font-size: 20px;'>真的要释放(删除)当前项目么?</b>" +
"<ul style='font-size: 20px;color:red;font-weight: bold;'>" +
'<li>不会真实请求节点删除项目信息</b></li>' +
'<li>一般用于服务器无法连接且已经确定不再使用</li>' +
'<li>如果误操作会产生冗余数据!!!</li>' +
' </ul>'
const html = `
<b style='font-size: 20px;'>
${this.$tl('p.reallyReleaseCurrentProject')}
</b>
<ul style='font-size: 20px;color:red;font-weight: bold;'>
<li>this.$tl('p.willNotActuallyRequestNodeToDeleteProjectInfo')</b></li>
<li>this.$tl('p.generallyUsedWhenServerCannotBeConnectedAndIsNoLongerNeeded')</li>
<li>this.$tl('p.willProduceRedundantDataIfMisoperated')</li>
</ul>
`
$confirm({
title: '危险操作!!!',
title: this.$tl('p.dangerousOperation'),
zIndex: 1009,
content: h('div', null, [h('p', { innerHTML: html }, null)]),
okButtonProps: { type: 'primary', size: 'small', danger: true },
cancelButtonProps: { type: 'primary' },
okText: '确认',
cancelText: '取消',
okText: this.$tl('p.confirm'),
cancelText: this.$tl('p.cancel'),
onOk: () => {
return removeProject({
nodeId: item.nodeId,

View File

@ -1,46 +1,46 @@
<template>
<div>
<a-space direction="vertical" style="width: 100%">
<a-alert message="温馨提醒" type="info" show-icon>
<a-alert :message="$tl('p.warmReminder')" type="info" show-icon>
<template #description>
<ul>
<li>当前为节点分发的授权路径配置</li>
<li>路径需要配置绝对路径</li>
<li>{{ $tl('p.distributionAuthorizationPathConfig') }}</li>
<li>{{ $tl('p.absolutePathRequired') }}</li>
</ul>
</template>
</a-alert>
<!-- <a-alert message=",不支持软链" type="info" /> -->
<a-form ref="editForm" :model="temp" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }" @finish="onSubmit">
<a-form-item label="授权路径" name="outGiving">
<template #help>用于创建节点分发项目文件中心发布文件</template>
<a-form-item :label="$tl('p.authorizationPath')" name="outGiving">
<template #help>{{ $tl('p.usageForCreatingDistributionProjects') }}</template>
<a-textarea
v-model:value="temp.outGiving"
:rows="5"
style="resize: none"
placeholder="请输入授权路径,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径"
:placeholder="$tl('p.inputAuthorizationPath')"
/>
</a-form-item>
<a-form-item label="静态目录" name="staticDir">
<template #help>用于静态文件绑定和读取(不建议配置大目录避免扫描消耗过多资源)</template>
<a-form-item :label="$tl('p.staticDirectory')" name="staticDir">
<template #help>{{ $tl('p.usageForStaticFileBindingAndReading') }}</template>
<a-textarea
v-model:value="temp.staticDir"
:rows="5"
style="resize: none"
placeholder="请输入静态,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径"
:placeholder="$tl('p.inputStaticPaths')"
/>
</a-form-item>
<a-form-item label="远程下载安全HOST" name="allowRemoteDownloadHost">
<template #help>用于下载远程文件来进行节点分发和文件上传</template>
<a-form-item :label="$tl('p.remoteDownloadSecureHost')" name="allowRemoteDownloadHost">
<template #help>{{ $tl('p.usageForDownloadingRemoteFiles') }}</template>
<a-textarea
v-model:value="temp.allowRemoteDownloadHost"
:rows="5"
style="resize: none"
placeholder="请输入远程下载安全HOST回车支持输入多个路径示例 https://www.test.com 等"
placeholder="{{$tl('p.inputRemoteDownloadSecureHosts')}}://www.test.com 等"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 6 }">
<a-button type="primary" html-type="submit" :disabled="submitAble">提交</a-button>
<a-button type="primary" html-type="submit" :disabled="submitAble">{{ $tl('p.submit') }}</a-button>
</a-form-item>
</a-form>
</a-space>
@ -67,6 +67,9 @@ export default {
this.loadData()
},
methods: {
$tl(key, ...args) {
return this.$t(`pages.dispatch.whiteList.${key}`, ...args)
},
// load data
loadData() {
this.loading = true

View File

@ -35,7 +35,7 @@
:type="systemNotificationData.level || 'info'"
:closable="systemNotificationData.closable"
banner
:afterClose="notificationAfterClose"
:after-close="notificationAfterClose"
>
<template #message> <div v-html="systemNotificationData.title"></div> </template>
<template #description> <div v-html="systemNotificationData.content"></div> </template>
@ -58,7 +58,11 @@
>
<router-view v-slot="{ Component, route }">
<keep-alive :include="menuTabKeyList">
<component :is="wrap(String(route.name), Component)" :key="String(route.name)" />
<component
:is="wrap(String(route.name), Component)"
v-if="menuTabKeyList.length"
:key="String(route.name)"
/>
</keep-alive>
</router-view>
</a-layout-content>
@ -83,7 +87,7 @@ defineProps({
required: true
}
})
const useUserStore2 = userStore()
//
const wrapperMap = shallowRef(new Map())
// name
@ -113,6 +117,11 @@ const menuTabKeyList = computed(() => {
watch(
menuTabKeyList,
(newKeys, oldKeys) => {
if (!useUserStore2.getToken()) {
// tab
// v-if="menuTabKeyList.length"
// return
}
// key
oldKeys
?.filter((key) => {

View File

@ -498,11 +498,11 @@
</a-modal>
<!-- 选择证书文件 -->
<a-drawer
v-if="certificateVisible"
destroy-on-close
:title="`选择证书文件`"
placement="right"
:open="certificateVisible"
v-if="certificateVisible"
width="85vw"
:z-index="1009"
:footer-style="{ textAlign: 'right' }"

View File

@ -0,0 +1,135 @@
<template>
<div>
<a-row :gutter="[16, 0]">
<a-col :span="10">
<a-form :model="temp">
<a-form-item>
<code-editor
v-model:content="temp.content"
height="calc(100vh - 50px - 30px - 100px)"
:options="{ mode: 'shell', tabSize: 2 }"
:show-tool="true"
>
<template #tool_before>
<a-tooltip>
<template #title>自由脚本是指直接在机器节点中执行任意脚本</template>
帮助
<QuestionCircleOutlined />
</a-tooltip>
</template>
</code-editor>
</a-form-item>
<a-form-item label="执行路径" name="path">
<a-input v-model:value="temp.path" placeholder="执行脚本的路径" />
</a-form-item>
<!-- <a-form-item :wrapper-col="{ span: 14, offset: 2 }">
<a-space>
<a-button type="primary" danger :disabled="submitAble" @click="onSubmit(true)">保存并重启</a-button>
</a-space>
</a-form-item> -->
</a-form>
</a-col>
<a-col :span="14">
<log-view2 ref="logView" height="calc(100vh - 50px - 30px)">
<template #before>
<a-space>
<a-button
type="primary"
size="small"
:loading="loading"
:disabled="!temp.content"
@click="onSubmit(false)"
>执行</a-button
>
<a-switch
v-model:checked="temp.appendTemplate"
checked-children="追加脚本模板"
un-checked-children="不追加脚本模板"
/>
</a-space>
</template>
</log-view2>
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import codeEditor from '@/components/codeEditor'
import LogView2 from '@/components/logView/index2'
import { getWebSocketUrl } from '@/api/config'
const props = defineProps({
machineId: {
type: String,
required: true
}
})
const socket = ref(null)
const loading = ref(false)
const logView = ref()
const userStore_ = userStore()
const temp = ref({
appendTemplate: true,
content: ''
})
const socketUrl = computed(() => {
return getWebSocketUrl(
'/socket/free_script',
`userId=${userStore_.getLongTermToken()}&machineId=${props.machineId}&nodeId=system&type=freeScript`
)
})
const conentScript = () => {
socket.vlaue?.close()
socket.vlaue = null
const socket_ = new WebSocket(socketUrl.value)
logView.value.clearLogCache()
//
socket_.onopen = () => {
loading.value = true
socket_.send(JSON.stringify(temp.value))
}
socket_.onmessage = (msg) => {
logView.value.appendLine(msg.data)
}
socket_.onerror = (err) => {
console.error(err)
$notification.error({
message: 'web socket 错误,请检查是否开启 ws 代理'
})
}
socket_.onclose = (err) => {
//onclose
console.error(err)
loading.value = false
$message.warning('会话已经关闭[free-script] ')
// clearInterval(this.heart);
}
socket.value = socket_
}
// console.log(socketUrl)
// const socketUrl = com
const onSubmit = () => {
conentScript()
}
const close = () => {
socket.vlaue?.close()
socket.vlaue = null
}
onMounted(() => {
// websocketserver
window.onbeforeunload = () => {
close()
}
})
onBeforeUnmount(() => {
close()
})
</script>

View File

@ -28,6 +28,7 @@
<a-tab-pane key="info" tab="基本信息"></a-tab-pane>
<a-tab-pane key="cache" tab="缓存监控"></a-tab-pane>
<a-tab-pane key="config" tab="系统配置"></a-tab-pane>
<a-tab-pane key="freeScript" tab="自由脚本"></a-tab-pane>
<a-tab-pane key="path-config" tab="授权配置"></a-tab-pane>
<a-tab-pane key="upgrade" tab="在线升级"></a-tab-pane>
<a-tab-pane key="log" tab="系统日志"></a-tab-pane>
@ -43,6 +44,7 @@
<cache v-if="current === 'cache'" :machine-id="machineId" />
<log v-if="current === 'log'" :machine-id="machineId" />
<config-file v-if="current === 'config'" :machine-id="machineId" />
<freeScript v-if="current === 'freeScript'" :machine-id="machineId" />
</div>
</a-drawer>
</template>
@ -50,6 +52,7 @@
<script>
import { mapState } from 'pinia'
import machineInfo from './machine-info'
import freeScript from './free-script'
import upgrade from '@/components/upgrade'
import WhiteList from '@/pages/node/node-layout/system/white-list.vue'
import Cache from '@/pages/node/node-layout/system/cache'
@ -63,7 +66,8 @@ export default {
WhiteList,
Cache,
ConfigFile,
Log
Log,
freeScript
},
props: {
machineId: {

View File

@ -148,10 +148,48 @@
<!-- top 图表 -->
<div id="top-chart" class="chart">loading...</div>
</a-card>
<a-card size="small" title="网络流量信息">
<a-card size="small">
<template #title>
<a-space :size="4">
<template #split>
<a-divider type="vertical" />
</template>
网络流量信息
<template v-if="monitorConfig?.network?.statExcludeNames">
<span>
排除
<a-tag v-for="item in monitorConfig?.network?.statExcludeNames?.split(',')">
{{ item }}
</a-tag>
</span>
</template>
<template v-if="monitorConfig?.network?.statContainsOnlyNames">
<span>
仅统计
<a-tag v-for="item in monitorConfig?.network?.statContainsOnlyNames?.split(',')">
{{ item }}
</a-tag>
</span>
</template>
<a-popover>
<template #title>统计说明 </template>
<template #content>
<b>默认统计机器中除本地接口环回或无硬件地址网卡流量总和</b>
<div>
统计的网卡
<a-tag v-for="item in JSON.parse(machineInfo?.extendInfo || '{}')?.monitorIfsNames?.split(',')">
{{ item }}
</a-tag>
</div>
</template>
<QuestionCircleOutlined />
</a-popover>
</a-space>
</template>
<template #extra>
<a-button v-if="netHistoryChart" size="small" type="primary" @click="handleHistory('network-stat')">
<AreaChartOutlined />历史监控图表
<AreaChartOutlined />
历史监控图表
</a-button>
</template>
<!-- 网络流量图表 -->
@ -462,7 +500,7 @@ import {
machineNetworkInterfaces
} from '@/api/node-stat'
import { Empty } from 'ant-design-vue'
import { statusMap } from '@/api/system/assets-machine'
import { statusMap, machineMonitorConfig } from '@/api/system/assets-machine'
import { useGuideStore } from '@/stores/guide'
import { mapState } from 'pinia'
export default {
@ -736,7 +774,8 @@ export default {
countdownTime: Date.now(),
machineInfo: null,
networkInterfaces: [],
nodeMonitorLoadStatus: 0
nodeMonitorLoadStatus: 0,
monitorConfig: {}
}
},
computed: {
@ -814,6 +853,12 @@ export default {
//this.refreshInterval = this.getCacheNode("refreshInterval", this.refreshInterval);
//
this.pullNodeData()
//
machineMonitorConfig({
id: this.machineId
}).then((res) => {
this.monitorConfig = res.data || {}
})
},
pullNodeData() {
this.loadNodeTop()

View File

@ -37,7 +37,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="dingtalk.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="dingtalk.autoCreteUser">
<a-form-item v-if="dingtalk.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="dingtalk.permissionGroup"
@ -88,7 +88,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="feishu.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="feishu.autoCreteUser">
<a-form-item v-if="feishu.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="feishu.permissionGroup"
@ -163,7 +163,7 @@
un-checked-children="停用"
/>
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="wechat_enterprise.autoCreteUser">
<a-form-item v-if="wechat_enterprise.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="wechat_enterprise.permissionGroup"
@ -227,7 +227,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="maxkey.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="maxkey.autoCreteUser">
<a-form-item v-if="maxkey.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="maxkey.permissionGroup"
@ -279,7 +279,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="gitee.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="gitee.autoCreteUser">
<a-form-item v-if="gitee.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="gitee.permissionGroup"
@ -340,7 +340,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="mygitlab.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="mygitlab.autoCreteUser">
<a-form-item v-if="mygitlab.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="mygitlab.permissionGroup"
@ -394,7 +394,7 @@
<a-form-item label="自动创建用户" name="autoCreteUser">
<a-switch v-model:checked="github.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="权限组" name="permissionGroup" v-if="github.autoCreteUser">
<a-form-item v-if="github.autoCreteUser" label="权限组" name="permissionGroup">
<template #help>创建用户后自动关联上对应的权限组</template>
<a-select
v-model:value="github.permissionGroup"

View File

@ -12,8 +12,8 @@
:columns="taskColumns"
bordered
:data-source="taskList"
@refresh="refresh"
:pagination="false"
@refresh="refresh"
>
<!-- <template #title>
<a-button size="small" type="primary" @click="refresh"><ReloadOutlined /></a-button>

View File

@ -73,13 +73,13 @@
</a-form-item>
<template v-if="Object.keys(pingReulst).length">
<a-form-item label="结果" name="result">
<a-tag color="green" v-if="pingReulst.ping">成功</a-tag>
<a-tag color="red" v-else>失败</a-tag>
<a-tag v-if="pingReulst.ping" color="green">成功</a-tag>
<a-tag v-else color="red">失败</a-tag>
</a-form-item>
<a-form-item label="类型" name="labels">
<a-tag v-for="item in pingReulst.labels">{{ item }}</a-tag>
</a-form-item>
<a-form-item label="原始IP" name="originalIP" v-if="pingReulst.originalIP">
<a-form-item v-if="pingReulst.originalIP" label="原始IP" name="originalIP">
{{ pingReulst.originalIP }}
</a-form-item>
</template>
@ -141,13 +141,13 @@
</a-form-item>
<template v-if="Object.keys(telnetReulst).length">
<a-form-item label="结果" name="result">
<a-tag color="green" v-if="telnetReulst.open">成功</a-tag>
<a-tag color="red" v-else>失败</a-tag>
<a-tag v-if="telnetReulst.open" color="green">成功</a-tag>
<a-tag v-else color="red">失败</a-tag>
</a-form-item>
<a-form-item label="类型" name="labels">
<a-tag v-for="item in telnetReulst.labels">{{ item }}</a-tag>
</a-form-item>
<a-form-item label="原始IP" name="originalIP" v-if="telnetReulst.originalIP">
<a-form-item v-if="telnetReulst.originalIP" label="原始IP" name="originalIP">
{{ telnetReulst.originalIP }}
</a-form-item>
</template>