Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0656c18e85 | ||
|
|
cce511c4b4 | ||
|
|
4f0f320fcd | ||
|
|
2fed2aaa34 | ||
|
|
4eeedb85de |
@@ -1,7 +1,7 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
orion-visor-service:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.0
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-service:2.0.1
|
||||
ports:
|
||||
- 1081:80
|
||||
environment:
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
- orion-visor-mysql
|
||||
- orion-visor-redis
|
||||
orion-visor-mysql:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.0
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-mysql:2.0.1
|
||||
privileged: true
|
||||
ports:
|
||||
- 3307:3306
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/var-lib-mysql-files:/var/lib/mysql-files
|
||||
- /data/orion-visor-space/docker-volumes/orion-visor-mysql/etc-mysql:/etc/mysql
|
||||
orion-visor-redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.0
|
||||
image: registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:2.0.1
|
||||
privileged: true
|
||||
ports:
|
||||
- 6380:6379
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.0.0
|
||||
version=2.0.1
|
||||
cp -r ../../sql ./sql
|
||||
docker build -t orion-visor-mysql:${version} .
|
||||
rm -rf ./sql
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.0.0
|
||||
version=2.0.1
|
||||
docker build -t orion-visor-redis:${version} .
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/lijiahangmax/orion-visor-redis:${version}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.0.0
|
||||
version=2.0.1
|
||||
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
||||
mv ../../orion-visor-ui/dist ./dist
|
||||
docker build -t orion-visor-service:${version} .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# orion-visor <small>2.0.0</small>
|
||||
# orion-visor <small>2.0.1</small>
|
||||
|
||||
> 一款开箱即用的运维平台。
|
||||
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
* 执行 升级的 `bash` 脚本
|
||||
* 进入 代码目录执行 `sh docker-upgrade.sh` 进行容器升级 `down` > `pull` > `up -d`
|
||||
|
||||
### v2.0.1
|
||||
|
||||
`2024-05-21` `release`
|
||||
|
||||
* ⭐ 添加 cron 组件
|
||||
* 🐞 修复 批量执行后日志偶尔不展示的问题
|
||||
* 🐞 修复 批量上传进度条显示异常的问题
|
||||
|
||||
### v2.0.0
|
||||
|
||||
`2024-05-17` `release`
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
* 终端背景图片
|
||||
* 资产授权 UI 改版
|
||||
* RDP 远程桌面
|
||||
* 接入 config 后端动态配置
|
||||
* 文档中巡检模板
|
||||
* 导入快捷命令
|
||||
* 导入命令模板
|
||||
* 使用 vite press 开发文档
|
||||
|
||||
## 已知问题
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
### 所需环境
|
||||
|
||||
* JDK 1.8
|
||||
* Mysql 8.0(+)
|
||||
* Redis 5.0.5(+)
|
||||
* Node 16.16.0(+)
|
||||
* Maven 3.5.4(+)
|
||||
* jdk 1.8
|
||||
* mysql 8.0.+
|
||||
* redis 6.0.+
|
||||
* maven 3.5.+
|
||||
* node 18.12.+
|
||||
* pnpm 9.1.+
|
||||
|
||||
⚡ maven 推荐使用阿里云 mirror
|
||||
⚡ npm 建议使用淘宝镜像 `npm config set registry https://registry.npmmirror.com/`
|
||||
⚡ pnpm 建议使用淘宝镜像 `pnpm config set registry https://registry.npmmirror.com/`
|
||||
|
||||
### 配置
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
### 所需环境
|
||||
|
||||
* JDK 1.8
|
||||
* Mysql 8.0(+)
|
||||
* Redis 5.0.5(+)
|
||||
* Node 16.16.0(+)
|
||||
* Maven 3.5.4(+)
|
||||
* Nginx
|
||||
* jdk 1.8
|
||||
* mysql 8.0.+
|
||||
* redis 6.0.+
|
||||
* maven 3.5.+
|
||||
* node 18.12.+
|
||||
* pnpm 9.1.+
|
||||
* nginx
|
||||
|
||||
⚡ maven 推荐使用阿里云 mirror
|
||||
⚡ npm 建议使用淘宝镜像 `npm config set registry https://registry.npmmirror.com/`
|
||||
⚡ npm 建议使用淘宝镜像 `npm config set registry https://registry.npmmirror.com/`
|
||||
⚡ pnpm 建议使用淘宝镜像 `pnpm config set registry https://registry.npmmirror.com/`
|
||||
|
||||
### 构建
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/lijiahangmax/orion-visor</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.0.0</revision>
|
||||
<revision>2.0.1</revision>
|
||||
<spring.boot.version>2.7.17</spring.boot.version>
|
||||
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
||||
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
||||
|
||||
@@ -14,7 +14,7 @@ public interface AppConst extends OrionConst {
|
||||
/**
|
||||
* 同 ${orion.version} 迭代时候需要手动更改
|
||||
*/
|
||||
String VERSION = "2.0.0";
|
||||
String VERSION = "2.0.1";
|
||||
|
||||
String ORION_VISOR = "orion-visor";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@cancel="handleClose">
|
||||
<a-spin class="full modal-form-small" :loading="loading">
|
||||
<a-spin class="full drawer-form-small" :loading="loading">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="right"
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.orion.visor.framework.websocket.core.session;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketExtension;
|
||||
import org.springframework.web.socket.WebSocketMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* web socket 同步会话
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/5/20 10:12
|
||||
*/
|
||||
public class WebSocketSyncSession implements WebSocketSession {
|
||||
|
||||
private final WebSocketSession delegate;
|
||||
|
||||
public WebSocketSyncSession(WebSocketSession delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.delegate.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return this.delegate.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHandshakeHeaders() {
|
||||
return this.delegate.getHandshakeHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.delegate.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return this.delegate.getPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalAddress() {
|
||||
return this.delegate.getLocalAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return this.delegate.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAcceptedProtocol() {
|
||||
return this.delegate.getAcceptedProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextMessageSizeLimit(int messageSizeLimit) {
|
||||
this.delegate.setTextMessageSizeLimit(messageSizeLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextMessageSizeLimit() {
|
||||
return this.delegate.getTextMessageSizeLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryMessageSizeLimit(int messageSizeLimit) {
|
||||
this.delegate.setBinaryMessageSizeLimit(messageSizeLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBinaryMessageSizeLimit() {
|
||||
return this.delegate.getBinaryMessageSizeLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebSocketExtension> getExtensions() {
|
||||
return this.delegate.getExtensions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(WebSocketMessage<?> message) throws IOException {
|
||||
synchronized (this.delegate) {
|
||||
this.delegate.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return this.delegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(CloseStatus status) throws IOException {
|
||||
this.delegate.close(status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.orion.visor.framework.websocket.core.utils;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.orion.lang.utils.Exceptions;
|
||||
import com.orion.lang.utils.Threads;
|
||||
import com.orion.visor.framework.common.constant.Const;
|
||||
import com.orion.visor.framework.websocket.core.constant.WsCloseCode;
|
||||
import com.orion.visor.framework.websocket.core.session.WebSocketSyncSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
@@ -24,6 +26,16 @@ public class WebSockets {
|
||||
private WebSockets() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建同步会话
|
||||
*
|
||||
* @param session session
|
||||
* @return session
|
||||
*/
|
||||
public static WebSocketSession createSyncSession(WebSocketSession session) {
|
||||
return new WebSocketSyncSession(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
@@ -58,13 +70,20 @@ public class WebSockets {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 发重消息
|
||||
session.sendMessage(new TextMessage(message));
|
||||
if (session instanceof WebSocketSyncSession) {
|
||||
// 发送消息
|
||||
session.sendMessage(new TextMessage(message));
|
||||
} else {
|
||||
synchronized (session) {
|
||||
// 发送消息
|
||||
session.sendMessage(new TextMessage(message));
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// 并发异常
|
||||
log.error("发送消息失败, 准备进行重试 {}", Exceptions.getDigest(e));
|
||||
// 并发重试
|
||||
retrySendText(session, message, 50);
|
||||
retrySendText(session, message, Const.MS_100);
|
||||
} catch (IOException e) {
|
||||
throw Exceptions.ioRuntime(e);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ public class ExecLogTailHandler extends AbstractWebSocketHandler {
|
||||
String trackerId = this.getTrackerId(id, info, host);
|
||||
String absolutePath = logsFileClient.getAbsolutePath(host.getPath());
|
||||
// 追踪器
|
||||
ExecLogTracker tracker = new ExecLogTracker(trackerId, absolutePath, session, host);
|
||||
ExecLogTracker tracker = new ExecLogTracker(trackerId,
|
||||
absolutePath,
|
||||
WebSockets.createSyncSession(session),
|
||||
host);
|
||||
// 执行
|
||||
AssetThreadPools.EXEC_LOG.execute(tracker);
|
||||
// 添加追踪器
|
||||
|
||||
@@ -79,7 +79,13 @@ public class ExecLogTracker implements IExecLogTracker {
|
||||
|
||||
@Override
|
||||
public void read(byte[] bytes, int len, Tracker tracker) {
|
||||
WebSockets.sendText(session, config.getId() + LogConst.SEPARATOR + new String(bytes, 0, len));
|
||||
// 发送消息
|
||||
String message = config.getId() + LogConst.SEPARATOR + new String(bytes, 0, len);
|
||||
try {
|
||||
WebSockets.sendText(session, message);
|
||||
} catch (Exception e) {
|
||||
log.error("ExecLogTracker.send error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VITE_API_BASE_URL= 'http://127.0.0.1:9200/orion-visor/api'
|
||||
VITE_WS_BASE_URL= 'ws://127.0.0.1:9200/orion-visor/keep-alive'
|
||||
VITE_APP_VERSION= '2.0.0'
|
||||
VITE_APP_VERSION= '2.0.1'
|
||||
VITE_SFTP_PREVIEW_MB= 2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VITE_API_BASE_URL= '/orion-visor/api'
|
||||
VITE_WS_BASE_URL= '/orion-visor/keep-alive'
|
||||
VITE_APP_VERSION= '2.0.0'
|
||||
VITE_APP_VERSION= '2.0.1'
|
||||
VITE_SFTP_PREVIEW_MB= 2
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ArcoResolver } from 'unplugin-vue-components/resolvers';
|
||||
* 按需引入
|
||||
* https://github.com/antfu/unplugin-vue-components
|
||||
* https://arco.design/vue/docs/start
|
||||
* 虽然Pro项目中是全量引入组件,但此插件会默认使用。
|
||||
* 虽然Pro项目中是全量引入组件, 但此插件会默认使用
|
||||
*/
|
||||
export default function configArcoResolverPlugin() {
|
||||
const arcoResolverPlugin = Components({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "orion-visor-ui",
|
||||
"description": "Orion Visor UI",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"author": "Jiahang Li",
|
||||
"license": "Apache 2.0",
|
||||
@@ -35,6 +35,7 @@
|
||||
"@sanqi377/arco-vue-icon-picker": "^1.0.7",
|
||||
"@vueuse/core": "^9.3.0",
|
||||
"axios": "^0.24.0",
|
||||
"cron-parser": "^4.9.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"echarts": "^5.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
|
||||
17070
orion-visor-ui/pnpm-lock.yaml
generated
17070
orion-visor-ui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -66,17 +66,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
// -- modal
|
||||
.modal-form-small {
|
||||
// -- drawer
|
||||
.drawer-form-small{
|
||||
padding: 20px 20px 2px 20px;
|
||||
}
|
||||
|
||||
// -- modal
|
||||
.modal-form-small {
|
||||
.arco-modal-header {
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 20px 20px 2px 20px;
|
||||
}
|
||||
|
||||
.arco-modal-footer {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-form-medium {
|
||||
padding: 20px 28px 4px 28px;
|
||||
.arco-modal-header {
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 20px 28px 4px 28px;
|
||||
}
|
||||
|
||||
.arco-modal-footer {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-form-large {
|
||||
padding: 24px 36px 4px 36px;
|
||||
.arco-modal-header {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 24px 36px 4px 36px;
|
||||
}
|
||||
|
||||
.arco-modal-footer {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// -- card-view
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<a-link target="_blank" href="https://gitee.com/lijiahangmax/orion-visor">gitee</a-link>
|
||||
<a-link target="_blank" href="https://lijiahangmax.github.io/orion-visor">文档</a-link>
|
||||
<a-link target="_blank" href="https://github.com/lijiahangmax/orion-visor/blob/main/LICENSE">License</a-link>
|
||||
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">v{{ version }} Community</a-link>
|
||||
<a-link target="_blank" :href="`https://github.com/lijiahangmax/orion-visor/releases/tag/v${version}`">V{{ version }} 社区版</a-link>
|
||||
</a-space>
|
||||
<span class="copyright">
|
||||
Copyright<icon-copyright /> {{ new Date().getFullYear() }} Li Jiahang All rights reserved.
|
||||
Copyright<icon-copyright /> 2023 - {{ new Date().getFullYear() }} Li Jiahang, All rights reserved.
|
||||
</span>
|
||||
</a-space>
|
||||
</a-layout-footer>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
export const cronEmits = ['change', 'update:modelValue'];
|
||||
|
||||
// cron 参数类型
|
||||
export interface CronPropType {
|
||||
modelValue: string;
|
||||
disabled: boolean;
|
||||
hideSecond: boolean;
|
||||
hideYear: boolean;
|
||||
placeholder: string;
|
||||
callback: (expression: string, timestamp: number, validated: boolean) => void;
|
||||
}
|
||||
|
||||
// cron 参数默认值
|
||||
export const cronDefaultProps: Partial<CronPropType> = {
|
||||
disabled: false,
|
||||
hideSecond: false,
|
||||
hideYear: false,
|
||||
placeholder: '请输入 cron 表达式',
|
||||
};
|
||||
|
||||
/**
|
||||
* 转化为 quartz 周
|
||||
* 1 = 周日
|
||||
* 2 = 周一
|
||||
* 3 = 周二
|
||||
* 4 = 周三
|
||||
* 5 = 周四
|
||||
* 6 = 周五
|
||||
* 7 = 周六
|
||||
*/
|
||||
export const convertWeekToQuartz = (week: string) => {
|
||||
const convert = (v: string) => {
|
||||
if (v === '0') {
|
||||
return '1';
|
||||
}
|
||||
if (v === '1') {
|
||||
return '0';
|
||||
}
|
||||
return (Number.parseInt(v) - 1).toString();
|
||||
};
|
||||
// 匹配示例 1-7 or 1/7
|
||||
const patten1 = /^([0-7])([-/])([0-7])$/;
|
||||
// 匹配示例 1,4,7
|
||||
const patten2 = /^([0-7])(,[0-7])+$/;
|
||||
if (/^[0-7]$/.test(week)) {
|
||||
return convert(week);
|
||||
} else if (patten1.test(week)) {
|
||||
return week.replace(patten1, ($0, before, separator, after) => {
|
||||
if (separator === '/') {
|
||||
return convert(before) + separator + after;
|
||||
} else {
|
||||
return convert(before) + separator + convert(after);
|
||||
}
|
||||
});
|
||||
} else if (patten2.test(week)) {
|
||||
return week.split(',')
|
||||
.map((v) => convert(v))
|
||||
.join(',');
|
||||
}
|
||||
return week;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="inputNumberAttrs" />
|
||||
<span>日 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="inputNumberAttrs" />
|
||||
<span>日</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>日开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>日</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.last" v-bind="beforeRadioAttrs">最后一日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<a-grid :cols="11">
|
||||
<a-grid-item v-for="i in specifyRange" :key="i">
|
||||
<a-checkbox :value="i" v-bind="typeSpecifyAttrs">
|
||||
{{ i }}
|
||||
</a-checkbox>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch } from 'vue';
|
||||
import { TypeEnum, useFormProps, useFromEmits, useFormSetup } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DayForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
props: {
|
||||
week: { type: String, default: '?' },
|
||||
},
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
const disabledChoice = computed(() => {
|
||||
return (props.week && props.week !== '?') || props.disabled;
|
||||
});
|
||||
const setup = useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
valueWork: 1,
|
||||
minValue: 1,
|
||||
maxValue: 31,
|
||||
valueRange: { start: 1, end: 31 },
|
||||
valueLoop: { start: 1, interval: 1 },
|
||||
disabled: disabledChoice,
|
||||
});
|
||||
const typeWorkAttrs = computed(() => ({
|
||||
disabled: setup.type.value !== TypeEnum.work || props.disabled || disabledChoice.value,
|
||||
...setup.inputNumberAttrs.value,
|
||||
}));
|
||||
|
||||
watch(
|
||||
() => props.week,
|
||||
() => {
|
||||
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value);
|
||||
},
|
||||
);
|
||||
|
||||
return { ...setup, typeWorkAttrs };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每时</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="typeRangeAttrs" />
|
||||
<span>时 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="typeRangeAttrs" />
|
||||
<span>时</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>时开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>时</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<a-grid :cols="12">
|
||||
<a-grid-item v-for="i in specifyRange" :key="i">
|
||||
<a-checkbox :value="i" v-bind="typeSpecifyAttrs">
|
||||
{{ i }}
|
||||
</a-checkbox>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFromEmits, useFormProps, useFormSetup } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HourForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
return useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
minValue: 0,
|
||||
maxValue: 23,
|
||||
valueRange: { start: 0, end: 23 },
|
||||
valueLoop: { start: 0, interval: 1 },
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每分</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="typeRangeAttrs" />
|
||||
<span>分 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="typeRangeAttrs" />
|
||||
<span>分</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>分开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>分</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<a-grid :cols="10">
|
||||
<a-grid-item v-for="i in specifyRange" :key="i">
|
||||
<a-checkbox :value="i" v-bind="typeSpecifyAttrs">
|
||||
{{ i }}
|
||||
</a-checkbox>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFromEmits, useFormProps, useFormSetup } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MinuteForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
return useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
minValue: 0,
|
||||
maxValue: 59,
|
||||
valueRange: { start: 0, end: 59 },
|
||||
valueLoop: { start: 0, interval: 1 },
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每月</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="typeRangeAttrs" />
|
||||
<span>月 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="typeRangeAttrs" />
|
||||
<span>月</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>月开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>月</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<a-grid :cols="12">
|
||||
<a-grid-item v-for="i in specifyRange" :key="i">
|
||||
<a-checkbox :value="i" v-bind="typeSpecifyAttrs">
|
||||
{{ i }}
|
||||
</a-checkbox>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFromEmits, useFormProps, useFormSetup } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MonthForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
return useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
minValue: 1,
|
||||
maxValue: 12,
|
||||
valueRange: { start: 1, end: 12 },
|
||||
valueLoop: { start: 1, interval: 1 },
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每秒</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="typeRangeAttrs" />
|
||||
<span>秒 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="typeRangeAttrs" />
|
||||
<span>秒</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>秒开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>秒</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<a-grid :cols="10">
|
||||
<a-grid-item v-for="i in specifyRange" :key="i">
|
||||
<a-checkbox :value="i" v-bind="typeSpecifyAttrs">
|
||||
{{ i }}
|
||||
</a-checkbox>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFromEmits, useFormProps, useFormSetup } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SecondForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
return useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
minValue: 0,
|
||||
maxValue: 59,
|
||||
valueRange: { start: 0, end: 59 },
|
||||
valueLoop: { start: 0, interval: 1 },
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,222 @@
|
||||
import { computed, reactive, ref, unref, watch } from 'vue';
|
||||
|
||||
// 类型定义
|
||||
export enum TypeEnum {
|
||||
unset = 'UNSET',
|
||||
every = 'EVERY',
|
||||
range = 'RANGE',
|
||||
loop = 'LOOP',
|
||||
work = 'WORK',
|
||||
last = 'LAST',
|
||||
specify = 'SPECIFY',
|
||||
}
|
||||
|
||||
// 周定义
|
||||
export const WEEK_MAP: any = {
|
||||
1: '周日',
|
||||
2: '周一',
|
||||
3: '周二',
|
||||
4: '周三',
|
||||
5: '周四',
|
||||
6: '周五',
|
||||
7: '周六',
|
||||
};
|
||||
|
||||
// use 公共 props
|
||||
export function useFormProps(options: any) {
|
||||
const defaultValue = options?.defaultValue ?? '?';
|
||||
return {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: defaultValue,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
...options?.props,
|
||||
};
|
||||
}
|
||||
|
||||
// use 公共 emits
|
||||
export function useFromEmits() {
|
||||
return ['change', 'update:modelValue'];
|
||||
}
|
||||
|
||||
// use 公共 setup
|
||||
export function useFormSetup(props: any, context: any, options: any) {
|
||||
const { emit } = context;
|
||||
const defaultValue = ref(options?.defaultValue ?? '?');
|
||||
// 类型
|
||||
const type = ref(options.defaultType ?? TypeEnum.every);
|
||||
const valueList = ref<any[]>([]);
|
||||
// 对于不同的类型, 所定义的值也有所不同
|
||||
const valueRange = reactive(options.valueRange);
|
||||
const valueLoop = reactive(options.valueLoop);
|
||||
const valueWork = ref(options.valueWork);
|
||||
const maxValue = ref(options.maxValue);
|
||||
const minValue = ref(options.minValue);
|
||||
|
||||
// 根据不同的类型计算出的 value
|
||||
const computeValue = computed(() => {
|
||||
const valueArray: any[] = [];
|
||||
switch (type.value) {
|
||||
case TypeEnum.unset:
|
||||
valueArray.push('?');
|
||||
break;
|
||||
case TypeEnum.every:
|
||||
valueArray.push('*');
|
||||
break;
|
||||
case TypeEnum.range:
|
||||
valueArray.push(`${valueRange.start}-${valueRange.end}`);
|
||||
break;
|
||||
case TypeEnum.loop:
|
||||
valueArray.push(`${valueLoop.start}/${valueLoop.interval}`);
|
||||
break;
|
||||
case TypeEnum.work:
|
||||
valueArray.push(`${valueWork.value}W`);
|
||||
break;
|
||||
case TypeEnum.last:
|
||||
valueArray.push('L');
|
||||
break;
|
||||
case TypeEnum.specify:
|
||||
if (valueList.value.length === 0) {
|
||||
valueList.value.push(minValue.value);
|
||||
}
|
||||
valueArray.push(valueList.value.join(','));
|
||||
break;
|
||||
default:
|
||||
valueArray.push(defaultValue.value);
|
||||
break;
|
||||
}
|
||||
return valueArray.length > 0 ? valueArray.join('') : defaultValue.value;
|
||||
});
|
||||
|
||||
// 指定值范围区间, 介于最小值和最大值之间
|
||||
const specifyRange = computed(() => {
|
||||
const range: number[] = [];
|
||||
if (maxValue.value != null) {
|
||||
for (let i = minValue.value; i <= maxValue.value; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
}
|
||||
return range;
|
||||
});
|
||||
|
||||
// 更新值
|
||||
const updateValue = (value: any) => {
|
||||
emit('change', value);
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
// 解析值
|
||||
const parseValue = (value: any) => {
|
||||
if (value === computeValue.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!value || value === defaultValue.value) {
|
||||
type.value = TypeEnum.every;
|
||||
} else if (value.indexOf('?') >= 0) {
|
||||
type.value = TypeEnum.unset;
|
||||
} else if (value.indexOf('-') >= 0) {
|
||||
type.value = TypeEnum.range;
|
||||
const values = value.split('-');
|
||||
if (values.length >= 2) {
|
||||
valueRange.start = parseInt(values[0]);
|
||||
valueRange.end = parseInt(values[1]);
|
||||
}
|
||||
} else if (value.indexOf('/') >= 0) {
|
||||
type.value = TypeEnum.loop;
|
||||
const values = value.split('/');
|
||||
if (values.length >= 2) {
|
||||
valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0]);
|
||||
valueLoop.interval = parseInt(values[1]);
|
||||
}
|
||||
} else if (value.indexOf('W') >= 0) {
|
||||
type.value = TypeEnum.work;
|
||||
const values = value.split('W');
|
||||
if (!values[0] && !isNaN(values[0])) {
|
||||
valueWork.value = parseInt(values[0]);
|
||||
}
|
||||
} else if (value.indexOf('L') >= 0) {
|
||||
type.value = TypeEnum.last;
|
||||
} else if (value.indexOf(',') >= 0 || !isNaN(value)) {
|
||||
type.value = TypeEnum.specify;
|
||||
valueList.value = value.split(',').map((item: any) => parseInt(item));
|
||||
} else {
|
||||
type.value = TypeEnum.every;
|
||||
}
|
||||
} catch (e) {
|
||||
type.value = TypeEnum.every;
|
||||
}
|
||||
};
|
||||
|
||||
// 更新值
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val !== computeValue.value) {
|
||||
parseValue(val);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 更新值
|
||||
watch(computeValue, (v) => updateValue(v));
|
||||
|
||||
// 单选框属性
|
||||
const beforeRadioAttrs = computed(() => ({
|
||||
class: ['choice'],
|
||||
disabled: props.disabled || unref(options.disabled),
|
||||
size: 'small',
|
||||
}));
|
||||
|
||||
// 输入框属性
|
||||
const inputNumberAttrs = computed(() => ({
|
||||
max: maxValue.value,
|
||||
min: minValue.value,
|
||||
precision: 0,
|
||||
size: 'small',
|
||||
hideButton: true,
|
||||
class: 'w60'
|
||||
}));
|
||||
|
||||
// 区间属性
|
||||
const typeRangeAttrs = computed(() => ({
|
||||
disabled: type.value !== TypeEnum.range || props.disabled || unref(options.disabled),
|
||||
...inputNumberAttrs.value,
|
||||
}));
|
||||
|
||||
// 间隔属性
|
||||
const typeLoopAttrs = computed(() => ({
|
||||
disabled: type.value !== TypeEnum.loop || props.disabled || unref(options.disabled),
|
||||
...inputNumberAttrs.value,
|
||||
}));
|
||||
|
||||
// 指定属性
|
||||
const typeSpecifyAttrs = computed(() => ({
|
||||
disabled: type.value !== TypeEnum.specify || props.disabled || unref(options.disabled),
|
||||
class: ['list-check-item'],
|
||||
size: 'small',
|
||||
}));
|
||||
|
||||
return {
|
||||
type,
|
||||
TypeEnum,
|
||||
defaultValue,
|
||||
valueRange,
|
||||
valueLoop,
|
||||
valueList,
|
||||
valueWork,
|
||||
maxValue,
|
||||
minValue,
|
||||
computeValue,
|
||||
specifyRange,
|
||||
updateValue,
|
||||
// parseValue,
|
||||
beforeRadioAttrs,
|
||||
inputNumberAttrs,
|
||||
typeRangeAttrs,
|
||||
typeLoopAttrs,
|
||||
typeSpecifyAttrs,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-select v-model="valueRange.start" v-bind="typeRangeSelectAttrs">
|
||||
<a-option v-for="item in weekOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</a-select>
|
||||
<span>至</span>
|
||||
<a-select v-model="valueRange.end" v-bind="typeRangeSelectAttrs">
|
||||
<a-option v-for="item in weekOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-select v-model="valueLoop.start" v-bind="typeLoopSelectAttrs">
|
||||
<a-option v-for="item in weekOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</a-select>
|
||||
<span>开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>天</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio>
|
||||
<div class="list list-cn">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="opt in weekOptions" :key="opt">
|
||||
<a-checkbox :value="opt.value" v-bind="typeSpecifyAttrs">
|
||||
{{ opt.label }}
|
||||
</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, watch } from 'vue';
|
||||
import { TypeEnum, useFormProps, useFormSetup, useFromEmits, WEEK_MAP } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WeekForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '?',
|
||||
props: {
|
||||
day: { type: String, default: '*' },
|
||||
},
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
const disabledChoice = computed(() => {
|
||||
return (props.day && props.day !== '?') || props.disabled;
|
||||
});
|
||||
const setup = useFormSetup(props, context, {
|
||||
defaultType: TypeEnum.unset,
|
||||
defaultValue: '?',
|
||||
minValue: 1,
|
||||
maxValue: 7,
|
||||
// 0,7表示周日 1表示周一
|
||||
valueRange: { start: 1, end: 7 },
|
||||
valueLoop: { start: 2, interval: 1 },
|
||||
disabled: disabledChoice,
|
||||
});
|
||||
const weekOptions = computed(() => {
|
||||
const options: { label: string; value: number }[] = [];
|
||||
for (const weekKey of Object.keys(WEEK_MAP)) {
|
||||
const weekName: string = WEEK_MAP[weekKey];
|
||||
options.push({
|
||||
value: Number.parseInt(weekKey),
|
||||
label: weekName,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
const typeRangeSelectAttrs = computed(() => ({
|
||||
disabled: setup.typeRangeAttrs.value.disabled,
|
||||
size: 'small',
|
||||
class: ['w80'],
|
||||
}));
|
||||
|
||||
const typeLoopSelectAttrs = computed(() => ({
|
||||
disabled: setup.typeLoopAttrs.value.disabled,
|
||||
size: 'small',
|
||||
class: ['w80'],
|
||||
}));
|
||||
|
||||
watch(() => props.day, () => {
|
||||
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
...setup,
|
||||
weekOptions,
|
||||
typeLoopSelectAttrs,
|
||||
typeRangeSelectAttrs,
|
||||
WEEK_MAP,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="cron-inner-config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.every" v-bind="beforeRadioAttrs">每年</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.range" v-bind="beforeRadioAttrs">区间</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueRange.start" v-bind="typeRangeAttrs" />
|
||||
<span>年 至</span>
|
||||
<a-input-number v-model="valueRange.end" v-bind="typeRangeAttrs" />
|
||||
<span>年</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</a-radio>
|
||||
<span>从</span>
|
||||
<a-input-number v-model="valueLoop.start" v-bind="typeLoopAttrs" />
|
||||
<span>年开始, 间隔</span>
|
||||
<a-input-number v-model="valueLoop.interval" v-bind="typeLoopAttrs" />
|
||||
<span>年</span>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFormProps, useFormSetup, useFromEmits } from './use-mixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'YearForm',
|
||||
props: useFormProps({
|
||||
defaultValue: '*',
|
||||
}),
|
||||
emits: useFromEmits(),
|
||||
setup(props, context) {
|
||||
const nowYear = new Date().getFullYear();
|
||||
return useFormSetup(props, context, {
|
||||
defaultValue: '*',
|
||||
minValue: 0,
|
||||
valueRange: { start: nowYear, end: nowYear + 100 },
|
||||
valueLoop: { start: nowYear, interval: 1 },
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<div class="cron-inner">
|
||||
<div class="content">
|
||||
<!-- 设置表单 -->
|
||||
<a-tabs v-model:active-key="activeKey" size="small">
|
||||
<!-- 秒 -->
|
||||
<a-tab-pane title="秒" key="second" v-if="!hideSecond">
|
||||
<second-form v-model="second" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 分 -->
|
||||
<a-tab-pane title="分" key="minute">
|
||||
<minute-form v-model="minute" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 时 -->
|
||||
<a-tab-pane title="时" key="hour">
|
||||
<hour-form v-model="hour" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 日 -->
|
||||
<a-tab-pane title="日" key="day">
|
||||
<day-form v-model="day" :week="week" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 月 -->
|
||||
<a-tab-pane title="月" key="month">
|
||||
<month-form v-model="month" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 周 -->
|
||||
<a-tab-pane title="周" key="week">
|
||||
<week-form v-model="week" :day="day" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 年 -->
|
||||
<a-tab-pane title="年" key="year" v-if="!hideYear && !hideSecond">
|
||||
<year-form v-model="year" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 执行时间预览 -->
|
||||
<a-row :gutter="8">
|
||||
<!-- 快捷修改 -->
|
||||
<a-col :span="18" style="margin-top: 28px">
|
||||
<a-row :gutter="[12, 12]">
|
||||
<!-- 秒 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.second" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'second'">秒</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 分 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.minute" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'minute'">分</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 时 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.hour" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'hour'">时</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 日 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.day" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'day'">日</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 月 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.month" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'month'">月</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 周 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.week" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'week'">周</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 年 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="inputValues.year" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'year'">年</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 表达式 -->
|
||||
<a-col :span="16">
|
||||
<a-input v-model="inputValues.cron"
|
||||
:placeholder="placeholder"
|
||||
@change="onInputCronChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click">表达式</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<span class="allow-click span-blue"
|
||||
title="点击复制"
|
||||
@click="copy(inputValues.cron,'已复制')">
|
||||
<icon-copy />
|
||||
</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<!-- 执行时间 -->
|
||||
<a-col :span="6">
|
||||
<div class="preview-times usn">近五次执行时间 (不解析年)</div>
|
||||
<a-textarea v-model="previewTimes" :auto-size="{ minRows: 5, maxRows: 5 }" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'cronGeneratorInput'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { CronPropType } from './const.types';
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { cronEmits, cronDefaultProps, convertWeekToQuartz } from './const.types';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { dateFormat } from '@/utils';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import CronParser from 'cron-parser';
|
||||
import SecondForm from './form/second-form.vue';
|
||||
import MinuteForm from './form/minute-form.vue';
|
||||
import HourForm from './form/hour-form.vue';
|
||||
import DayForm from './form/day-form.vue';
|
||||
import MonthForm from './form/month-form.vue';
|
||||
import WeekForm from './form/week-form.vue';
|
||||
import YearForm from './form/year-form.vue';
|
||||
|
||||
const emit = defineEmits([...cronEmits]);
|
||||
const props = withDefaults(defineProps<Partial<CronPropType>>(), { ...cronDefaultProps });
|
||||
|
||||
const activeKey = ref(props.hideSecond ? 'minute' : 'second');
|
||||
const second = ref('*');
|
||||
const minute = ref('*');
|
||||
const hour = ref('*');
|
||||
const day = ref('*');
|
||||
const month = ref('*');
|
||||
const week = ref('?');
|
||||
const year = ref('*');
|
||||
const inputValues = reactive({
|
||||
second: '',
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
week: '',
|
||||
year: '',
|
||||
cron: '',
|
||||
});
|
||||
const previewTimes = ref('执行预览');
|
||||
|
||||
// cron 表达式
|
||||
const expression = computed(() => {
|
||||
const result: string[] = [];
|
||||
if (!props.hideSecond) {
|
||||
result.push(second.value ? second.value : '*');
|
||||
}
|
||||
result.push(minute.value ? minute.value : '*');
|
||||
result.push(hour.value ? hour.value : '*');
|
||||
result.push(day.value ? day.value : '*');
|
||||
result.push(month.value ? month.value : '*');
|
||||
result.push(week.value ? week.value : '?');
|
||||
if (!props.hideYear && !props.hideSecond) {
|
||||
result.push(year.value ? year.value : '*');
|
||||
}
|
||||
return result.join(' ');
|
||||
});
|
||||
|
||||
// 不含年的 cron 表达式
|
||||
const expressionNoYear = computed(() => {
|
||||
const v = expression.value;
|
||||
if (props.hideYear || props.hideSecond) return v;
|
||||
const vs = v.split(' ');
|
||||
if (vs.length >= 6) {
|
||||
// 转成 quartz 周
|
||||
vs[5] = convertWeekToQuartz(vs[5]);
|
||||
}
|
||||
return vs.slice(0, vs.length - 1).join(' ');
|
||||
});
|
||||
|
||||
// 计算触发时间
|
||||
const calcTriggerTime = () => {
|
||||
try {
|
||||
// 解析表达式
|
||||
const iter = CronParser.parseExpression(expressionNoYear.value, {
|
||||
currentDate: dateFormat(new Date()),
|
||||
});
|
||||
const result: string[] = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
result.push(dateFormat(new Date(iter.next() as any)));
|
||||
}
|
||||
previewTimes.value = result.length > 0 ? result.join('\n') : '无执行时间';
|
||||
// 回调
|
||||
if (props.callback) {
|
||||
props.callback(expression.value, +new Date(), true);
|
||||
}
|
||||
} catch (e) {
|
||||
previewTimes.value = '表达式错误';
|
||||
// 回调
|
||||
if (props.callback) {
|
||||
props.callback(expression.value, +new Date(), false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const calcTriggerTimeList = useDebounceFn(calcTriggerTime, 500);
|
||||
|
||||
// 监听 cron 修改
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal === expression.value) {
|
||||
return;
|
||||
}
|
||||
parseCron();
|
||||
});
|
||||
|
||||
// 监听 cron 修改
|
||||
watch(expression, (newValue) => {
|
||||
calcTriggerTimeList();
|
||||
emitValue(newValue);
|
||||
assignInput();
|
||||
});
|
||||
|
||||
// 根据 cron 解析
|
||||
const parseCron = () => {
|
||||
// 计算执行时间
|
||||
calcTriggerTimeList();
|
||||
if (!props.modelValue) {
|
||||
return;
|
||||
}
|
||||
const values = props.modelValue.split(' ').filter((item) => !!item);
|
||||
if (!values || values.length <= 0) {
|
||||
return;
|
||||
}
|
||||
let i = 0;
|
||||
if (!props.hideSecond) second.value = values[i++];
|
||||
if (values.length > i) minute.value = values[i++];
|
||||
if (values.length > i) hour.value = values[i++];
|
||||
if (values.length > i) day.value = values[i++];
|
||||
if (values.length > i) month.value = values[i++];
|
||||
if (values.length > i) week.value = values[i++];
|
||||
if (values.length > i) year.value = values[i];
|
||||
// 重新分配
|
||||
assignInput();
|
||||
};
|
||||
|
||||
// 重新分配
|
||||
const assignInput = () => {
|
||||
inputValues.second = second.value;
|
||||
inputValues.minute = minute.value;
|
||||
inputValues.hour = hour.value;
|
||||
inputValues.day = day.value;
|
||||
inputValues.month = month.value;
|
||||
inputValues.week = week.value;
|
||||
inputValues.year = year.value;
|
||||
inputValues.cron = expression.value;
|
||||
};
|
||||
|
||||
// 修改 cron 解析内容
|
||||
const onInputChange = () => {
|
||||
second.value = inputValues.second;
|
||||
minute.value = inputValues.minute;
|
||||
hour.value = inputValues.hour;
|
||||
day.value = inputValues.day;
|
||||
month.value = inputValues.month;
|
||||
week.value = inputValues.week;
|
||||
year.value = inputValues.year;
|
||||
};
|
||||
|
||||
// 修改 cron 输入框
|
||||
const onInputCronChange = (value: string) => {
|
||||
emitValue(value);
|
||||
};
|
||||
|
||||
// 修改 cron
|
||||
const emitValue = (value: string) => {
|
||||
emit('change', value);
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
assignInput();
|
||||
parseCron();
|
||||
// 如果 modelValue 没有值则更新为 expression
|
||||
if (!props.modelValue) {
|
||||
emitValue(expression.value);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.cron-inner {
|
||||
user-select: none;
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
:deep(.cron-inner-config-list) {
|
||||
text-align: left;
|
||||
margin: 0 12px 4px 12px;
|
||||
|
||||
.item {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choice {
|
||||
padding: 4px 8px 4px 0;
|
||||
}
|
||||
|
||||
.w60 {
|
||||
margin: 0 8px !important;
|
||||
padding: 0 8px !important;
|
||||
width: 60px !important;
|
||||
}
|
||||
|
||||
.w80 {
|
||||
margin: 0 8px !important;
|
||||
padding: 0 8px !important;
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.list-check-item {
|
||||
padding: 1px 3px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.list-cn .list-check-item {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
.tip-info {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-input-prepend) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.arco-input-append) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.preview-times {
|
||||
color: var(--color-text-3);
|
||||
margin: 2px 0 4px 0;
|
||||
}
|
||||
|
||||
.allow-click {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
modal-class="modal-form-small"
|
||||
title-align="start"
|
||||
title="cron 生成器"
|
||||
:top="32"
|
||||
:width="780"
|
||||
:align-center="false"
|
||||
:draggable="true"
|
||||
:mask-closable="false"
|
||||
:unmount-on-close="true"
|
||||
:body-style="{ padding: '4px 16px 8px 16px' }">
|
||||
<!-- cron 输入框 -->
|
||||
<cron-generator-input v-model="cronExpression" />
|
||||
<!-- 页脚-->
|
||||
<template #footer>
|
||||
<a-button size="small" @click="handlerClose">关闭</a-button>
|
||||
<a-button size="small"
|
||||
type="primary"
|
||||
@click="handlerOk">
|
||||
确定
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'cronGeneratorModal'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import CronGeneratorInput from '../generator-input/index.vue';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
|
||||
const cronExpression = ref('');
|
||||
|
||||
const emits = defineEmits(['ok']);
|
||||
|
||||
// 打开新增
|
||||
const open = (cron: string = '') => {
|
||||
cronExpression.value = cron;
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
|
||||
// 确定
|
||||
const handlerOk = () => {
|
||||
setVisible(false);
|
||||
console.log(cronExpression.value);
|
||||
console.log('ok', cronExpression.value);
|
||||
emits('ok', cronExpression.value);
|
||||
};
|
||||
|
||||
// 关闭
|
||||
const handlerClose = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'nextCronModal'
|
||||
name: 'cronNextModal'
|
||||
};
|
||||
</script>
|
||||
|
||||
45
orion-visor-ui/src/components/meta/cron/validator.ts
Normal file
45
orion-visor-ui/src/components/meta/cron/validator.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import CronParser from 'cron-parser';
|
||||
|
||||
// 验证器
|
||||
export const cronValidator = ({}, value: any) => {
|
||||
// 没填写就不校验
|
||||
if (!value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const values: string[] = value.split(' ').filter((item: any) => !!item);
|
||||
if (values.length > 7) {
|
||||
return Promise.reject('表达式最多 7 项');
|
||||
}
|
||||
// 检查第7项
|
||||
let val: string = value;
|
||||
if (values.length === 7) {
|
||||
const year = values[6];
|
||||
if (year !== '*' && year !== '?') {
|
||||
let yearValues: string[] = [];
|
||||
if (year.indexOf('-') >= 0) {
|
||||
yearValues = year.split('-');
|
||||
} else if (year.indexOf('/')) {
|
||||
yearValues = year.split('/');
|
||||
} else {
|
||||
yearValues = [year];
|
||||
}
|
||||
// 判断是否都是数字
|
||||
const checkYear = yearValues.some((item: any) => isNaN(Number(item)));
|
||||
if (checkYear) {
|
||||
return Promise.reject('表达式参数[年]错误: ' + year);
|
||||
}
|
||||
}
|
||||
// 取其中的前六项
|
||||
val = values.slice(0, 6).join(' ');
|
||||
}
|
||||
// 6位 没有年, 5位 没有秒年
|
||||
try {
|
||||
const iter = CronParser.parseExpression(val);
|
||||
iter.next();
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject('表达式错误: ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
export default cronValidator;
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-spin class="message-classify-container"
|
||||
:hide-icon="true"
|
||||
:loading="fetchLoading">
|
||||
<a-tabs v-model:activeKey="currentClassify"
|
||||
<a-tabs v-model:active-key="currentClassify"
|
||||
type="rounded"
|
||||
:hide-content="true"
|
||||
@change="loadClassifyMessage">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="重置密码"
|
||||
:top="120"
|
||||
|
||||
@@ -64,6 +64,9 @@ export const YMD_HMS = 'yyyy-MM-dd HH:mm:ss';
|
||||
* 格式化时间
|
||||
*/
|
||||
export function dateFormat(date = new Date(), pattern = YMD_HMS) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1,
|
||||
'd+': date.getDate(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="清空主机连接日志"
|
||||
:align-center="false"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
@@ -52,7 +52,8 @@
|
||||
<!-- 主机密钥 -->
|
||||
<a-form-item v-if="formModel.type === IdentityType.KEY"
|
||||
field="keyId"
|
||||
label="主机密钥">
|
||||
label="主机密钥"
|
||||
:hide-asterisk="true">
|
||||
<host-key-selector v-model="formModel.keyId" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@cancel="handleClose">
|
||||
<a-spin class="full modal-form-small" :loading="loading">
|
||||
<a-spin class="full drawer-form-small" :loading="loading">
|
||||
<a-alert class="keygen-alert">
|
||||
请使用 ssh-keygen -m PEM -t rsa 生成密钥
|
||||
</a-alert>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
|
||||
@@ -2,15 +2,15 @@ export default {
|
||||
'login.form.title': '登录 Orion Visor',
|
||||
'login.form.userName.errMsg': '用户名不能为空',
|
||||
'login.form.password.errMsg': '密码不能为空',
|
||||
'login.form.login.errMsg': '登录出错,轻刷新重试',
|
||||
'login.form.login.errMsg': '登录出错, 轻刷新重试',
|
||||
'login.form.login.success': '欢迎使用',
|
||||
'login.form.userName.placeholder': '用户名',
|
||||
'login.form.password.placeholder': '密码',
|
||||
'login.form.login': '登录',
|
||||
'login.banner.slogan1': '开箱即用的一站式智能运维平台',
|
||||
'login.banner.subSlogan1': '一站式操作 智能运维 让运维变得更简单',
|
||||
'login.banner.slogan2': '内置权限角色管理',
|
||||
'login.banner.subSlogan2': '让每一次操作都安全可控可追溯',
|
||||
'login.banner.slogan3': '终端操作无障碍',
|
||||
'login.banner.subSlogan3': '高效稳定 远程操作 让工作更高效',
|
||||
'login.banner.slogan1': '现代化的智能运维平台',
|
||||
'login.banner.subSlogan1': '一站式操作 让运维变得更简单',
|
||||
'login.banner.slogan2': '高颜值的轻量堡垒机平台',
|
||||
'login.banner.subSlogan2': '内置批量处理模块 让工作更高效',
|
||||
'login.banner.slogan3': '动态权限角色管理',
|
||||
'login.banner.subSlogan3': '让每一次操作都可追溯',
|
||||
};
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
await cancelUploadTask(taskId.value, false);
|
||||
taskStatus.value = UploadTaskStepStatus.WAITING;
|
||||
Message.success('已取消');
|
||||
taskId.value = undefined;
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -171,6 +172,8 @@
|
||||
|
||||
// 上传请求结束
|
||||
const uploadRequestEnd = async () => {
|
||||
// 上传请求结束后重置文件进度
|
||||
resetSelectedFileProgress();
|
||||
if (taskStatus.value.value === UploadTaskStepStatus.REQUESTING.value) {
|
||||
// 如果结束后还是请求中则代表请求完毕
|
||||
setLoading(true);
|
||||
@@ -196,6 +199,8 @@
|
||||
|
||||
// 上传请求失败
|
||||
const uploadRequestError = async () => {
|
||||
// 上传请求结束后重置文件进度
|
||||
resetSelectedFileProgress();
|
||||
setLoading(true);
|
||||
try {
|
||||
// 开始上传
|
||||
@@ -264,6 +269,11 @@
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
// 重置选择的文件进度
|
||||
const resetSelectedFileProgress = () => {
|
||||
fileList.value.forEach(s => s.percent = 0);
|
||||
};
|
||||
|
||||
// 设置轮询状态
|
||||
onMounted(() => {
|
||||
pullIntervalId.value = setInterval(pullTaskStatus, 5000);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="清空批量执行日志"
|
||||
:align-center="false"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@cancel="handleClose">
|
||||
<a-spin class="full modal-form-small" :loading="loading">
|
||||
<a-spin class="full drawer-form-small" :loading="loading">
|
||||
<!-- 命令表单 -->
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@cancel="handleClose">
|
||||
<a-spin class="full modal-form-small" :loading="loading">
|
||||
<a-spin class="full drawer-form-small" :loading="loading">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="right"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="清理上传任务"
|
||||
:top="80"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="修改权限"
|
||||
:align-center="false"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="touch ? '创建文件' : '创建文件夹'"
|
||||
:align-center="false"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="移动文件"
|
||||
:align-center="false"
|
||||
|
||||
@@ -165,11 +165,11 @@
|
||||
}
|
||||
|
||||
&-left {
|
||||
border-right: 1px solid var(--color-bg-content);
|
||||
border-right: 1px var(--color-bg-content) solid;
|
||||
}
|
||||
|
||||
&-right {
|
||||
border-left: 1px solid var(--color-bg-content);
|
||||
border-left: 1px var(--color-bg-content) solid;
|
||||
}
|
||||
|
||||
&-content {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="清空计划任务日志"
|
||||
:align-center="false"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@cancel="handleClose">
|
||||
<a-spin class="full modal-form-small" :loading="loading">
|
||||
<a-spin class="full drawer-form-small" :loading="loading">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="right"
|
||||
@@ -51,7 +51,12 @@
|
||||
placeholder="请输入 cron 表达式"
|
||||
allow-clear>
|
||||
<template #append>
|
||||
<span class="span-blue pointer usn"
|
||||
<span class="span-blue usn cron-action-item"
|
||||
title="生成 cron 表达式"
|
||||
@click="emits('genCron', formModel.expression)">
|
||||
生成
|
||||
</span>
|
||||
<span class="span-blue usn cron-action-item"
|
||||
title="获取 cron 下次执行时间"
|
||||
@click="emits('testCron', formModel.expression)">
|
||||
测试
|
||||
@@ -142,7 +147,7 @@
|
||||
import { useDictStore } from '@/store';
|
||||
import ExecEditor from '@/components/view/exec-editor/index.vue';
|
||||
|
||||
const emits = defineEmits(['added', 'updated', 'openHost', 'openTemplate', 'testCron']);
|
||||
const emits = defineEmits(['added', 'updated', 'openHost', 'openTemplate', 'testCron', 'genCron']);
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
@@ -205,6 +210,11 @@
|
||||
};
|
||||
};
|
||||
|
||||
// 设置表达式
|
||||
const setExpression = (expression: string) => {
|
||||
formModel.value.expression = expression;
|
||||
};
|
||||
|
||||
// 设置选中主机
|
||||
const setSelectedHost = (hosts: Array<number>) => {
|
||||
formModel.value.hostIdList = hosts;
|
||||
@@ -231,7 +241,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ openAdd, openUpdate, setSelectedHost, setWithTemplate });
|
||||
defineExpose({ openAdd, openUpdate, setSelectedHost, setWithTemplate, setExpression });
|
||||
|
||||
// 打开选择主机
|
||||
const openSelectHost = () => {
|
||||
@@ -320,4 +330,26 @@
|
||||
height: calc(100vh - 264px);
|
||||
}
|
||||
|
||||
:deep(.arco-input-append) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.cron-action-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
transition: background-color .2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-3);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-right: 1px var(--color-neutral-3) solid;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
@updated="modalUpdateCallback"
|
||||
@open-host="(e) => hostModal.open(e)"
|
||||
@open-template="() => templateModal.open()"
|
||||
@test-cron="openNextCron" />
|
||||
@test-cron="openNextCron"
|
||||
@gen-cron="openGeneratorCron" />
|
||||
<!-- 任务详情模态框 -->
|
||||
<exec-job-detail-drawer ref="detail" />
|
||||
<!-- cron 执行时间模态框 -->
|
||||
<next-cron-modal ref="nextCron" />
|
||||
<cron-next-modal ref="nextCron" />
|
||||
<!-- cron 生成模态框 -->
|
||||
<cron-generator-modal ref="genModal"
|
||||
@ok="(e) => drawer.setExpression(e)" />
|
||||
<!-- 执行模板模态框 -->
|
||||
<exec-template-modal ref="templateModal"
|
||||
@selected="(e) => drawer.setWithTemplate(e)" />
|
||||
@@ -40,14 +44,16 @@
|
||||
import ExecJobFormDrawer from './components/exec-job-form-drawer.vue';
|
||||
import ExecJobDetailDrawer from './components/exec-job-detail-drawer.vue';
|
||||
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
|
||||
import NextCronModal from '@/components/meta/expression/next-cron-modal/index.vue';
|
||||
import ExecTemplateModal from '@/components/exec/template/modal/index.vue';
|
||||
import CronNextModal from '@/components/meta/cron/next-modal/index.vue';
|
||||
import CronGeneratorModal from '@/components/meta/cron/generator-model/index.vue';
|
||||
|
||||
const render = ref(false);
|
||||
const table = ref();
|
||||
const drawer = ref();
|
||||
const detail = ref();
|
||||
const nextCron = ref();
|
||||
const genModal = ref();
|
||||
const templateModal = ref();
|
||||
const hostModal = ref();
|
||||
|
||||
@@ -66,6 +72,11 @@
|
||||
nextCron.value.open({ expression: cron, times: CronNextTimes });
|
||||
};
|
||||
|
||||
// 打开生成表达式
|
||||
const openGeneratorCron = (cron: string) => {
|
||||
genModal.value.open(cron);
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const dictStore = useDictStore();
|
||||
await dictStore.loadKeys(dictKeys);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="30"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="清空操作日志"
|
||||
:align-center="false"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-small"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="分配菜单"
|
||||
width="80%"
|
||||
@@ -15,7 +15,7 @@
|
||||
:on-before-ok="handlerOk"
|
||||
@close="handleClose">
|
||||
<div class="role-menu-wrapper">
|
||||
<a-spin :loading="loading">
|
||||
<a-spin class="full" :loading="loading">
|
||||
<a-alert class="usn mb8">
|
||||
<span>{{ roleRecord.name }} {{ roleRecord.code }}</span>
|
||||
<span class="mx8">-</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="分配角色"
|
||||
:top="120"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form-large"
|
||||
modal-class="modal-form-large"
|
||||
title-align="start"
|
||||
title="重置密码"
|
||||
:top="120"
|
||||
|
||||
Reference in New Issue
Block a user