Compare commits

..

9 Commits

Author SHA1 Message Date
lijiahangmax
31df0c6cdf Merge pull request #11 from lijiahangmax/dev
Dev
2024-05-15 11:31:26 +08:00
lijiahangmax
81b7c7d592 📝 修改文档. 2024-05-15 00:21:16 +08:00
lijiahangmax
a6209751de 🚧 修改 exitCode. 2024-05-15 00:20:38 +08:00
lijiahang
4fe6208d0e 站内消息. 2024-05-14 19:17:12 +08:00
lijiahang
2fa3eb2251 🔨 优化批量执行日志跳转逻辑. 2024-05-14 16:03:28 +08:00
lijiahang
a0717c3338 站内消息. 2024-05-14 15:37:50 +08:00
lijiahang
e86bf3f19d 修改变量规范. 2024-05-14 11:34:51 +08:00
lijiahang
1ae47f8ab9 站内消息服务. 2024-05-14 11:32:17 +08:00
lijiahang
4b17d7b4ab 🔖 升级版本. 2024-05-13 12:22:26 +08:00
75 changed files with 2078 additions and 558 deletions

View File

@@ -26,7 +26,7 @@
</a> </a>
</p> </p>
当前版本: **1.0.7** 当前版本: **1.0.8**
**github:** https://github.com/lijiahangmax/orion-ops-pro **github:** https://github.com/lijiahangmax/orion-ops-pro
**gitee:** https://gitee.com/lijiahangmax/orion-ops-pro **gitee:** https://gitee.com/lijiahangmax/orion-ops-pro

View File

@@ -1,7 +1,7 @@
version: '3.3' version: '3.3'
services: services:
orion-ops-pro: orion-ops-pro:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.7 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro:1.0.8
ports: ports:
- 1081:80 - 1081:80
environment: environment:
@@ -19,7 +19,7 @@ services:
- orion-ops-pro-db - orion-ops-pro-db
- orion-ops-pro-redis - orion-ops-pro-redis
orion-ops-pro-db: orion-ops-pro-db:
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro-mysql:1.0.7 image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-ops-pro-mysql:1.0.8
privileged: true privileged: true
ports: ports:
- 3307:3306 - 3307:3306

View File

@@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
version=1.0.7 version=1.0.8
cp -r ../../sql ./sql cp -r ../../sql ./sql
docker build -t orion-ops-pro-mysql:${version} . docker build -t orion-ops-pro-mysql:${version} .
rm -rf ./sql rm -rf ./sql

View File

@@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
version=1.0.7 version=1.0.8
mv ../../orion-ops-launch/target/orion-ops-launch.jar ./orion-ops-launch.jar mv ../../orion-ops-launch/target/orion-ops-launch.jar ./orion-ops-launch.jar
mv ../../orion-ops-ui/dist ./dist mv ../../orion-ops-ui/dist ./dist
docker build -t orion-ops-pro:${version} . docker build -t orion-ops-pro:${version} .

View File

@@ -26,7 +26,7 @@
</a> </a>
</p> </p>
当前版本: **1.0.7** 当前版本: **1.0.8**
**github:** https://github.com/lijiahangmax/orion-ops-pro **github:** https://github.com/lijiahangmax/orion-ops-pro
**gitee:** https://gitee.com/lijiahangmax/orion-ops-pro **gitee:** https://gitee.com/lijiahangmax/orion-ops-pro

View File

@@ -1,4 +1,4 @@
# orion-ops-pro <small>1.0.7</small> # orion-ops-pro <small>1.0.8</small>
> 一款开箱即用的运维平台。 > 一款开箱即用的运维平台。

View File

@@ -7,6 +7,16 @@
* 执行完成菜单 sql 后请刷新缓存 `系统设置` > `系统菜单` > `刷新缓存` * 执行完成菜单 sql 后请刷新缓存 `系统设置` > `系统菜单` > `刷新缓存`
* 执行完成字典 sql 后请刷新缓存 `系统设置` > `数据字典项` > `刷新缓存` * 执行完成字典 sql 后请刷新缓存 `系统设置` > `数据字典项` > `刷新缓存`
### v1.0.8
`2024-05-15` `release`
* 🌈 新增 站内信模块
* 🔨 优化 执行命令日志跳转逻辑
* 🔨 修改 `exitStatus` 改为 `exitCode`
[如何升级](/update/v1.0.8.md)
### v1.0.7 ### v1.0.7
`2024-05-13` `release` `2024-05-13` `release`

View File

@@ -1,7 +1,5 @@
## 功能排期 ## 功能排期
* 批量上传
* 站内消息
* 终端背景图片 * 终端背景图片
* 资产授权 UI 改版 * 资产授权 UI 改版
* RDP 远程桌面 * RDP 远程桌面

View File

@@ -3,9 +3,53 @@
> sql 脚本 - DDL > sql 脚本 - DDL
```sql ```sql
-- 修改字段名称
ALTER TABLE `exec_host_log`
CHANGE COLUMN `exit_status` `exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码' AFTER `parameter`;
-- 系统消息
DROP TABLE IF EXISTS `system_message`;
CREATE TABLE `system_message`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`classify` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息分类',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息类型',
`status` tinyint(0) NULL DEFAULT NULL COMMENT '消息状态',
`rel_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息关联',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
`receiver_id` bigint(0) NULL DEFAULT NULL COMMENT '接收人id',
`receiver_username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接收人用户名',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_receiver_classify` (`receiver_id`, `classify`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '系统消息'
ROW_FORMAT = Dynamic;
``` ```
> sql 脚本 - DML > sql 脚本 - DML
```sql ```sql
-- 菜单
DELETE FROM system_menu WHERE id IN (161, 175, 197, 198) OR id > 202;
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 50, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-05-14 15:58:51', '1', '1', 0);
INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 30, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-14 15:58:44', '1', '1', 0);
INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 40, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-14 15:58:46', '1', '1', 0);
-- 字典项
DELETE FROM dict_key WHERE id >= 43;
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"STRING\"}, {\"name\": \"tagColor\", \"type\": \"STRING\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-05-14 14:48:28', '1', '1', 0);
INSERT INTO `dict_key` VALUES (44, 'messageClassify', 'STRING', '[]', '消息分类', '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
-- 字典值
DELETE FROM dict_value WHERE id >= 295;
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-05-14 15:19:19', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-05-14 15:11:21', '1', '1', 0);
INSERT INTO `dict_value` VALUES (297, 44, 'messageClassify', 'NOTICE', '通知', '{}', 10, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (298, 44, 'messageClassify', 'TODO', '待办', '{}', 20, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
``` ```

11
docs/update/v2.0.0.md Normal file
View File

@@ -0,0 +1,11 @@
## v2.0.0
> sql 脚本 - DDL
```sql
```
> sql 脚本 - DML
```sql
```

View File

@@ -14,7 +14,7 @@
<url>https://github.com/lijiahangmax/orion-ops-pro</url> <url>https://github.com/lijiahangmax/orion-ops-pro</url>
<properties> <properties>
<revision>1.0.7</revision> <revision>1.0.8</revision>
<spring.boot.version>2.7.17</spring.boot.version> <spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version> <spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version> <flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>

View File

@@ -14,7 +14,7 @@ public interface AppConst extends OrionConst {
/** /**
* 同 ${orion.version} 迭代时候需要手动更改 * 同 ${orion.version} 迭代时候需要手动更改
*/ */
String VERSION = "1.0.7"; String VERSION = "1.0.8";
String ORION_OPS_PRO = "orion-ops-pro"; String ORION_OPS_PRO = "orion-ops-pro";

View File

@@ -55,6 +55,10 @@ public interface FieldConst {
String COUNT = "count"; String COUNT = "count";
String DATE = "date";
String TIME = "time";
String LOCATION = "location"; String LOCATION = "location";
String USER_AGENT = "userAgent"; String USER_AGENT = "userAgent";

View File

@@ -26,7 +26,7 @@ public class CodeGenerators {
// 作者 // 作者
String author = Const.ORION_AUTHOR; String author = Const.ORION_AUTHOR;
// 模块 // 模块
String module = "asset"; String module = "infra";
// 生成的表 // 生成的表
Table[] tables = { Table[] tables = {
// Template.create("dict_key", "字典配置项", "dict") // Template.create("dict_key", "字典配置项", "dict")
@@ -49,24 +49,22 @@ public class CodeGenerators {
// .disableUnitTest() // .disableUnitTest()
// .vue("exec", "exec-template-host") // .vue("exec", "exec-template-host")
// .build(), // .build(),
Template.create("upload_task", "上传任务", "upload") Template.create("system_message", "系统消息", "message")
.disableUnitTest() .disableUnitTest()
.vue("exec", "upload-task") .enableProviderApi()
.enableRowSelection() .vue("system", "message")
.dict("uploadTaskStatus", "status") .dict("messageClassify", "classify", "messageClassify")
.comment("上传任务状态") .comment("消息分类")
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED") .fields("NOTICE", "TODO")
.labels("等待中", "上传中", "已完成", "已失败", "已取消") .labels("通知", "待办")
.valueUseFields() .valueUseFields()
.build(), .dict("messageType", "type", "messageType")
Template.create("upload_task_file", "上传任务文件", "upload") .comment("消息类型")
.disableUnitTest() .fields("EXEC_FAILED", "UPLOAD_FAILED")
.vue("exec", "upload-task-file") .labels("执行失败", "上传失败")
.enableRowSelection() .extra("tagLabel", "执行失败", "上传失败")
.dict("uploadTaskFileStatus", "status") .extra("tagVisible", true, true)
.comment("上传任务文件状态") .extra("tagColor", "red", "red")
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED")
.labels("等待中", "上传中", "已完成", "已失败", "已取消")
.valueUseFields() .valueUseFields()
.build(), .build(),
}; };

View File

@@ -14,6 +14,17 @@ import java.util.LinkedHashMap;
*/ */
public class DictTemplate extends Template { public class DictTemplate extends Template {
// // $comment
// export const $field = {
// // labels[0]
// fields[0]: 'values[0]',
// // labels[1]
// fields[0]: 'values[1]',
// };
//
// // $comment 字典项
// export const $keyField = '$keyName';
private final DictMeta dictMeta; private final DictMeta dictMeta;
public DictTemplate(Table table, String keyName, String variable) { public DictTemplate(Table table, String keyName, String variable) {

View File

@@ -0,0 +1,53 @@
package com.orion.ops.module.asset.define.message;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 命令执行 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum ExecMessageDefine implements SystemMessageDefine {
/**
* 命令执行部分失败
*/
EXEC_FAILED(MessageClassifyEnum.NOTICE,
"命令执行失败",
"您在 <sb>${time}</sb> 执行的命令部分失败, 或者返回了非零的 exitCode。点击查看详情 <sb>#${id}</sb> >>>"),
;
ExecMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -0,0 +1,53 @@
package com.orion.ops.module.asset.define.message;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import lombok.Getter;
/**
* 上传任务 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:23
*/
@Getter
public enum UploadMessageDefine implements SystemMessageDefine {
/**
* 上传任务部分失败
*/
UPLOAD_FAILED(MessageClassifyEnum.NOTICE,
"批量上传失败",
"您在 <sb>${time}</sb> 提交的上传任务中, 有部分主机文件上传失败。点击查看详情 <sb>#${id}</sb> >>>"),
;
UploadMessageDefine(MessageClassifyEnum classify, String title, String content) {
this.classify = classify;
this.type = this.name();
this.title = title;
this.content = content;
}
/**
* 消息分类
*/
private final MessageClassifyEnum classify;
/**
* 消息类型
*/
private final String type;
/**
* 标题
*/
private final String title;
/**
* 内容
*/
private final String content;
}

View File

@@ -61,8 +61,8 @@ public class ExecHostLogDO extends BaseDO {
private String parameter; private String parameter;
@Schema(description = "退出码") @Schema(description = "退出码")
@TableField("exit_status") @TableField("exit_code")
private Integer exitStatus; private Integer exitCode;
@Schema(description = "日志路径") @Schema(description = "日志路径")
@TableField("log_path") @TableField("log_path")

View File

@@ -50,7 +50,7 @@ public class ExecHostLogVO implements Serializable {
private String parameter; private String parameter;
@Schema(description = "退出码") @Schema(description = "退出码")
private Integer exitStatus; private Integer exitCode;
@Schema(description = "错误信息") @Schema(description = "错误信息")
private String errorMessage; private String errorMessage;

View File

@@ -22,7 +22,7 @@ import java.util.List;
@Schema(name = "ExecCommandDTO", description = "批量执行启动对象") @Schema(name = "ExecCommandDTO", description = "批量执行启动对象")
public class ExecCommandDTO { public class ExecCommandDTO {
@Schema(description = "hostId") @Schema(description = "logId")
private Long logId; private Long logId;
@Schema(description = "用户id") @Schema(description = "用户id")

View File

@@ -67,6 +67,9 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
private CommandExecutor executor; private CommandExecutor executor;
@Getter
private Integer exitCode;
private volatile boolean closed; private volatile boolean closed;
private volatile boolean interrupted; private volatile boolean interrupted;
@@ -227,7 +230,8 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
} else if (ExecHostStatusEnum.COMPLETED.equals(status)) { } else if (ExecHostStatusEnum.COMPLETED.equals(status)) {
// 完成 // 完成
updateRecord.setFinishTime(new Date()); updateRecord.setFinishTime(new Date());
updateRecord.setExitStatus(executor.getExitCode()); updateRecord.setExitCode(executor.getExitCode());
this.exitCode = executor.getExitCode();
} else if (ExecHostStatusEnum.FAILED.equals(status)) { } else if (ExecHostStatusEnum.FAILED.equals(status)) {
// 失败 // 失败
updateRecord.setFinishTime(new Date()); updateRecord.setFinishTime(new Date());

View File

@@ -149,17 +149,17 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
.append(Dates.current()) .append(Dates.current())
.newLine(); .newLine();
} else { } else {
long ms = this.updateRecord.getFinishTime().getTime() - this.updateRecord.getStartTime().getTime(); long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
Integer exitStatus = this.updateRecord.getExitStatus(); Integer exitCode = updateRecord.getExitCode();
// 执行完成 // 执行完成
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ") appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
.append(Dates.current()) .append(Dates.current())
.newLine() .newLine()
.append(AnsiForeground.BRIGHT_BLUE, "exit: "); .append(AnsiForeground.BRIGHT_BLUE, "exit: ");
if (ExitCode.isSuccess(exitStatus)) { if (ExitCode.isSuccess(exitCode)) {
appender.append(AnsiForeground.BRIGHT_GREEN, exitStatus); appender.append(AnsiForeground.BRIGHT_GREEN, exitCode);
} else { } else {
appender.append(AnsiForeground.BRIGHT_RED, exitStatus); appender.append(AnsiForeground.BRIGHT_RED, exitCode);
} }
appender.newLine() appender.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "used: ") .append(AnsiForeground.BRIGHT_BLUE, "used: ")

View File

@@ -7,20 +7,29 @@ import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.Threads; import com.orion.lang.utils.Threads;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.io.Streams; import com.orion.lang.utils.io.Streams;
import com.orion.lang.utils.time.Dates;
import com.orion.net.host.ssh.ExitCode;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.dao.ExecLogDAO; import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.define.AssetThreadPools; import com.orion.ops.module.asset.define.AssetThreadPools;
import com.orion.ops.module.asset.define.config.AppExecLogConfig; import com.orion.ops.module.asset.define.config.AppExecLogConfig;
import com.orion.ops.module.asset.define.message.ExecMessageDefine;
import com.orion.ops.module.asset.entity.domain.ExecLogDO; import com.orion.ops.module.asset.entity.domain.ExecLogDO;
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
import com.orion.ops.module.asset.enums.ExecStatusEnum; import com.orion.ops.module.asset.enums.ExecStatusEnum;
import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandDTO; import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandDTO;
import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandHostDTO; import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandHostDTO;
import com.orion.ops.module.asset.handler.host.exec.command.manager.ExecTaskManager; import com.orion.ops.module.asset.handler.host.exec.command.manager.ExecTaskManager;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.spring.SpringHolder; import com.orion.spring.SpringHolder;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 命令执行任务 * 命令执行任务
@@ -38,6 +47,8 @@ public class ExecTaskHandler implements IExecTaskHandler {
private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class); private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class);
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
private final ExecCommandDTO execCommand; private final ExecCommandDTO execCommand;
private TimeoutChecker<TimeoutEndpoint> timeoutChecker; private TimeoutChecker<TimeoutEndpoint> timeoutChecker;
@@ -45,6 +56,8 @@ public class ExecTaskHandler implements IExecTaskHandler {
@Getter @Getter
private final List<IExecCommandHandler> handlers; private final List<IExecCommandHandler> handlers;
private Date startTime;
public ExecTaskHandler(ExecCommandDTO execCommand) { public ExecTaskHandler(ExecCommandDTO execCommand) {
this.execCommand = execCommand; this.execCommand = execCommand;
this.handlers = Lists.newList(); this.handlers = Lists.newList();
@@ -56,9 +69,9 @@ public class ExecTaskHandler implements IExecTaskHandler {
// 添加任务 // 添加任务
execTaskManager.addTask(id, this); execTaskManager.addTask(id, this);
log.info("ExecTaskHandler.run start id: {}", id); log.info("ExecTaskHandler.run start id: {}", id);
// 更新状态
this.updateStatus(ExecStatusEnum.RUNNING);
try { try {
// 更新状态
this.updateStatus(ExecStatusEnum.RUNNING);
// 执行命令 // 执行命令
this.runHostCommand(); this.runHostCommand();
// 更新状态-执行完成 // 更新状态-执行完成
@@ -69,10 +82,12 @@ public class ExecTaskHandler implements IExecTaskHandler {
this.updateStatus(ExecStatusEnum.FAILED); this.updateStatus(ExecStatusEnum.FAILED);
log.error("ExecTaskHandler.run error id: {}", id, e); log.error("ExecTaskHandler.run error id: {}", id, e);
} finally { } finally {
// 释放资源 // 检查是否发送消息
Streams.close(this); this.checkSendMessage();
// 移除任务 // 移除任务
execTaskManager.removeTask(id); execTaskManager.removeTask(id);
// 释放资源
this.close();
} }
} }
@@ -82,6 +97,13 @@ public class ExecTaskHandler implements IExecTaskHandler {
handlers.forEach(IExecCommandHandler::interrupt); handlers.forEach(IExecCommandHandler::interrupt);
} }
@Override
public void close() {
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
Streams.close(timeoutChecker);
this.handlers.forEach(Streams::close);
}
/** /**
* 执行主机命令 * 执行主机命令
* *
@@ -139,6 +161,7 @@ public class ExecTaskHandler implements IExecTaskHandler {
update.setStatus(statusName); update.setStatus(statusName);
if (ExecStatusEnum.RUNNING.equals(status)) { if (ExecStatusEnum.RUNNING.equals(status)) {
// 执行中 // 执行中
this.startTime = new Date();
update.setStartTime(new Date()); update.setStartTime(new Date());
} else if (ExecStatusEnum.COMPLETED.equals(status)) { } else if (ExecStatusEnum.COMPLETED.equals(status)) {
// 执行完成 // 执行完成
@@ -151,11 +174,30 @@ public class ExecTaskHandler implements IExecTaskHandler {
log.info("ExecTaskHandler-updateStatus finish id: {}, effect: {}", id, effect); log.info("ExecTaskHandler-updateStatus finish id: {}, effect: {}", id, effect);
} }
@Override /**
public void close() { * 检查是否发送消息
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId()); */
Streams.close(timeoutChecker); private void checkSendMessage() {
this.handlers.forEach(Streams::close); // 检查是否执行失败/exitCode
boolean hasError = handlers.stream().anyMatch(s ->
ExecHostStatusEnum.FAILED.equals(s.getStatus())
|| ExecHostStatusEnum.TIMEOUT.equals(s.getStatus())
|| !ExitCode.isSuccess(s.getExitCode()));
if (!hasError) {
return;
}
// 参数
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ID, execCommand.getLogId());
params.put(ExtraFieldConst.TIME, Dates.format(this.startTime, Dates.MD_HM));
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(execCommand.getUserId())
.receiverUsername(execCommand.getUsername())
.relKey(String.valueOf(execCommand.getLogId()))
.params(params)
.build();
// 发送
systemMessageApi.create(ExecMessageDefine.EXEC_FAILED, message);
} }
} }

View File

@@ -31,6 +31,13 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable {
*/ */
ExecHostStatusEnum getStatus(); ExecHostStatusEnum getStatus();
/**
* 获取退出码
*
* @return exit code
*/
Integer getExitCode();
/** /**
* 获取主机 id * 获取主机 id
* *

View File

@@ -3,10 +3,13 @@ package com.orion.ops.module.asset.handler.host.upload.task;
import com.orion.lang.utils.Threads; import com.orion.lang.utils.Threads;
import com.orion.lang.utils.io.Files1; import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.io.Streams; import com.orion.lang.utils.io.Streams;
import com.orion.lang.utils.time.Dates;
import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.dao.UploadTaskDAO; import com.orion.ops.module.asset.dao.UploadTaskDAO;
import com.orion.ops.module.asset.dao.UploadTaskFileDAO; import com.orion.ops.module.asset.dao.UploadTaskFileDAO;
import com.orion.ops.module.asset.define.AssetThreadPools; import com.orion.ops.module.asset.define.AssetThreadPools;
import com.orion.ops.module.asset.define.message.UploadMessageDefine;
import com.orion.ops.module.asset.entity.domain.UploadTaskDO; import com.orion.ops.module.asset.entity.domain.UploadTaskDO;
import com.orion.ops.module.asset.entity.domain.UploadTaskFileDO; import com.orion.ops.module.asset.entity.domain.UploadTaskFileDO;
import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum; import com.orion.ops.module.asset.enums.UploadTaskFileStatusEnum;
@@ -16,14 +19,13 @@ import com.orion.ops.module.asset.handler.host.upload.manager.FileUploadTaskMana
import com.orion.ops.module.asset.handler.host.upload.uploader.FileUploader; import com.orion.ops.module.asset.handler.host.upload.uploader.FileUploader;
import com.orion.ops.module.asset.handler.host.upload.uploader.IFileUploader; import com.orion.ops.module.asset.handler.host.upload.uploader.IFileUploader;
import com.orion.ops.module.asset.service.UploadTaskService; import com.orion.ops.module.asset.service.UploadTaskService;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.spring.SpringHolder; import com.orion.spring.SpringHolder;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -42,6 +44,8 @@ public class FileUploadTask implements IFileUploadTask {
private static final UploadTaskService uploadTaskService = SpringHolder.getBean(UploadTaskService.class); private static final UploadTaskService uploadTaskService = SpringHolder.getBean(UploadTaskService.class);
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
private static final FileUploadTaskManager fileUploadTaskManager = SpringHolder.getBean(FileUploadTaskManager.class); private static final FileUploadTaskManager fileUploadTaskManager = SpringHolder.getBean(FileUploadTaskManager.class);
private final Long id; private final Long id;
@@ -91,6 +95,8 @@ public class FileUploadTask implements IFileUploadTask {
} else { } else {
this.updateStatus(UploadTaskStatusEnum.FINISHED); this.updateStatus(UploadTaskStatusEnum.FINISHED);
} }
// 检查是否发送消息
this.checkSendMessage();
// 移除任务 // 移除任务
fileUploadTaskManager.removeTask(id); fileUploadTaskManager.removeTask(id);
// 释放资源 // 释放资源
@@ -187,4 +193,33 @@ public class FileUploadTask implements IFileUploadTask {
uploadTaskDAO.updateById(update); uploadTaskDAO.updateById(update);
} }
/**
* 检查是否发送消息
*/
private void checkSendMessage() {
if (canceled) {
return;
}
// 检查是否上传失败
boolean hasError = uploaderList.stream()
.map(IFileUploader::getFiles)
.flatMap(Collection::stream)
.anyMatch(s -> UploadTaskFileStatusEnum.FAILED.name().equals(s.getStatus()));
if (!hasError) {
return;
}
// 参数
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ID, record.getId());
params.put(ExtraFieldConst.TIME, Dates.format(record.getCreateTime(), Dates.MD_HM));
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(record.getUserId())
.receiverUsername(record.getUsername())
.relKey(String.valueOf(record.getId()))
.params(params)
.build();
// 发送
systemMessageApi.create(UploadMessageDefine.UPLOAD_FAILED, message);
}
} }

View File

@@ -156,7 +156,7 @@ public class ExecLogServiceImpl implements ExecLogService {
ExecHostLogDO::getStatus, ExecHostLogDO::getStatus,
ExecHostLogDO::getStartTime, ExecHostLogDO::getStartTime,
ExecHostLogDO::getFinishTime, ExecHostLogDO::getFinishTime,
ExecHostLogDO::getExitStatus, ExecHostLogDO::getExitCode,
ExecHostLogDO::getErrorMessage) ExecHostLogDO::getErrorMessage)
.in(ExecHostLogDO::getLogId, idList) .in(ExecHostLogDO::getLogId, idList)
.then() .then()

View File

@@ -12,7 +12,7 @@
<result column="status" property="status"/> <result column="status" property="status"/>
<result column="command" property="command"/> <result column="command" property="command"/>
<result column="parameter" property="parameter"/> <result column="parameter" property="parameter"/>
<result column="exit_status" property="exitStatus"/> <result column="exit_code" property="exitCode"/>
<result column="log_path" property="logPath"/> <result column="log_path" property="logPath"/>
<result column="script_path" property="scriptPath"/> <result column="script_path" property="scriptPath"/>
<result column="error_message" property="errorMessage"/> <result column="error_message" property="errorMessage"/>
@@ -27,7 +27,7 @@
<!-- 通用查询结果列 --> <!-- 通用查询结果列 -->
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, log_id, host_id, host_name, host_address, status, command, parameter, exit_status, log_path, script_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted id, log_id, host_id, host_name, host_address, status, command, parameter, exit_code, log_path, script_path, error_message, start_time, finish_time, create_time, update_time, creator, updater, deleted
</sql> </sql>
</mapper> </mapper>

View File

@@ -0,0 +1,35 @@
package com.orion.ops.module.infra.api;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
/**
* 系统消息 对外服务类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
public interface SystemMessageApi {
/**
* 创建系统消息
*
* @param define define
* @param dto dto
* @return id
*/
Long create(SystemMessageDefine define, SystemMessageDTO dto);
/**
* 创建系统消息
*
* @param classify classify
* @param dto dto
* @return id
*/
Long create(MessageClassifyEnum classify, SystemMessageCreateDTO dto);
}

View File

@@ -0,0 +1,47 @@
package com.orion.ops.module.infra.define;
import com.orion.lang.utils.Strings;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import java.util.Map;
/**
* 系统消息定义
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/5/14 17:06
*/
public interface SystemMessageDefine {
/**
* @return 消息分类
*/
MessageClassifyEnum getClassify();
/**
* @return 消息类型
*/
String getType();
/**
* @return 标题
*/
String getTitle();
/**
* @return 内容
*/
String getContent();
/**
* 格式化内容
*
* @param params params
* @return content
*/
default String formatContent(Map<String, Object> params) {
return Strings.format(this.getContent(), params);
}
}

View File

@@ -0,0 +1,61 @@
package com.orion.ops.module.infra.entity.dto.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 系统消息 创建请求业务对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCreateDTO", description = "系统消息 创建请求业务对象")
public class SystemMessageCreateDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 10)
@Schema(description = "消息分类")
private String classify;
@NotBlank
@Size(max = 32)
@Schema(description = "消息类型")
private String type;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotBlank
@Size(max = 128)
@Schema(description = "标题")
private String title;
@NotBlank
@Schema(description = "消息内容")
private String content;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Schema(description = "接收人用户名")
private String receiverUsername;
}

View File

@@ -0,0 +1,46 @@
package com.orion.ops.module.infra.entity.dto.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Map;
/**
* 系统消息 请求业务对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageDTO", description = "系统消息 请求业务对象")
public class SystemMessageDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Schema(description = "接收人用户名")
private String receiverUsername;
@Schema(description = "参数")
private Map<String, Object> params;
}

View File

@@ -0,0 +1,24 @@
package com.orion.ops.module.infra.enums;
/**
* 消息分类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 16:38
*/
public enum MessageClassifyEnum {
/**
* 通知
*/
NOTICE,
/**
* 待办
*/
TODO,
;
}

View File

@@ -0,0 +1,58 @@
package com.orion.ops.module.infra.api.impl;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.infra.api.SystemMessageApi;
import com.orion.ops.module.infra.convert.SystemMessageProviderConvert;
import com.orion.ops.module.infra.define.SystemMessageDefine;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.enums.MessageClassifyEnum;
import com.orion.ops.module.infra.service.SystemMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 系统消息 对外服务实现类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Slf4j
@Service
public class SystemMessageApiImpl implements SystemMessageApi {
@Resource
private SystemMessageService systemMessageService;
@Override
public Long create(SystemMessageDefine define, SystemMessageDTO dto) {
Valid.valid(dto);
// 转换
SystemMessageCreateRequest request = SystemMessageCreateRequest.builder()
.classify(define.getClassify().name())
.type(define.getType())
.title(define.getTitle())
.content(define.formatContent(dto.getParams()))
.relKey(dto.getRelKey())
.receiverId(dto.getReceiverId())
.receiverUsername(dto.getReceiverUsername())
.build();
// 创建
return systemMessageService.createSystemMessage(request);
}
@Override
public Long create(MessageClassifyEnum classify, SystemMessageCreateDTO dto) {
dto.setClassify(classify.name());
Valid.valid(dto);
// 转换
SystemMessageCreateRequest request = SystemMessageProviderConvert.MAPPER.toRequest(dto);
// 创建
return systemMessageService.createSystemMessage(request);
}
}

View File

@@ -0,0 +1,39 @@
### 查询系统消息列表
POST {{baseUrl}}/infra/system-message/list
Content-Type: application/json
Authorization: {{token}}
{
"limit": 10,
"maxId": null,
"classify": "NOTICE",
"queryCount": true,
"queryUnread": true
}
### 查询是否有未读消息
GET {{baseUrl}}/infra/system-message/has-unread
Authorization: {{token}}
### 查询是否有未读消息
PUT {{baseUrl}}/infra/system-message/read?id=1
Authorization: {{token}}
### 更新全部系统消息为已读
PUT {{baseUrl}}/infra/system-message/read-all?classify=NOTICE
Authorization: {{token}}
### 删除系统消息
DELETE {{baseUrl}}/infra/system-message/delete?id=1
Authorization: {{token}}
### 清理已读的系统消息
DELETE {{baseUrl}}/infra/system-message/clear?classify=NOTICE
Authorization: {{token}}
###

View File

@@ -0,0 +1,90 @@
package com.orion.ops.module.infra.controller;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import com.orion.ops.module.infra.service.SystemMessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 系统消息 api
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Tag(name = "infra - 系统消息服务")
@Slf4j
@Validated
@RestWrapper
@RestController
@RequestMapping("/infra/system-message")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class SystemMessageController {
@Resource
private SystemMessageService systemMessageService;
@IgnoreLog(IgnoreLogMode.ALL)
@PostMapping("/list")
@Operation(summary = "查询系统消息列表")
public List<SystemMessageVO> getSystemMessageList(@RequestBody SystemMessageQueryRequest request) {
return systemMessageService.getSystemMessageList(request);
}
@IgnoreLog(IgnoreLogMode.ALL)
@GetMapping("/count")
@Operation(summary = "查询系统消息数量")
@Parameter(name = "queryUnread", description = "queryUnread", required = true)
public Map<String, Integer> getSystemMessageCount(@RequestParam("queryUnread") Boolean queryUnread) {
return systemMessageService.getSystemMessageCount(queryUnread);
}
@IgnoreLog(IgnoreLogMode.ALL)
@GetMapping("/has-unread")
@Operation(summary = "查询是否有未读消息")
public Boolean checkHasUnreadMessage() {
return systemMessageService.checkHasUnreadMessage();
}
@PutMapping("/read")
@Operation(summary = "更新系统消息为已读")
@Parameter(name = "id", description = "id", required = true)
public Integer readSystemMessage(@RequestParam("id") Long id) {
return systemMessageService.readSystemMessage(id);
}
@PutMapping("/read-all")
@Operation(summary = "更新全部系统消息为已读")
@Parameter(name = "classify", description = "classify", required = true)
public Integer readAllSystemMessage(@RequestParam("classify") String classify) {
return systemMessageService.readAllSystemMessage(classify);
}
@DeleteMapping("/delete")
@Operation(summary = "删除系统消息")
@Parameter(name = "id", description = "id", required = true)
public Integer deleteSystemMessage(@RequestParam("id") Long id) {
return systemMessageService.deleteSystemMessageById(id);
}
@DeleteMapping("/clear")
@Operation(summary = "清理已读的系统消息")
@Parameter(name = "classify", description = "classify", required = true)
public Integer clearSystemMessage(@RequestParam("classify") String classify) {
return systemMessageService.clearSystemMessage(classify);
}
}

View File

@@ -0,0 +1,32 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 系统消息 内部对象转换器
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageConvert {
SystemMessageConvert MAPPER = Mappers.getMapper(SystemMessageConvert.class);
SystemMessageDO to(SystemMessageCreateRequest request);
SystemMessageDO to(SystemMessageQueryRequest request);
SystemMessageVO to(SystemMessageDO domain);
List<SystemMessageVO> to(List<SystemMessageDO> list);
}

View File

@@ -0,0 +1,22 @@
package com.orion.ops.module.infra.convert;
import com.orion.ops.module.infra.entity.dto.message.SystemMessageCreateDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 系统消息 对外服务对象转换器
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageProviderConvert {
SystemMessageProviderConvert MAPPER = Mappers.getMapper(SystemMessageProviderConvert.class);
SystemMessageCreateRequest toRequest(SystemMessageCreateDTO request);
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.dao;
import com.orion.ops.framework.mybatis.core.mapper.IMapper;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 系统消息 Mapper 接口
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Mapper
public interface SystemMessageDAO extends IMapper<SystemMessageDO> {
/**
* 查询消息数量
*
* @param receiverId receiverId
* @param status status
* @return count
*/
List<SystemMessageCountDTO> selectSystemMessageCount(@Param("receiverId") Long receiverId,
@Param("status") Integer status);
}

View File

@@ -0,0 +1,73 @@
package com.orion.ops.module.infra.entity.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.orion.ops.framework.mybatis.core.domain.BaseDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
/**
* 系统消息 实体对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName(value = "system_message", autoResultMap = true)
@Schema(name = "SystemMessageDO", description = "系统消息 实体对象")
public class SystemMessageDO extends BaseDO {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "消息分类")
@TableField("classify")
private String classify;
@Schema(description = "消息类型")
@TableField("type")
private String type;
@Schema(description = "消息状态")
@TableField("status")
private Integer status;
@Schema(description = "消息关联")
@TableField("rel_key")
private String relKey;
@Schema(description = "标题")
@TableField("title")
private String title;
@Schema(description = "消息内容")
@TableField("content")
private String content;
@Schema(description = "接收人id")
@TableField("receiver_id")
private Long receiverId;
@Schema(description = "接收人用户名")
@TableField("receiver_username")
private String receiverUsername;
@Schema(description = "创建人")
@TableField(exist = false)
private String creator;
@Schema(description = "修改人")
@TableField(exist = false)
private String updater;
}

View File

@@ -0,0 +1,33 @@
package com.orion.ops.module.infra.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 系统消息数量对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 18:15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCountDTO", description = "系统消息数量对象")
public class SystemMessageCountDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "数量")
private Integer count;
}

View File

@@ -0,0 +1,62 @@
package com.orion.ops.module.infra.entity.request.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* 系统消息 创建请求对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageCreateRequest", description = "系统消息 创建请求对象")
public class SystemMessageCreateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank
@Size(max = 10)
@Schema(description = "消息分类")
private String classify;
@NotBlank
@Size(max = 32)
@Schema(description = "消息类型")
private String type;
@NotBlank
@Size(max = 64)
@Schema(description = "消息关联")
private String relKey;
@NotBlank
@Size(max = 128)
@Schema(description = "标题")
private String title;
@NotBlank
@Schema(description = "消息内容")
private String content;
@NotNull
@Schema(description = "接收人id")
private Long receiverId;
@Size(max = 32)
@Schema(description = "接收人用户名")
private String receiverUsername;
}

View File

@@ -0,0 +1,35 @@
package com.orion.ops.module.infra.entity.request.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 系统消息 查询请求对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageQueryRequest", description = "系统消息 查询请求对象")
public class SystemMessageQueryRequest {
@Schema(description = "大小")
private Integer limit;
@Schema(description = "maxId")
private Long maxId;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "是否查询未读消息")
private Boolean queryUnread;
}

View File

@@ -0,0 +1,52 @@
package com.orion.ops.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 系统消息 视图响应对象
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "SystemMessageVO", description = "系统消息 视图响应对象")
public class SystemMessageVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "消息分类")
private String classify;
@Schema(description = "消息类型")
private String type;
@Schema(description = "消息状态")
private Integer status;
@Schema(description = "消息关联")
private String relKey;
@Schema(description = "标题")
private String title;
@Schema(description = "消息内容")
private String content;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.infra.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 消息状态
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024/5/11 16:35
*/
@Getter
@AllArgsConstructor
public enum MessageStatusEnum {
/**
* 未读
*/
UNREAD(0),
/**
* 已读
*/
READ(1),
;
private final Integer status;
}

View File

@@ -0,0 +1,82 @@
package com.orion.ops.module.infra.service;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import java.util.List;
import java.util.Map;
/**
* 系统消息 服务类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
public interface SystemMessageService {
/**
* 创建系统消息
*
* @param request request
* @return id
*/
Long createSystemMessage(SystemMessageCreateRequest request);
/**
* 查询系统消息列表
*
* @param request request
* @return rows
*/
List<SystemMessageVO> getSystemMessageList(SystemMessageQueryRequest request);
/**
* 查询系统消息数量
*
* @param queryUnread queryUnread
* @return rows
*/
Map<String, Integer> getSystemMessageCount(Boolean queryUnread);
/**
* 查询是否有未读消息
*
* @return has
*/
Boolean checkHasUnreadMessage();
/**
* 更新系统消息为已读
*
* @param id id
* @return effect
*/
Integer readSystemMessage(Long id);
/**
* 更新全部系统消息为已读
*
* @param classify classify
* @return effect
*/
Integer readAllSystemMessage(String classify);
/**
* 删除系统消息
*
* @param id id
* @return effect
*/
Integer deleteSystemMessageById(Long id);
/**
* 清理已读的系统消息
*
* @param classify classify
* @return effect
*/
Integer clearSystemMessage(String classify);
}

View File

@@ -0,0 +1,167 @@
package com.orion.ops.module.infra.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.function.Functions;
import com.orion.lang.utils.Booleans;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.infra.convert.SystemMessageConvert;
import com.orion.ops.module.infra.dao.SystemMessageDAO;
import com.orion.ops.module.infra.dao.SystemUserDAO;
import com.orion.ops.module.infra.entity.domain.SystemMessageDO;
import com.orion.ops.module.infra.entity.domain.SystemUserDO;
import com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO;
import com.orion.ops.module.infra.entity.request.message.SystemMessageCreateRequest;
import com.orion.ops.module.infra.entity.request.message.SystemMessageQueryRequest;
import com.orion.ops.module.infra.entity.vo.SystemMessageVO;
import com.orion.ops.module.infra.enums.MessageStatusEnum;
import com.orion.ops.module.infra.service.SystemMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 系统消息 服务实现类
*
* @author Jiahang Li
* @version 1.0.8
* @since 2024-5-11 16:29
*/
@Slf4j
@Service
public class SystemMessageServiceImpl implements SystemMessageService {
@Resource
private SystemMessageDAO systemMessageDAO;
@Resource
private SystemUserDAO systemUserDAO;
@Override
public Long createSystemMessage(SystemMessageCreateRequest request) {
log.info("SystemMessageService.createSystemMessage request: {}", JSON.toJSONString(request));
// 设置接收人用户名
if (request.getReceiverUsername() == null) {
Optional.ofNullable(request.getReceiverId())
.map(systemUserDAO::selectById)
.map(SystemUserDO::getUsername)
.ifPresent(request::setReceiverUsername);
}
// 转换
SystemMessageDO record = SystemMessageConvert.MAPPER.to(request);
record.setStatus(MessageStatusEnum.UNREAD.getStatus());
// 插入
int effect = systemMessageDAO.insert(record);
Long id = record.getId();
log.info("SystemMessageService-createSystemMessage id: {}, effect: {}", id, effect);
return id;
}
@Override
public List<SystemMessageVO> getSystemMessageList(SystemMessageQueryRequest request) {
Long userId = SecurityUtils.getLoginUserId();
Integer status = Booleans.isTrue(request.getQueryUnread()) ? MessageStatusEnum.UNREAD.getStatus() : null;
// 查询列表
return systemMessageDAO.of()
.createValidateWrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, request.getClassify())
.lt(SystemMessageDO::getId, request.getMaxId())
.eq(SystemMessageDO::getStatus, status)
.last(Const.LIMIT + Const.SPACE + request.getLimit())
.orderByDesc(SystemMessageDO::getId)
.then()
.list(SystemMessageConvert.MAPPER::to);
}
@Override
public Map<String, Integer> getSystemMessageCount(Boolean queryUnread) {
Long userId = SecurityUtils.getLoginUserId();
Integer status = queryUnread ? MessageStatusEnum.UNREAD.getStatus() : null;
// 查询数量
List<SystemMessageCountDTO> countList = systemMessageDAO.selectSystemMessageCount(userId, status);
// 返回
return countList.stream()
.collect(Collectors.toMap(SystemMessageCountDTO::getClassify,
SystemMessageCountDTO::getCount,
Functions.right()));
}
@Override
public Boolean checkHasUnreadMessage() {
// 查询
return systemMessageDAO.of()
.createWrapper()
.select(SystemMessageDO::getId)
.eq(SystemMessageDO::getReceiverId, SecurityUtils.getLoginUserId())
.eq(SystemMessageDO::getStatus, MessageStatusEnum.UNREAD.getStatus())
.then()
.only()
.optional()
.isPresent();
}
@Override
public Integer readSystemMessage(Long id) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-readSystemMessage id: {}, userId: {}", id, userId);
// 修改状态
SystemMessageDO update = new SystemMessageDO();
update.setStatus(MessageStatusEnum.READ.getStatus());
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getId, id)
.eq(SystemMessageDO::getReceiverId, userId);
int effect = systemMessageDAO.update(update, wrapper);
log.info("SystemMessageService-readSystemMessage id: {}, effect: {}", id, effect);
return effect;
}
@Override
public Integer readAllSystemMessage(String classify) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-readAllSystemMessage classify: {}, userId: {}", classify, userId);
// 修改状态
SystemMessageDO update = new SystemMessageDO();
update.setStatus(MessageStatusEnum.READ.getStatus());
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, classify)
.eq(SystemMessageDO::getStatus, MessageStatusEnum.UNREAD.getStatus());
int effect = systemMessageDAO.update(update, wrapper);
log.info("SystemMessageService-readAllSystemMessage classify: {}, effect: {}", classify, effect);
return effect;
}
@Override
public Integer deleteSystemMessageById(Long id) {
log.info("SystemMessageService-deleteSystemMessageById id: {}", id);
// 删除
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getId, id)
.eq(SystemMessageDO::getReceiverId, SecurityUtils.getLoginUserId());
int effect = systemMessageDAO.delete(wrapper);
log.info("SystemMessageService-deleteSystemMessageById id: {}, effect: {}", id, effect);
return effect;
}
@Override
public Integer clearSystemMessage(String classify) {
Long userId = SecurityUtils.getLoginUserId();
log.info("SystemMessageService-clearSystemMessage classify: {}, userId: {}", classify, userId);
// 删除
LambdaQueryWrapper<SystemMessageDO> wrapper = systemMessageDAO.wrapper()
.eq(SystemMessageDO::getReceiverId, userId)
.eq(SystemMessageDO::getClassify, classify)
.eq(SystemMessageDO::getStatus, MessageStatusEnum.READ.getStatus());
int effect = systemMessageDAO.delete(wrapper);
log.info("SystemMessageService-clearSystemMessage classify: {}, effect: {}", classify, effect);
return effect;
}
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.orion.ops.module.infra.dao.SystemMessageDAO">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.orion.ops.module.infra.entity.domain.SystemMessageDO">
<id column="id" property="id"/>
<result column="classify" property="classify"/>
<result column="type" property="type"/>
<result column="status" property="status"/>
<result column="rel_key" property="relKey"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="receiver_id" property="receiverId"/>
<result column="receiver_username" property="receiverUsername"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
</resultMap>
<resultMap id="CountResultMap" type="com.orion.ops.module.infra.entity.dto.SystemMessageCountDTO">
<result column="classify" property="classify"/>
<result column="count" property="count"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, classify, type, status, rel_key, title, content, receiver_id, receiver_username, create_time, update_time, deleted
</sql>
<!-- 查询消息数量 -->
<select id="selectSystemMessageCount" resultMap="CountResultMap">
SELECT classify, count(1) count
FROM system_message
WHERE deleted = 0
AND receiver_id = #{receiverId}
<if test="status != null">
AND status = #{status}
</if>
GROUP BY classify
</select>
</mapper>

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion/api' VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion/api'
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion/keep-alive' VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion/keep-alive'
VITE_APP_VERSION= '1.0.7' VITE_APP_VERSION= '1.0.8'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,4 +1,4 @@
VITE_API_BASE_URL= '/orion/api' VITE_API_BASE_URL= '/orion/api'
VITE_WS_BASE_URL= '/orion/keep-alive' VITE_WS_BASE_URL= '/orion/keep-alive'
VITE_APP_VERSION= '1.0.7' VITE_APP_VERSION= '1.0.8'
VITE_SFTP_PREVIEW_MB= 2 VITE_SFTP_PREVIEW_MB= 2

View File

@@ -1,7 +1,7 @@
{ {
"name": "orion-ops-pro-ui", "name": "orion-ops-pro-ui",
"description": "Orion Ops Pro for Vue", "description": "Orion Ops Pro for Vue",
"version": "1.0.7", "version": "1.0.8",
"private": true, "private": true,
"author": "Jiahang Li", "author": "Jiahang Li",
"license": "Apache 2.0", "license": "Apache 2.0",

View File

@@ -54,7 +54,7 @@ export interface ExecHostLogQueryResponse extends TableData {
status: string; status: string;
command: string; command: string;
parameter: string; parameter: string;
exitStatus: number; exitCode: number;
errorMessage: string; errorMessage: string;
startTime: number; startTime: number;
finishTime: number; finishTime: number;

View File

@@ -1,38 +1,75 @@
import axios from 'axios'; import axios from 'axios';
export interface MessageRecord { /**
* 系统消息查询请求
*/
export interface MessageQueryRequest {
limit?: number;
maxId?: number;
classify?: string;
queryUnread?: boolean;
}
/**
* 系统消息查询响应
*/
export interface MessageRecordResponse {
id: number; id: number;
classify: string;
type: string; type: string;
status: number;
relKey: string;
title: string; title: string;
subTitle: string;
avatar?: string;
content: string; content: string;
time: string; contentHtml: string;
status: 0 | 1; createTime: number;
messageType?: number;
}
export type MessageListType = MessageRecord[];
export function queryMessageList() {
return axios.post<MessageListType>('/api/message/list');
} }
interface MessageStatus { /**
ids: number[]; * 查询系统消息列表
*/
export function getSystemMessageList(request: MessageQueryRequest) {
return axios.post<Array<MessageRecordResponse>>('/infra/system-message/list', request);
} }
export function setMessageStatus(data: MessageStatus) { /**
return axios.post<MessageListType>('/api/message/read', data); * 查询系统消息数量
*/
export function getSystemMessageCount(queryUnread: boolean) {
return axios.get<Record<string, number>>('/infra/system-message/count', { params: { queryUnread } });
} }
export interface ChatRecord { /**
id: number; * 查询是否有未读消息
username: string; */
content: string; export function checkHasUnreadMessage() {
time: string; return axios.get<boolean>('/infra/system-message/has-unread');
isCollect: boolean;
} }
export function queryChatList() { /**
return axios.post<ChatRecord[]>('/api/chat/list'); * 更新系统消息为已读
*/
export function updateSystemMessageRead(id: number) {
return axios.put('/infra/system-message/read', undefined, { params: { id } });
}
/**
* 更新全部系统消息为已读
*/
export function updateSystemMessageReadAll(classify: string) {
return axios.put('/infra/system-message/read-all', undefined, { params: { classify } });
}
/**
* 删除系统消息
*/
export function deleteSystemMessage(id: number) {
return axios.delete('/infra/system-message/delete', { params: { id } });
}
/**
* 清理已读的系统消息
*/
export function clearSystemMessage(classify: string) {
return axios.delete('/infra/system-message/clear', { params: { classify } });
} }

View File

@@ -80,11 +80,11 @@
</a-button> </a-button>
</a-tooltip> </a-tooltip>
</li> </li>
<!-- 消息列表 --> <!-- 系统消息 -->
<li v-if="false"> <li>
<a-tooltip content="消息通知"> <a-tooltip content="系统消息" :show-arrow="false">
<div class="message-box-trigger"> <div class="message-box-trigger">
<a-badge :count="9" dot> <a-badge :count="messageCount" dot>
<a-button class="nav-btn" <a-button class="nav-btn"
type="outline" type="outline"
shape="circle" shape="circle"
@@ -95,9 +95,12 @@
</div> </div>
</a-tooltip> </a-tooltip>
<a-popover trigger="click" <a-popover trigger="click"
:arrow-style="{ display: 'none' }" content-class="message-popover"
:content-style="{ padding: 0, minWidth: '400px' }" position="br"
content-class="message-popover"> :show-arrow="false"
:popup-style="{ marginLeft: '198px' }"
:content-style="{ padding: 0, width: '428px' }"
@hide="pullHasUnreadMessage">
<div ref="messageRef" class="ref-btn" /> <div ref="messageRef" class="ref-btn" />
<template #content> <template #content>
<message-box /> <message-box />
@@ -202,7 +205,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, ref } from 'vue'; import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
import useLocale from '@/hooks/locale'; import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user'; import useUser from '@/hooks/user';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
@@ -214,6 +217,7 @@
import { preferenceTipsKey } from './const'; import { preferenceTipsKey } from './const';
import { REDIRECT_ROUTE_NAME, routerToTag } from '@/router/constants'; import { REDIRECT_ROUTE_NAME, routerToTag } from '@/router/constants';
import { openNewRoute } from '@/router'; import { openNewRoute } from '@/router';
import { checkHasUnreadMessage } from '@/api/system/message';
import SystemMenuTree from '@/components/system/menu/tree/index.vue'; import SystemMenuTree from '@/components/system/menu/tree/index.vue';
import MessageBox from '@/components/system/message-box/index.vue'; import MessageBox from '@/components/system/message-box/index.vue';
import UpdatePasswordModal from '@/components/user/user/update-password-modal/index.vue'; import UpdatePasswordModal from '@/components/user/user/update-password-modal/index.vue';
@@ -258,6 +262,9 @@
const messageRef = ref(); const messageRef = ref();
// 语言 // 语言
const localeRef = ref(); const localeRef = ref();
// 消息数量
const messageCount = ref(0);
const messageIntervalId = ref();
// 打开应用设置 // 打开应用设置
const openAppSetting = inject(openAppSettingKey) as () => void; const openAppSetting = inject(openAppSettingKey) as () => void;
@@ -302,6 +309,14 @@
await logout(); await logout();
}; };
// 获取是否有未读的消息
const pullHasUnreadMessage = () => {
// 查询
checkHasUnreadMessage().then(({ data }) => {
messageCount.value = data ? 1 : 0;
});
};
// 关闭偏好提示 // 关闭偏好提示
const closePreferenceTip = (ack: boolean) => { const closePreferenceTip = (ack: boolean) => {
tippedPreference.value = false; tippedPreference.value = false;
@@ -310,6 +325,18 @@
} }
}; };
onMounted(() => {
// 查询未读消息
pullHasUnreadMessage();
// 注册未读消息轮询
messageIntervalId.value = setInterval(pullHasUnreadMessage, 30000);
});
onUnmounted(() => {
// 清理消息轮询
clearInterval(messageIntervalId.value);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -44,7 +44,7 @@
const logViewRef = ref(); const logViewRef = ref();
const currentHostExecId = ref(); const currentHostExecId = ref();
const statusIntervalId = ref(); const pullIntervalId = ref();
const execLog = ref<ExecLogQueryResponse>(); const execLog = ref<ExecLogQueryResponse>();
const appender = ref<ILogAppender>(); const appender = ref<ILogAppender>();
@@ -57,9 +57,9 @@
if (record.status === execStatus.WAITING || if (record.status === execStatus.WAITING ||
record.status === execStatus.RUNNING) { record.status === execStatus.RUNNING) {
// 等待一秒后先查询一下状态 // 等待一秒后先查询一下状态
setTimeout(fetchTaskStatus, 1000); setTimeout(pullExecStatus, 1000);
// 注册状态轮询 // 注册状态轮询
statusIntervalId.value = setInterval(fetchTaskStatus, 5000); pullIntervalId.value = setInterval(pullExecStatus, 5000);
} }
// 打开日志 // 打开日志
nextTick(() => { nextTick(() => {
@@ -68,7 +68,7 @@
}; };
// 加载状态 // 加载状态
const fetchTaskStatus = async () => { const pullExecStatus = async () => {
if (!execLog.value) { if (!execLog.value) {
return; return;
} }
@@ -95,7 +95,7 @@
host.startTime = hostStatus.startTime; host.startTime = hostStatus.startTime;
// 结束时间绑定了使用时间 如果未完成则使用当前时间 // 结束时间绑定了使用时间 如果未完成则使用当前时间
host.finishTime = hostStatus.finishTime || Date.now(); host.finishTime = hostStatus.finishTime || Date.now();
host.exitStatus = hostStatus.exitStatus; host.exitCode = hostStatus.exitCode;
host.errorMessage = hostStatus.errorMessage; host.errorMessage = hostStatus.errorMessage;
} }
} }
@@ -150,7 +150,7 @@
// 清理轮询 // 清理轮询
const clearAllInterval = () => { const clearAllInterval = () => {
// 关闭状态轮询 // 关闭状态轮询
clearInterval(statusIntervalId.value); clearInterval(pullIntervalId.value);
}; };
// 加载字典值 // 加载字典值

View File

@@ -12,15 +12,15 @@
<a-tag :color="getDictValue(execHostStatusKey, host.status, 'color')"> <a-tag :color="getDictValue(execHostStatusKey, host.status, 'color')">
{{ getDictValue(execHostStatusKey, host.status) }} {{ getDictValue(execHostStatusKey, host.status) }}
</a-tag> </a-tag>
<!-- exitStatus --> <!-- exitCode -->
<a-tag v-if="host.exitStatus || host.exitStatus === 0" <a-tag v-if="host.exitCode || host.exitCode === 0"
:color="host.exitStatus === 0 ? 'arcoblue' : 'orangered'" :color="host.exitCode === 0 ? 'arcoblue' : 'orangered'"
title="exit status"> title="exit code">
<template #icon> <template #icon>
<icon-check v-if="host.exitStatus === 0" /> <icon-check v-if="host.exitCode === 0" />
<icon-exclamation v-else /> <icon-exclamation v-else />
</template> </template>
<span class="tag-value">{{ host.exitStatus }}</span> <span class="tag-value">{{ host.exitCode }}</span>
</a-tag> </a-tag>
<!-- 持续时间 --> <!-- 持续时间 -->
<a-tag v-if="host.startTime" <a-tag v-if="host.startTime"

View File

@@ -0,0 +1,22 @@
// 消息状态
export const MessageStatus = {
UNREAD: 0,
READ: 1,
};
export const MESSAGE_CONFIG_KEY = 'messageConfig';
// 查询数量
export const messageLimit = 15;
// 默认消息分类 通知
export const defaultClassify = 'NOTICE';
// 消息分类 字典项
export const messageClassifyKey = 'messageClassify';
// 消息类型 字典项
export const messageTypeKey = 'messageType';
// 加载的字典值
export const dictKeys = [messageClassifyKey, messageTypeKey];

View File

@@ -1,108 +1,242 @@
<template> <template>
<a-spin style="display: block" :loading="loading"> <div class="full">
<a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> <!-- 消息分类 -->
<a-tab-pane v-for="item in tabList" :key="item.key"> <a-spin class="message-classify-container"
<template #title> :hide-icon="true"
<span> {{ item.title }}{{ formatUnreadLength(item.key) }} </span> :loading="fetchLoading">
<a-tabs v-model:activeKey="currentClassify"
type="rounded"
:hide-content="true"
@change="loadClassifyMessage">
<!-- 消息列表 -->
<a-tab-pane v-for="item in toOptions(messageClassifyKey)"
:key="item.value as string">
<!-- 标题 -->
<template #title>
<span class="usn">{{ item.label }} ({{ classifyCount[item.value as any] || 0 }})</span>
</template>
<!-- 消息列表 -->
</a-tab-pane>
<!-- 右侧操作 -->
<template #extra>
<a-space>
<!-- 状态 -->
<a-switch v-model="queryUnread"
type="round"
checked-text="未读"
unchecked-text="全部"
@change="reloadAllMessage" />
<!-- 清空 -->
<a-button class="header-button"
type="text"
size="small"
@click="clearAllMessage">
清空
</a-button>
<!-- 全部已读 -->
<a-button class="header-button"
type="text"
size="small"
@click="setAllRead">
全部已读
</a-button>
</a-space>
</template> </template>
<a-result v-if="!renderList.length" status="404"> </a-tabs>
<template #subtitle>暂无内容</template> </a-spin>
</a-result> <!-- 消息列表 -->
<list :render-list="renderList" <list :fetch-loading="fetchLoading"
:unread-count="unreadCount" :message-loading="messageLoading"
@item-click="handleItemClick" /> :has-more="hasMore"
</a-tab-pane> :message-list="messageList"
<template #extra> @load="loadMessage"
<a-button type="text" @click="emptyList"> @click="clickMessage"
清空 @delete="deleteMessage" />
</a-button> </div>
</template>
</a-tabs>
</a-spin>
</template> </template>
<script lang="ts">
export default {
name: 'messageBox'
};
</script>
<script lang="ts" setup> <script lang="ts" setup>
import type { MessageRecord, MessageListType } from '@/api/system/message'; import type { MessageRecordResponse } from '@/api/system/message';
import { ref, reactive, toRefs, computed } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import { queryMessageList, setMessageStatus } from '@/api/system/message'; import {
clearSystemMessage,
deleteSystemMessage,
getSystemMessageCount,
getSystemMessageList,
updateSystemMessageRead,
updateSystemMessageReadAll
} from '@/api/system/message';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { useRouter } from 'vue-router';
import { clearHtmlTag, replaceHtmlTag } from '@/utils';
import { useDictStore } from '@/store';
import { dictKeys, messageClassifyKey, messageTypeKey, defaultClassify, MESSAGE_CONFIG_KEY, messageLimit, MessageStatus } from './const';
import List from './list.vue'; import List from './list.vue';
interface TabItem { const { loading: fetchLoading, setLoading: setFetchLoading } = useLoading();
key: string; const { loading: messageLoading, setLoading: setMessageLoading } = useLoading();
title: string; const { loadKeys, toOptions, getDictValue } = useDictStore();
avatar?: string; const router = useRouter();
}
const { loading, setLoading } = useLoading(true); const currentClassify = ref(defaultClassify);
const queryUnread = ref(false);
const classifyCount = ref<Record<string, number>>({});
const messageList = ref<Array<MessageRecordResponse>>([]);
const hasMore = ref(true);
const messageType = ref('message'); // 重新加载消息
const messageData = reactive<{ const reloadAllMessage = async () => {
renderList: MessageRecord[]; hasMore.value = true;
messageList: MessageRecord[]; messageList.value = [];
}>({ // 查询数量
renderList: [], queryMessageCount();
messageList: [], // 加载列表
}); await loadMessage();
toRefs(messageData); };
const tabList: TabItem[] = [
{
key: 'message',
title: '消息',
},
{
key: 'notice',
title: '通知',
},
{
key: 'todo',
title: '待办',
},
];
async function fetchSourceData() { // 获取数量
setLoading(true); const queryMessageCount = async () => {
setFetchLoading(true);
try { try {
const { data } = await queryMessageList(); const { data } = await getSystemMessageCount(queryUnread.value);
messageData.messageList = data; classifyCount.value = data;
} catch (err) { } catch (ex) {
// you can report use errorHandler or other
} finally { } finally {
setLoading(false); setFetchLoading(false);
} }
} };
async function readMessage(data: MessageListType) { // 查询分类消息
const ids = data.map((item) => item.id); const loadClassifyMessage = async () => {
await setMessageStatus({ ids }); hasMore.value = true;
await fetchSourceData(); messageList.value = [];
} await loadMessage();
};
const renderList = computed(() => { // 加载消息
return messageData.messageList.filter( const loadMessage = async () => {
(item) => messageType.value === item.type hasMore.value = true;
); setFetchLoading(true);
try {
const maxId = messageList.value.length
? messageList.value[messageList.value.length - 1].id
: undefined;
// 查询数据
const { data } = await getSystemMessageList({
limit: messageLimit,
classify: currentClassify.value,
queryUnread: queryUnread.value,
maxId,
});
data.forEach(s => {
messageList.value.push({
...s,
content: clearHtmlTag(s.content),
contentHtml: replaceHtmlTag(s.content),
});
});
hasMore.value = data.length === messageLimit;
} catch (ex) {
} finally {
setFetchLoading(false);
}
};
// 设置全部已读
const setAllRead = async () => {
setMessageLoading(true);
try {
// 设置为已读
await updateSystemMessageReadAll(currentClassify.value);
// 修改状态
messageList.value.forEach(s => s.status = MessageStatus.READ);
} catch (ex) {
} finally {
setMessageLoading(false);
}
};
// 清理已读消息
const clearAllMessage = async () => {
setMessageLoading(true);
try {
// 清理消息
await clearSystemMessage(currentClassify.value);
} catch (ex) {
} finally {
setMessageLoading(false);
}
// 查询消息
await reloadAllMessage();
};
// 点击消息
const clickMessage = (message: MessageRecordResponse) => {
// 设置为已读
if (message.status === MessageStatus.UNREAD) {
updateSystemMessageRead(message.id);
message.status = MessageStatus.READ;
}
const redirectComponent = getDictValue(messageTypeKey, message.type, 'redirectComponent');
if (redirectComponent && redirectComponent !== '0') {
// 跳转组件
router.push({ name: redirectComponent, query: { key: message.relKey } });
}
};
// 删除消息
const deleteMessage = async (message: MessageRecordResponse) => {
setMessageLoading(true);
try {
// 删除消息
await deleteSystemMessage(message.id);
// 减少数量
classifyCount.value[currentClassify.value] -= 1;
// 移除
const index = messageList.value.findIndex(s => s.id === message.id);
messageList.value.splice(index, 1);
} catch (ex) {
} finally {
setMessageLoading(false);
}
};
// 加载字典值
onMounted(() => {
loadKeys(dictKeys);
}); });
const unreadCount = computed(() => {
return renderList.value.filter((item) => !item.status).length; // 获取消息
onMounted(() => {
// 获取配置缓存
const item = localStorage.getItem(MESSAGE_CONFIG_KEY);
if (item) {
const config = JSON.parse(item) as Record<string, any>;
if (config?.currentClassify) {
currentClassify.value = config.currentClassify;
}
if (config?.queryUnread) {
queryUnread.value = config.queryUnread;
}
}
// 查询数据
reloadAllMessage();
}); });
const getUnreadList = (type: string) => {
const list = messageData.messageList.filter( // 设置缓存配置
(item) => item.type === type && !item.status onUnmounted(() => {
); localStorage.setItem(MESSAGE_CONFIG_KEY, JSON.stringify({
return list; currentClassify: currentClassify.value,
}; queryUnread: queryUnread.value,
const formatUnreadLength = (type: string) => { }));
const list = getUnreadList(type); });
return list.length ? `(${list.length})` : '';
};
const handleItemClick = (items: MessageListType) => {
if (renderList.value.length) readMessage([...items]);
};
const emptyList = () => {
messageData.messageList = [];
};
fetchSourceData();
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -110,20 +244,23 @@
padding: 0; padding: 0;
} }
:deep(.arco-list-item-meta) {
align-items: flex-start;
}
:deep(.arco-tabs-nav) { :deep(.arco-tabs-nav) {
padding: 14px 0 12px 16px; padding: 10px 12px;
border-bottom: 1px solid var(--color-neutral-3); border-bottom: 1px solid var(--color-neutral-3);
} }
:deep(.arco-tabs-content) { :deep(.arco-tabs-content) {
padding-top: 0; padding-top: 0;
}
.arco-result-subtitle { .message-classify-container {
color: rgb(var(--gray-6)); width: 100%;
height: 100%;
display: block;
.header-button {
padding: 0 6px;
} }
} }
</style> </style>

View File

@@ -1,149 +1,218 @@
<template> <template>
<a-list :bordered="false"> <!-- 消息列表 -->
<a-list-item v-for="item in renderList" <a-spin class="message-list-container" :loading="messageLoading">
:key="item.id" <!-- 加载中 -->
action-layout="vertical" <div v-if="!messageList.length && fetchLoading">
:style="{ <!-- 加载中 -->
opacity: item.status ? 0.5 : 1, <a-skeleton class="skeleton-wrapper" :animation="true">
}"> <a-skeleton-line :rows="3"
<template #extra> :line-height="96"
<a-tag v-if="item.messageType === 0" color="gray">未开始</a-tag> :line-spacing="8" />
<a-tag v-else-if="item.messageType === 1" color="green">已开通</a-tag> </a-skeleton>
<a-tag v-else-if="item.messageType === 2" color="blue">进行中</a-tag>
<a-tag v-else-if="item.messageType === 3" color="red">即将到期</a-tag>
</template>
<div class="item-wrap" @click="onItemClick(item)">
<a-list-item-meta>
<template v-if="item.avatar" #avatar>
<a-avatar shape="circle">
<img v-if="item.avatar" :src="item.avatar" />
<icon-desktop v-else />
</a-avatar>
</template>
<template #title>
<a-space :size="4">
<span>{{ item.title }}</span>
<a-typography-text type="secondary">
{{ item.subTitle }}
</a-typography-text>
</a-space>
</template>
<template #description>
<div>
<a-typography-paragraph :ellipsis="{
rows: 1,
}">
{{ item.content }}
</a-typography-paragraph>
<a-typography-text v-if="item.type === 'message'"
class="time-text">
{{ item.time }}
</a-typography-text>
</div>
</template>
</a-list-item-meta>
</div>
</a-list-item>
<template #footer>
<a-space fill
:size="0"
:class="{ 'add-border-top': renderList.length < showMax }">
<div class="footer-wrap">
<a-link @click="allRead">全部已读</a-link>
</div>
<div class="footer-wrap">
<a-link>查看更多</a-link>
</div>
</a-space>
</template>
<div v-if="renderList.length && renderList.length < 3"
:style="{ height: (showMax - renderList.length) * 86 + 'px' }">
</div> </div>
</a-list> <!-- 无数据 -->
<div v-else-if="!messageList.length && !fetchLoading">
<a-result status="404">
<template #subtitle>暂无内容</template>
</a-result>
</div>
<!-- 消息容器 -->
<div v-else class="message-list-wrapper">
<a-scrollbar style="overflow-y: auto; height: 100%;">
<!-- 消息列表-->
<div v-for="message in messageList"
class="message-item"
:class="[ message.status === MessageStatus.READ ? 'message-item-read' : 'message-item-unread' ]"
@click="emits('click', message)">
<!-- 标题 -->
<div class="message-item-title">
<!-- 标题 -->
<div class="message-item-title-text text-ellipsis" :title="message.title">
{{ message.title }}
</div>
<!-- tag -->
<div class="message-item-title-status">
<template v-if="getDictValue(messageTypeKey, message.type, 'tagVisible', false)">
<a-tag size="small" :color="getDictValue(messageTypeKey, message.type, 'tagColor')">
{{ getDictValue(messageTypeKey, message.type, 'tagLabel') }}
</a-tag>
</template>
</div>
<!-- 操作 -->
<div class="message-item-title-actions">
<!-- 删除 -->
<a-button size="mini"
type="text"
status="danger"
@click.stop="emits('delete', message)">
删除
</a-button>
</div>
</div>
<!-- 内容 -->
<div v-html="message.contentHtml" class="message-item-content" />
<!-- 时间 -->
<div class="message-item-time">
{{ dateFormat(new Date(message.createTime)) }}
</div>
</div>
<!-- 加载中 -->
<a-skeleton v-if="fetchLoading"
class="skeleton-wrapper"
:animation="true">
<a-skeleton-line :rows="3"
:line-height="96"
:line-spacing="8" />
</a-skeleton>
<!-- 加载更多 -->
<div v-if="hasMore" class="load-more-wrapper">
<a-button size="small"
:fetchLoading="fetchLoading"
@click="() => emits('load')">
加载更多
</a-button>
</div>
</a-scrollbar>
</div>
</a-spin>
</template> </template>
<script lang="ts">
export default {
name: 'messageBoxList'
};
</script>
<script lang="ts" setup> <script lang="ts" setup>
import type { MessageListType, MessageRecord } from '@/api/system/message'; import type { MessageRecordResponse } from '@/api/system/message';
import { MessageStatus, messageTypeKey } from './const';
import { useDictStore } from '@/store';
import { dateFormat } from '@/utils';
const props = withDefaults(defineProps<{ const emits = defineEmits(['load', 'click', 'delete']);
renderList: MessageListType; const props = defineProps<{
unreadCount?: number; fetchLoading: boolean;
}>(), { messageLoading: boolean;
unreadCount: 0, hasMore: boolean;
}); messageList: Array<MessageRecordResponse>;
}>();
const emit = defineEmits(['itemClick']); const { getDictValue } = useDictStore();
const allRead = () => {
emit('itemClick', [...props.renderList]);
};
const onItemClick = (item: MessageRecord) => {
if (!item.status) {
emit('itemClick', [item]);
}
};
const showMax = 3;
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
:deep(.arco-list) { @gap: 8px;
.arco-list-item { @actions-width: 82px;
min-height: 86px;
border-bottom: 1px solid rgb(var(--gray-3)); .skeleton-wrapper {
padding: 8px 12px 0 12px;
}
.message-list-container {
width: 100%;
height: 338px;
display: block;
.message-list-wrapper {
width: 100%;
height: 100%;
position: relative;
} }
.arco-list-item-extra { .load-more-wrapper {
position: absolute; display: flex;
right: 20px; justify-content: center;
margin: 12px 0;
}
}
.message-item {
padding: 12px 20px;
border-bottom: 1px solid var(--color-neutral-3);
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
cursor: pointer;
transition: all .2s;
&-title {
height: 22px;
display: flex;
justify-content: space-between;
align-items: flex-start;
&-text {
width: calc(100% - @actions-width - @gap);
display: block;
font-size: 14px;
text-overflow: clip;
color: var(--color-text-1);
}
&-status {
width: @actions-width;
display: flex;
align-items: flex-start;
justify-content: flex-end;
}
&-actions {
width: @actions-width;
display: none;
justify-content: flex-end;
align-items: flex-end;
button {
padding: 0 6px !important;
&:hover {
background: var(--color-fill-3) !important;
}
}
}
} }
.arco-list-item-meta-content { &-content {
flex: 1; display: block;
} margin-top: 4px;
.item-wrap {
cursor: pointer;
}
.time-text {
font-size: 12px; font-size: 12px;
color: rgb(var(--gray-6)); color: var(--color-text-2);
} }
.arco-empty { &-time {
height: 18px;
margin-top: 4px;
display: block;
font-size: 12px;
color: var(--color-text-2);
}
}
.message-item:hover {
background: var(--color-fill-1);
.message-item-title-status {
display: none; display: none;
} }
.arco-list-footer { .message-item-title-actions {
padding: 0; display: flex;
height: 50px; opacity: 1;
line-height: 50px;
border-top: none;
.arco-space-item {
width: 100%;
border-right: 1px solid rgb(var(--gray-3));
&:last-child {
border-right: none;
}
}
.add-border-top {
border-top: 1px solid rgb(var(--gray-3));
}
}
.footer-wrap {
text-align: center;
}
.arco-typography {
margin-bottom: 0;
}
.add-border {
border-top: 1px solid rgb(var(--gray-3));
} }
} }
.message-item-read {
.message-item-title-text, .message-item-title-status, .message-item-content, .message-item-time {
opacity: .65;
}
}
:deep(.arco-scrollbar) {
position: absolute;
height: 100%;
width: 100%;
}
</style> </style>

View File

@@ -1,51 +1,37 @@
import type { AppRouteRecordRaw } from '../types'; import type { AppRouteRecordRaw } from '../types';
import { DEFAULT_LAYOUT, FULL_LAYOUT } from '../base'; import { DEFAULT_LAYOUT } from '../base';
const EXEC: AppRouteRecordRaw[] = [ const EXEC: AppRouteRecordRaw = {
{ name: 'execModule',
name: 'execModule', path: '/exec-module',
path: '/exec-module', component: DEFAULT_LAYOUT,
component: DEFAULT_LAYOUT, children: [
children: [ {
{ name: 'execCommand',
name: 'execCommand', path: '/exec-command',
path: '/exec-command', component: () => import('@/views/exec/exec-command/index.vue'),
component: () => import('@/views/exec/exec-command/index.vue'), },
}, {
{ name: 'execCommandLog',
name: 'execCommandLog', path: '/exec-log',
path: '/exec-log', component: () => import('@/views/exec/exec-command-log/index.vue'),
component: () => import('@/views/exec/exec-command-log/index.vue'), },
}, {
{ name: 'batchUpload',
name: 'batchUpload', path: '/batch-upload',
path: '/batch-upload', component: () => import('@/views/exec/batch-upload/index.vue'),
component: () => import('@/views/exec/batch-upload/index.vue'), },
}, {
{ name: 'uploadTask',
name: 'uploadTask', path: '/upload-task',
path: '/upload-task', component: () => import('@/views/exec/upload-task/index.vue'),
component: () => import('@/views/exec/upload-task/index.vue'), },
}, {
{ name: 'execTemplate',
name: 'execTemplate', path: '/exec-template',
path: '/exec-template', component: () => import('@/views/exec/exec-template/index.vue'),
component: () => import('@/views/exec/exec-template/index.vue'), },
}, ],
], };
},
{
name: 'execFullModule',
path: '/exec-full-module',
component: FULL_LAYOUT,
children: [
{
name: 'execCommandLogView',
path: '/exec-log-view',
component: () => import('@/views/exec/exec-command-log-view/index.vue'),
},
],
}
];
export default EXEC; export default EXEC;

View File

@@ -7,12 +7,12 @@ export default ({ mock, setup }: { mock?: boolean; setup: () => void }) => {
export const successResponseWrap = (data: unknown) => { export const successResponseWrap = (data: unknown) => {
return { return {
data, data,
msg: '请求成功', msg: 'success',
code: 200, code: 200,
}; };
}; };
export const failResponseWrap = (data: unknown, msg: string, code = 5000) => { export const failResponseWrap = (data: unknown, msg: string, code = 500) => {
return { return {
data, data,
msg, msg,

View File

@@ -7,99 +7,18 @@ import setupMock, { successResponseWrap } from '@/utils/setup-mock';
const textList = [ const textList = [
{ {
key: 1, key: 1,
clickNumber: '346.3w+', clickNumber: '1w+',
title: '经济日报:财政政策要精准提升…', title: 'text...',
increases: 35, increases: 35,
}, },
{ {
key: 2, key: 2,
clickNumber: '324.2w+', clickNumber: '2w+',
title: '双12遇冷消费者厌倦了电商平…', title: 'text...',
increases: 22, increases: 22,
}, },
{
key: 3,
clickNumber: '318.9w+',
title: '致敬坚守战“疫”一线的社区工作…',
increases: 9,
},
{
key: 4,
clickNumber: '257.9w+',
title: '普高还是职高?家长们陷入选择…',
increases: 17,
},
{
key: 5,
clickNumber: '124.2w+',
title: '人民快评:没想到“浓眉大眼”的…',
increases: 37,
},
];
const imageList = [
{
key: 1,
clickNumber: '15.3w+',
title: '杨涛接替陆慷出任外交部美大司…',
increases: 15,
},
{
key: 2,
clickNumber: '12.2w+',
title: '图集:龙卷风袭击美国多州房屋…',
increases: 26,
},
{
key: 3,
clickNumber: '18.9w+',
title: '52岁大姐贴钱照顾自闭症儿童八…',
increases: 9,
},
{
key: 4,
clickNumber: '7.9w+',
title: '杭州一家三口公园宿营取暖中毒',
increases: 0,
},
{
key: 5,
clickNumber: '5.2w+',
title: '派出所副所长威胁市民?警方调…',
increases: 4,
},
];
const videoList = [
{
key: 1,
clickNumber: '367.6w+',
title: '这是今日10点的南京',
increases: 5,
},
{
key: 2,
clickNumber: '352.2w+',
title: '立陶宛不断挑衅致经济受损民众…',
increases: 17,
},
{
key: 3,
clickNumber: '348.9w+',
title: '韩国艺人刘在石确诊新冠',
increases: 30,
},
{
key: 4,
clickNumber: '346.3w+',
title: '关于北京冬奥会,文在寅表态',
increases: 12,
},
{
key: 5,
clickNumber: '271.2w+',
title: '95后现役军人荣立一等功',
increases: 2,
},
]; ];
setupMock({ setupMock({
setup() { setup() {
Mock.mock(new RegExp('/api/content-data'), () => { Mock.mock(new RegExp('/api/content-data'), () => {
@@ -117,13 +36,11 @@ setupMock({
}); });
Mock.mock(new RegExp('/api/popular/list'), (params: GetParams) => { Mock.mock(new RegExp('/api/popular/list'), (params: GetParams) => {
const { type = 'text' } = qs.parseUrl(params.url).query; const { type = 'text' } = qs.parseUrl(params.url).query;
if (type === 'image') { if (type === 'text') {
return successResponseWrap([...videoList]); return successResponseWrap([...textList]);
} else {
return successResponseWrap([]);
} }
if (type === 'video') {
return successResponseWrap([...imageList]);
}
return successResponseWrap([...textList]);
}); });
}, },
}); });

View File

@@ -76,7 +76,7 @@
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const pullStatusId = ref(); const pullIntervalId = ref();
const taskId = ref(); const taskId = ref();
const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse); const task = ref<UploadTaskQueryResponse>({} as UploadTaskQueryResponse);
const selectedHost = ref(); const selectedHost = ref();
@@ -266,12 +266,12 @@
// 设置轮询状态 // 设置轮询状态
onMounted(() => { onMounted(() => {
pullStatusId.value = setInterval(pullTaskStatus, 5000); pullIntervalId.value = setInterval(pullTaskStatus, 5000);
}); });
// 卸载状态查询 // 卸载状态查询
onUnmounted(() => { onUnmounted(() => {
clearInterval(pullStatusId.value); clearInterval(pullIntervalId.value);
}); });
</script> </script>

View File

@@ -28,10 +28,14 @@
await dictStore.loadKeys(dictKeys); await dictStore.loadKeys(dictKeys);
}); });
// 跳转日志
onMounted(async () => { onMounted(async () => {
const idParam = route.query.id; const idParam = route.query.id;
const keyParam = route.query.key;
if (idParam) { if (idParam) {
await panel.value?.openLog(Number.parseInt(idParam as string)); await panel.value?.openLog(Number.parseInt(idParam as string));
} else if (keyParam) {
await panel.value?.openLog(Number.parseInt(keyParam as string));
} }
}); });

View File

@@ -1,61 +0,0 @@
<template>
<div class="container">
<div class="wrapper">
<exec-log-panel ref="log"
type="BATCH"
:visible-back="false" />
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'execCommandLogView'
};
</script>
<script lang="ts" setup>
import { onMounted, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { getExecCommandLog } from '@/api/exec/exec-command-log';
import ExecLogPanel from '@/components/exec/log/panel/index.vue';
const route = useRoute();
const log = ref();
// 初始化
const init = async (id: number) => {
// 获取执行日志
const { data } = await getExecCommandLog(id);
// 打开日志
await nextTick(() => {
setTimeout(() => {
log.value.open(data);
}, 50);
});
};
onMounted(() => {
const idParam = route.query.id;
if (idParam) {
init(Number.parseInt(idParam as string));
}
});
</script>
<style lang="less" scoped>
.container {
width: 100%;
height: 100%;
position: relative;
padding: 16px;
background: var(--color-fill-2);
.wrapper {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

View File

@@ -236,7 +236,7 @@
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore(); const { toOptions, getDictValue } = useDictStore();
const intervalId = ref(); const pullIntervalId = ref();
const tableRef = ref(); const tableRef = ref();
const selectedKeys = ref<number[]>([]); const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecLogQueryResponse[]>([]); const tableRenderData = ref<ExecLogQueryResponse[]>([]);
@@ -343,7 +343,7 @@
}; };
// 加载状态 // 加载状态
const fetchTaskStatus = async () => { const pullExecStatus = async () => {
const unCompleteIdList = tableRenderData.value const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING) .filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.map(s => s.id); .map(s => s.id);
@@ -374,7 +374,7 @@
host.status = s.status; host.status = s.status;
host.startTime = s.startTime; host.startTime = s.startTime;
host.finishTime = s.finishTime; host.finishTime = s.finishTime;
host.exitStatus = s.exitStatus; host.exitCode = s.exitCode;
host.errorMessage = s.errorMessage; host.errorMessage = s.errorMessage;
}); });
}; };
@@ -409,12 +409,12 @@
// 加载数据 // 加载数据
fetchTableData(); fetchTableData();
// 注册状态轮询 // 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000); pullIntervalId.value = setInterval(pullExecStatus, 10000);
}); });
onUnmounted(() => { onUnmounted(() => {
// 卸载状态轮询 // 卸载状态轮询
clearInterval(intervalId.value); clearInterval(pullIntervalId.value);
}); });
</script> </script>

View File

@@ -69,7 +69,7 @@
if (newWindow) { if (newWindow) {
// 跳转新页面 // 跳转新页面
openNewRoute({ openNewRoute({
name: 'execCommandLogView', name: 'execCommand',
query: { query: {
id id
} }

View File

@@ -18,12 +18,12 @@ const columns = [
tooltip: true, tooltip: true,
}, { }, {
title: '退出码', title: '退出码',
dataIndex: 'exitStatus', dataIndex: 'exitCode',
slotName: 'exitStatus', slotName: 'exitCode',
align: 'left', align: 'left',
width: 118, width: 118,
render: ({ record }) => { render: ({ record }) => {
return isNumber(record.exitStatus) ? record.exitStatus : '-'; return isNumber(record.exitCode) ? record.exitCode : '-';
}, },
}, { }, {
title: '执行状态', title: '执行状态',

View File

@@ -26,10 +26,13 @@
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { dictKeys } from '@/components/exec/log/const'; import { dictKeys } from '@/components/exec/log/const';
import { useRoute } from 'vue-router';
import { getExecCommandLog } from '@/api/exec/exec-command-log';
import ExecCommandPanel from './components/exec-command-panel.vue'; import ExecCommandPanel from './components/exec-command-panel.vue';
import ExecLogPanel from '@/components/exec/log/panel/index.vue'; import ExecLogPanel from '@/components/exec/log/panel/index.vue';
const { visible: logVisible, setVisible: setLogVisible } = useVisible(); const { visible: logVisible, setVisible: setLogVisible } = useVisible();
const route = useRoute();
const log = ref(); const log = ref();
@@ -41,12 +44,30 @@
}); });
}; };
// 打开日志
const openLogWithId = async (id: number) => {
setLogVisible(true);
// 查询日志
const { data } = await getExecCommandLog(id);
openLog(data);
};
// 加载字典值 // 加载字典值
onMounted(async () => { onMounted(async () => {
const dictStore = useDictStore(); const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys); await dictStore.loadKeys(dictKeys);
}); });
// 跳转日志
onMounted(async () => {
const idParam = route.query.id;
const keyParam = route.query.key;
if (idParam) {
await openLogWithId(Number.parseInt(idParam as string));
} else if (keyParam) {
await openLogWithId(Number.parseInt(keyParam as string));
}
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -199,7 +199,7 @@
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore(); const { toOptions, getDictValue } = useDictStore();
const intervalId = ref(); const pullIntervalId = ref();
const selectedKeys = ref<number[]>([]); const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<UploadTaskQueryResponse[]>([]); const tableRenderData = ref<UploadTaskQueryResponse[]>([]);
const formModel = reactive<UploadTaskQueryRequest>({ const formModel = reactive<UploadTaskQueryRequest>({
@@ -264,7 +264,7 @@
}; };
// 加载状态 // 加载状态
const fetchTaskStatus = async () => { const pullTaskStatus = async () => {
const unCompleteIdList = tableRenderData.value const unCompleteIdList = tableRenderData.value
.filter(s => s.status === UploadTaskStatus.WAITING || s.status === UploadTaskStatus.UPLOADING) .filter(s => s.status === UploadTaskStatus.WAITING || s.status === UploadTaskStatus.UPLOADING)
.map(s => s.id); .map(s => s.id);
@@ -311,12 +311,12 @@
// 加载数据 // 加载数据
fetchTableData(); fetchTableData();
// 注册状态轮询 // 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000); pullIntervalId.value = setInterval(pullTaskStatus, 10000);
}); });
onUnmounted(() => { onUnmounted(() => {
// 卸载状态轮询 // 卸载状态轮询
clearInterval(intervalId.value); clearInterval(pullIntervalId.value);
}); });
</script> </script>

View File

@@ -14,7 +14,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
private run: boolean; private run: boolean;
private progressId?: number; private progressIntervalId?: number;
private currentItem?: SftpTransferItem; private currentItem?: SftpTransferItem;
@@ -136,7 +136,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 处理消息 // 处理消息
this.client.onmessage = this.resolveMessage.bind(this); this.client.onmessage = this.resolveMessage.bind(this);
// 计算传输进度 // 计算传输进度
this.progressId = setInterval(this.calcProgress.bind(this), 500); this.progressIntervalId = setInterval(this.calcProgress.bind(this), 500);
// 打开后自动传输下一个任务 // 打开后自动传输下一个任务
this.transferNextItem(); this.transferNextItem();
} }
@@ -275,7 +275,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 重置 run // 重置 run
this.run = false; this.run = false;
// 关闭传输进度 // 关闭传输进度
clearInterval(this.progressId); clearInterval(this.progressIntervalId);
} }
} }

View File

@@ -23,7 +23,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
private sessions: Record<string, ITerminalSession>; private sessions: Record<string, ITerminalSession>;
private keepAliveTask?: any; private keepAliveTaskId?: any;
private readonly dispatchResizeFn: () => {}; private readonly dispatchResizeFn: () => {};
@@ -136,7 +136,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 注册 resize 事件 // 注册 resize 事件
addEventListen(window, 'resize', this.dispatchResizeFn); addEventListen(window, 'resize', this.dispatchResizeFn);
// 注册 ping 事件 // 注册 ping 事件
this.keepAliveTask = setInterval(() => { this.keepAliveTaskId = setInterval(() => {
this.channel.send(InputProtocol.PING, {}); this.channel.send(InputProtocol.PING, {});
}, 15000); }, 15000);
} }
@@ -158,10 +158,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 关闭 channel // 关闭 channel
this.channel.close(); this.channel.close();
// 清除 ping 事件 // 清除 ping 事件
if (this.keepAliveTask) { clearInterval(this.keepAliveTaskId);
clearInterval(this.keepAliveTask);
this.keepAliveTask = undefined;
}
// 移除 resize 事件 // 移除 resize 事件
removeEventListen(window, 'resize', this.dispatchResizeFn); removeEventListen(window, 'resize', this.dispatchResizeFn);
} catch (e) { } catch (e) {

View File

@@ -217,7 +217,7 @@
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore(); const { toOptions, getDictValue } = useDictStore();
const intervalId = ref(); const pullIntervalId = ref();
const tableRef = ref(); const tableRef = ref();
const selectedKeys = ref<number[]>([]); const selectedKeys = ref<number[]>([]);
const tableRenderData = ref<ExecLogQueryResponse[]>([]); const tableRenderData = ref<ExecLogQueryResponse[]>([]);
@@ -308,7 +308,7 @@
}; };
// 加载状态 // 加载状态
const fetchTaskStatus = async () => { const pullJobStatus = async () => {
const unCompleteIdList = tableRenderData.value const unCompleteIdList = tableRenderData.value
.filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING) .filter(s => s.status === execStatus.WAITING || s.status === execStatus.RUNNING)
.map(s => s.id); .map(s => s.id);
@@ -339,7 +339,7 @@
host.status = s.status; host.status = s.status;
host.startTime = s.startTime; host.startTime = s.startTime;
host.finishTime = s.finishTime; host.finishTime = s.finishTime;
host.exitStatus = s.exitStatus; host.exitCode = s.exitCode;
host.errorMessage = s.errorMessage; host.errorMessage = s.errorMessage;
}); });
}; };
@@ -374,12 +374,12 @@
// 加载数据 // 加载数据
fetchTableData(); fetchTableData();
// 注册状态轮询 // 注册状态轮询
intervalId.value = setInterval(fetchTaskStatus, 10000); pullIntervalId.value = setInterval(pullJobStatus, 10000);
}); });
onUnmounted(() => { onUnmounted(() => {
// 卸载状态轮询 // 卸载状态轮询
clearInterval(intervalId.value); clearInterval(pullIntervalId.value);
}); });
</script> </script>

View File

@@ -22,7 +22,7 @@
</modules> </modules>
<properties> <properties>
<revision>1.0.7</revision> <revision>1.0.8</revision>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version> <maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>

View File

@@ -211,7 +211,7 @@ CREATE TABLE `exec_host_log`
`status` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行状态', `status` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '执行状态',
`command` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行命令', `command` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '执行命令',
`parameter` json NULL COMMENT '执行参数', `parameter` json NULL COMMENT '执行参数',
`exit_status` int(0) NULL DEFAULT NULL COMMENT '退出码', `exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码',
`log_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志路径', `log_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志路径',
`script_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '脚本路径', `script_path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '脚本路径',
`error_message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误信息', `error_message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误信息',
@@ -644,6 +644,32 @@ CREATE TABLE `system_menu`
COLLATE = utf8mb4_unicode_ci COMMENT = '菜单表' COLLATE = utf8mb4_unicode_ci COMMENT = '菜单表'
ROW_FORMAT = Dynamic; ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for system_message
-- ----------------------------
DROP TABLE IF EXISTS `system_message`;
CREATE TABLE `system_message`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`classify` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息分类',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息类型',
`status` tinyint(0) NULL DEFAULT NULL COMMENT '消息状态',
`rel_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息关联',
`title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容',
`receiver_id` bigint(0) NULL DEFAULT NULL COMMENT '接收人id',
`receiver_username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '接收人用户名',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_receiver_classify` (`receiver_id`, `classify`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '系统消息'
ROW_FORMAT = Dynamic;
-- ---------------------------- -- ----------------------------
-- Table structure for system_role -- Table structure for system_role
-- ---------------------------- -- ----------------------------

View File

@@ -90,7 +90,7 @@ INSERT INTO `system_menu` VALUES (157, 122, '清空操作日志', 'infra:operato
INSERT INTO `system_menu` VALUES (158, 152, '文件操作日志', NULL, 2, 30, 1, 1, 1, 0, 'IconFile', NULL, 'sftpLog', '2024-03-05 15:30:13', '2024-05-07 11:11:24', '1', '1', 0); INSERT INTO `system_menu` VALUES (158, 152, '文件操作日志', NULL, 2, 30, 1, 1, 1, 0, 'IconFile', NULL, 'sftpLog', '2024-03-05 15:30:13', '2024-05-07 11:11:24', '1', '1', 0);
INSERT INTO `system_menu` VALUES (159, 158, '查询文件操作日志', 'asset:host-sftp-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:02', '2024-04-12 14:49:18', '1', '1', 0); INSERT INTO `system_menu` VALUES (159, 158, '查询文件操作日志', 'asset:host-sftp-log:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:02', '2024-04-12 14:49:18', '1', '1', 0);
INSERT INTO `system_menu` VALUES (160, 158, '删除文件操作日志', 'asset:host-sftp-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:17', '2024-04-12 14:49:21', '1', '1', 0); INSERT INTO `system_menu` VALUES (160, 158, '删除文件操作日志', 'asset:host-sftp-log:management:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-05 15:31:17', '2024-04-12 14:49:21', '1', '1', 0);
INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 60, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-04-28 17:14:30', '1', '1', 0); INSERT INTO `system_menu` VALUES (161, 176, '执行模板', NULL, 2, 50, 1, 1, 1, 0, 'IconBookmark', NULL, 'execTemplate', '2024-03-07 18:32:41', '2024-05-14 15:58:51', '1', '1', 0);
INSERT INTO `system_menu` VALUES (162, 161, '查询执行模板', 'asset:exec-template:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0); INSERT INTO `system_menu` VALUES (162, 161, '查询执行模板', 'asset:exec-template:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
INSERT INTO `system_menu` VALUES (163, 161, '创建执行模板', 'asset:exec-template:create', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0); INSERT INTO `system_menu` VALUES (163, 161, '创建执行模板', 'asset:exec-template:create', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
INSERT INTO `system_menu` VALUES (164, 161, '修改执行模板', 'asset:exec-template:update', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0); INSERT INTO `system_menu` VALUES (164, 161, '修改执行模板', 'asset:exec-template:update', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-07 18:32:41', '2024-03-07 18:32:41', '1', '1', 0);
@@ -102,7 +102,6 @@ INSERT INTO `system_menu` VALUES (171, 167, '清理执行日志', 'asset:exec-co
INSERT INTO `system_menu` VALUES (172, 176, '命令执行', NULL, 2, 10, 1, 1, 1, 0, 'icon-thunderbolt', NULL, 'execCommand', '2024-03-13 15:08:23', '2024-05-08 21:58:24', '1', '1', 0); INSERT INTO `system_menu` VALUES (172, 176, '命令执行', NULL, 2, 10, 1, 1, 1, 0, 'icon-thunderbolt', NULL, 'execCommand', '2024-03-13 15:08:23', '2024-05-08 21:58:24', '1', '1', 0);
INSERT INTO `system_menu` VALUES (173, 172, '执行命令', 'asset:exec-command:exec', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:08:23', '2024-04-10 22:04:32', '1', '1', 0); INSERT INTO `system_menu` VALUES (173, 172, '执行命令', 'asset:exec-command:exec', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:08:23', '2024-04-10 22:04:32', '1', '1', 0);
INSERT INTO `system_menu` VALUES (174, 167, '中断批量执行', 'asset:exec-command-log:interrupt', 3, 40, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:25:36', '2024-04-12 12:48:36', '1', '1', 0); INSERT INTO `system_menu` VALUES (174, 167, '中断批量执行', 'asset:exec-command-log:interrupt', 3, 40, 1, 1, 1, 0, NULL, NULL, NULL, '2024-03-13 15:25:36', '2024-04-12 12:48:36', '1', '1', 0);
INSERT INTO `system_menu` VALUES (175, 176, '批量执行日志新页面', NULL, 2, 30, 0, 1, 0, 1, '', NULL, 'execCommandLogView', '2024-03-21 14:03:10', '2024-04-11 23:45:08', '1', '1', 0);
INSERT INTO `system_menu` VALUES (176, 0, '批量执行', NULL, 1, 420, 1, 1, 1, 0, 'icon-relation', NULL, 'execModule', '2024-04-10 16:13:27', '2024-04-28 15:30:31', '1', '1', 0); INSERT INTO `system_menu` VALUES (176, 0, '批量执行', NULL, 1, 420, 1, 1, 1, 0, 'icon-relation', NULL, 'execModule', '2024-04-10 16:13:27', '2024-04-28 15:30:31', '1', '1', 0);
INSERT INTO `system_menu` VALUES (177, 193, '任务列表', NULL, 2, 10, 1, 1, 1, 0, 'IconUnorderedList', NULL, 'execJob', '2024-04-10 16:13:27', '2024-04-28 15:36:10', '1', '1', 0); INSERT INTO `system_menu` VALUES (177, 193, '任务列表', NULL, 2, 10, 1, 1, 1, 0, 'IconUnorderedList', NULL, 'execJob', '2024-04-10 16:13:27', '2024-04-28 15:36:10', '1', '1', 0);
INSERT INTO `system_menu` VALUES (178, 177, '查询计划任务', 'asset:exec-job:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-04-10 16:13:27', '2024-04-10 16:13:27', '1', '1', 0); INSERT INTO `system_menu` VALUES (178, 177, '查询计划任务', 'asset:exec-job:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-04-10 16:13:27', '2024-04-10 16:13:27', '1', '1', 0);
@@ -123,8 +122,8 @@ INSERT INTO `system_menu` VALUES (193, 0, '计划任务', NULL, 1, 430, 1, 1, 1,
INSERT INTO `system_menu` VALUES (194, 152, '在线会话', NULL, 2, 20, 1, 1, 1, 0, 'IconUserGroup', NULL, 'connectSession', '2024-05-07 11:12:17', '2024-05-07 11:12:35', '1', '1', 0); INSERT INTO `system_menu` VALUES (194, 152, '在线会话', NULL, 2, 20, 1, 1, 1, 0, 'IconUserGroup', NULL, 'connectSession', '2024-05-07 11:12:17', '2024-05-07 11:12:35', '1', '1', 0);
INSERT INTO `system_menu` VALUES (195, 194, '查询在线会话', 'asset:host-connect-session:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:16', '2024-05-07 11:13:16', '1', '1', 0); INSERT INTO `system_menu` VALUES (195, 194, '查询在线会话', 'asset:host-connect-session:management:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:16', '2024-05-07 11:13:16', '1', '1', 0);
INSERT INTO `system_menu` VALUES (196, 194, '强制断开连接', 'asset:host-connect-session:management:force-offline', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:37', '2024-05-07 11:13:37', '1', '1', 0); INSERT INTO `system_menu` VALUES (196, 194, '强制断开连接', 'asset:host-connect-session:management:force-offline', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-07 11:13:37', '2024-05-07 11:13:37', '1', '1', 0);
INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 40, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-08 22:12:23', '1', '1', 0); INSERT INTO `system_menu` VALUES (197, 176, '批量上传', NULL, 2, 30, 1, 1, 1, 0, 'IconUpload', NULL, 'batchUpload', '2024-05-08 22:12:23', '2024-05-14 15:58:44', '1', '1', 0);
INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 50, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-10 23:09:58', '1', '1', 0); INSERT INTO `system_menu` VALUES (198, 176, '上传任务', NULL, 2, 40, 1, 1, 1, 0, 'IconCloud', NULL, 'uploadTask', '2024-05-08 22:16:05', '2024-05-14 15:58:46', '1', '1', 0);
INSERT INTO `system_menu` VALUES (199, 197, '上传文件', 'asset:upload-task:upload', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:19:35', '2024-05-08 22:19:35', '1', '1', 0); INSERT INTO `system_menu` VALUES (199, 197, '上传文件', 'asset:upload-task:upload', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:19:35', '2024-05-08 22:19:35', '1', '1', 0);
INSERT INTO `system_menu` VALUES (200, 198, '查询上传日志', 'asset:upload-task:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:01', '2024-05-08 22:20:01', '1', '1', 0); INSERT INTO `system_menu` VALUES (200, 198, '查询上传日志', 'asset:upload-task:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:01', '2024-05-08 22:20:01', '1', '1', 0);
INSERT INTO `system_menu` VALUES (201, 198, '删除上传日志', 'asset:upload-task:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:26', '2024-05-08 22:20:26', '1', '1', 0); INSERT INTO `system_menu` VALUES (201, 198, '删除上传日志', 'asset:upload-task:delete', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2024-05-08 22:20:26', '2024-05-08 22:20:26', '1', '1', 0);
@@ -165,6 +164,8 @@ INSERT INTO `dict_key` VALUES (39, 'pathBookmarkType', 'STRING', '[]', '路径
INSERT INTO `dict_key` VALUES (40, 'sftpTransferStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}, {\"name\": \"color\", \"type\": \"COLOR\"}, {\"name\": \"icon\", \"type\": \"STRING\"}]', 'SFTP 传输状态', '2024-05-06 11:54:49', '2024-05-06 11:54:49', '1', '1', 0); INSERT INTO `dict_key` VALUES (40, 'sftpTransferStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}, {\"name\": \"color\", \"type\": \"COLOR\"}, {\"name\": \"icon\", \"type\": \"STRING\"}]', 'SFTP 传输状态', '2024-05-06 11:54:49', '2024-05-06 11:54:49', '1', '1', 0);
INSERT INTO `dict_key` VALUES (41, 'uploadTaskStatus', 'STRING', '[{\"name\": \"color\", \"type\": \"COLOR\"}]', '上传任务状态', '2024-05-07 22:18:48', '2024-05-08 22:06:23', '1', '1', 0); INSERT INTO `dict_key` VALUES (41, 'uploadTaskStatus', 'STRING', '[{\"name\": \"color\", \"type\": \"COLOR\"}]', '上传任务状态', '2024-05-07 22:18:48', '2024-05-08 22:06:23', '1', '1', 0);
INSERT INTO `dict_key` VALUES (42, 'uploadTaskFileStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}]', '上传任务文件状态', '2024-05-08 10:30:29', '2024-05-10 17:34:13', '1', '1', 0); INSERT INTO `dict_key` VALUES (42, 'uploadTaskFileStatus', 'STRING', '[{\"name\": \"status\", \"type\": \"STRING\"}]', '上传任务文件状态', '2024-05-08 10:30:29', '2024-05-10 17:34:13', '1', '1', 0);
INSERT INTO `dict_key` VALUES (43, 'messageType', 'STRING', '[{\"name\": \"tagLabel\", \"type\": \"STRING\"}, {\"name\": \"tagVisible\", \"type\": \"STRING\"}, {\"name\": \"tagColor\", \"type\": \"STRING\"}, {\"name\": \"redirectComponent\", \"type\": \"STRING\"}]', '消息类型', '2024-05-13 12:07:56', '2024-05-14 14:48:28', '1', '1', 0);
INSERT INTO `dict_key` VALUES (44, 'messageClassify', 'STRING', '[]', '消息分类', '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
-- 字典值 -- 字典值
INSERT INTO `dict_value` VALUES (3, 4, 'systemMenuType', '1', '父菜单', '{}', 10, '2023-10-26 15:58:59', '2023-10-26 15:58:59', '1', '1', 0); INSERT INTO `dict_value` VALUES (3, 4, 'systemMenuType', '1', '父菜单', '{}', 10, '2023-10-26 15:58:59', '2023-10-26 15:58:59', '1', '1', 0);
@@ -405,3 +406,7 @@ INSERT INTO `dict_value` VALUES (291, 2, 'operatorLogType', 'upload-task:cancel'
INSERT INTO `dict_value` VALUES (292, 2, 'operatorLogType', 'upload-task:delete', '删除上传记录', '{}', 30, '2024-05-08 22:23:44', '2024-05-08 22:23:44', '1', '1', 0); INSERT INTO `dict_value` VALUES (292, 2, 'operatorLogType', 'upload-task:delete', '删除上传记录', '{}', 30, '2024-05-08 22:23:44', '2024-05-08 22:23:44', '1', '1', 0);
INSERT INTO `dict_value` VALUES (293, 2, 'operatorLogType', 'upload-task:clear', '清理上传记录', '{}', 40, '2024-05-08 22:23:59', '2024-05-08 22:23:59', '1', '1', 0); INSERT INTO `dict_value` VALUES (293, 2, 'operatorLogType', 'upload-task:clear', '清理上传记录', '{}', 40, '2024-05-08 22:23:59', '2024-05-08 22:23:59', '1', '1', 0);
INSERT INTO `dict_value` VALUES (294, 41, 'uploadTaskStatus', 'FAILED', '已失败', '{\"color\": \"red\"}', 40, '2024-05-10 11:29:17', '2024-05-10 11:29:17', '1', '1', 0); INSERT INTO `dict_value` VALUES (294, 41, 'uploadTaskStatus', 'FAILED', '已失败', '{\"color\": \"red\"}', 40, '2024-05-10 11:29:17', '2024-05-10 11:29:17', '1', '1', 0);
INSERT INTO `dict_value` VALUES (295, 43, 'messageType', 'EXEC_FAILED', '执行失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"execCommand\"}', 10, '2024-05-13 12:07:56', '2024-05-14 15:19:19', '1', '1', 0);
INSERT INTO `dict_value` VALUES (296, 43, 'messageType', 'UPLOAD_FAILED', '上传失败', '{\"tagColor\": \"red\", \"tagLabel\": \"部分失败\", \"tagVisible\": \"true\", \"redirectComponent\": \"batchUpload\"}', 20, '2024-05-13 12:07:56', '2024-05-14 15:11:21', '1', '1', 0);
INSERT INTO `dict_value` VALUES (297, 44, 'messageClassify', 'NOTICE', '通知', '{}', 10, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);
INSERT INTO `dict_value` VALUES (298, 44, 'messageClassify', 'TODO', '待办', '{}', 20, '2024-05-13 15:06:27', '2024-05-13 15:06:27', '1', '1', 0);