🔨 批量上传.
This commit is contained in:
@@ -55,8 +55,8 @@ public class CodeGenerators {
|
|||||||
.enableRowSelection()
|
.enableRowSelection()
|
||||||
.dict("uploadTaskStatus", "status")
|
.dict("uploadTaskStatus", "status")
|
||||||
.comment("上传任务状态")
|
.comment("上传任务状态")
|
||||||
.fields("PREPARATION", "UPLOADING", "FINISHED", "CANCELED")
|
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED")
|
||||||
.labels("准备中", "上传中", "已完成", "已取消")
|
.labels("等待中", "上传中", "已完成", "已失败", "已取消")
|
||||||
.valueUseFields()
|
.valueUseFields()
|
||||||
.build(),
|
.build(),
|
||||||
Template.create("upload_task_file", "上传任务文件", "upload")
|
Template.create("upload_task_file", "上传任务文件", "upload")
|
||||||
@@ -65,8 +65,8 @@ public class CodeGenerators {
|
|||||||
.enableRowSelection()
|
.enableRowSelection()
|
||||||
.dict("uploadTaskFileStatus", "status")
|
.dict("uploadTaskFileStatus", "status")
|
||||||
.comment("上传任务文件状态")
|
.comment("上传任务文件状态")
|
||||||
.fields("WAITING", "UPLOADING", "FINISHED", "CANCELED")
|
.fields("WAITING", "UPLOADING", "FINISHED", "FAILED", "CANCELED")
|
||||||
.labels("等待中", "上传中", "已完成", "已取消")
|
.labels("等待中", "上传中", "已完成", "已失败", "已取消")
|
||||||
.valueUseFields()
|
.valueUseFields()
|
||||||
.build(),
|
.build(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ import java.util.List;
|
|||||||
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
|
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
|
||||||
public class UploadTaskController {
|
public class UploadTaskController {
|
||||||
|
|
||||||
|
// todo create 返回 host, STATUS
|
||||||
|
// 修改状态元数据
|
||||||
|
// 上船前检查size, size不对则直接cancel
|
||||||
|
// cancel 需要设置子元素为 cancel
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private UploadTaskService uploadTaskService;
|
private UploadTaskService uploadTaskService;
|
||||||
|
|
||||||
@@ -64,7 +69,7 @@ public class UploadTaskController {
|
|||||||
@Operation(summary = "取消上传")
|
@Operation(summary = "取消上传")
|
||||||
@PreAuthorize("@ss.hasPermission('asset:upload-task:upload')")
|
@PreAuthorize("@ss.hasPermission('asset:upload-task:upload')")
|
||||||
public Boolean cancelUploadTask(@Validated @RequestBody UploadTaskRequest request) {
|
public Boolean cancelUploadTask(@Validated @RequestBody UploadTaskRequest request) {
|
||||||
uploadTaskService.cancelUploadTask(request.getId());
|
uploadTaskService.cancelUploadTask(request);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,7 @@ public class UploadTaskRequest implements Serializable {
|
|||||||
@Schema(description = "id")
|
@Schema(description = "id")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "是否失败")
|
||||||
|
private Boolean failed;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import lombok.Getter;
|
|||||||
public enum UploadTaskStatusEnum {
|
public enum UploadTaskStatusEnum {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 准备中
|
* 等待中
|
||||||
*/
|
*/
|
||||||
PREPARATION(true),
|
WAITING(true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传中
|
* 上传中
|
||||||
@@ -29,6 +29,11 @@ public enum UploadTaskStatusEnum {
|
|||||||
*/
|
*/
|
||||||
FINISHED(false),
|
FINISHED(false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已失败
|
||||||
|
*/
|
||||||
|
FAILED(false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已取消
|
* 已取消
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class FileUploadTask implements IFileUploadTask {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检查任务状态 非准备中则取消执行
|
// 检查任务状态 非准备中则取消执行
|
||||||
if (!UploadTaskStatusEnum.PREPARATION.name().equals(record.getStatus())) {
|
if (!UploadTaskStatusEnum.WAITING.name().equals(record.getStatus())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.orion.ops.module.asset.service;
|
|||||||
import com.orion.lang.define.wrapper.DataGrid;
|
import com.orion.lang.define.wrapper.DataGrid;
|
||||||
import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
|
||||||
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
|
||||||
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest;
|
||||||
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
|
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
|
||||||
import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
|
import com.orion.ops.module.asset.entity.vo.UploadTaskVO;
|
||||||
|
|
||||||
@@ -85,9 +86,9 @@ public interface UploadTaskService {
|
|||||||
/**
|
/**
|
||||||
* 取消上传
|
* 取消上传
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param request request
|
||||||
*/
|
*/
|
||||||
void cancelUploadTask(Long id);
|
void cancelUploadTask(UploadTaskRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除上传交换区的文件
|
* 删除上传交换区的文件
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.orion.lang.define.wrapper.DataGrid;
|
import com.orion.lang.define.wrapper.DataGrid;
|
||||||
import com.orion.lang.utils.Arrays1;
|
import com.orion.lang.utils.Arrays1;
|
||||||
|
import com.orion.lang.utils.Booleans;
|
||||||
import com.orion.lang.utils.Strings;
|
import com.orion.lang.utils.Strings;
|
||||||
import com.orion.lang.utils.collect.Lists;
|
import com.orion.lang.utils.collect.Lists;
|
||||||
import com.orion.lang.utils.collect.Maps;
|
import com.orion.lang.utils.collect.Maps;
|
||||||
@@ -27,6 +28,7 @@ import com.orion.ops.module.asset.entity.dto.UploadTaskExtraDTO;
|
|||||||
import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskCreateRequest;
|
||||||
import com.orion.ops.module.asset.entity.request.upload.UploadTaskFileRequest;
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskFileRequest;
|
||||||
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskQueryRequest;
|
||||||
|
import com.orion.ops.module.asset.entity.request.upload.UploadTaskRequest;
|
||||||
import com.orion.ops.module.asset.entity.vo.HostBaseVO;
|
import com.orion.ops.module.asset.entity.vo.HostBaseVO;
|
||||||
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
|
import com.orion.ops.module.asset.entity.vo.UploadTaskCreateVO;
|
||||||
import com.orion.ops.module.asset.entity.vo.UploadTaskFileVO;
|
import com.orion.ops.module.asset.entity.vo.UploadTaskFileVO;
|
||||||
@@ -110,7 +112,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
|
|||||||
record.setUserId(user.getId());
|
record.setUserId(user.getId());
|
||||||
record.setUsername(user.getUsername());
|
record.setUsername(user.getUsername());
|
||||||
record.setDescription(Strings.def(record.getDescription(), () -> Strings.format(DEFAULT_DESC, Dates.current())));
|
record.setDescription(Strings.def(record.getDescription(), () -> Strings.format(DEFAULT_DESC, Dates.current())));
|
||||||
record.setStatus(UploadTaskStatusEnum.PREPARATION.name());
|
record.setStatus(UploadTaskStatusEnum.WAITING.name());
|
||||||
UploadTaskExtraDTO extra = UploadTaskExtraDTO.builder()
|
UploadTaskExtraDTO extra = UploadTaskExtraDTO.builder()
|
||||||
.hostIdList(hostIdList)
|
.hostIdList(hostIdList)
|
||||||
.hosts(hosts)
|
.hosts(hosts)
|
||||||
@@ -231,7 +233,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
|
|||||||
// 查询任务
|
// 查询任务
|
||||||
List<UploadTaskDO> records = uploadTaskDAO.selectBatchIds(idList);
|
List<UploadTaskDO> records = uploadTaskDAO.selectBatchIds(idList);
|
||||||
// 取消任务
|
// 取消任务
|
||||||
this.checkCancelTask(records);
|
this.checkCancelTask(records, UploadTaskStatusEnum.CANCELED);
|
||||||
// 删除任务
|
// 删除任务
|
||||||
int effect = uploadTaskDAO.deleteBatchIds(idList);
|
int effect = uploadTaskDAO.deleteBatchIds(idList);
|
||||||
// 删除任务文件
|
// 删除任务文件
|
||||||
@@ -248,20 +250,24 @@ public class UploadTaskServiceImpl implements UploadTaskService {
|
|||||||
UploadTaskDO record = uploadTaskDAO.selectById(id);
|
UploadTaskDO record = uploadTaskDAO.selectById(id);
|
||||||
Valid.notNull(record, ErrorMessage.TASK_ABSENT);
|
Valid.notNull(record, ErrorMessage.TASK_ABSENT);
|
||||||
// 检查状态
|
// 检查状态
|
||||||
Valid.eq(record.getStatus(), UploadTaskStatusEnum.PREPARATION.name(), ErrorMessage.ILLEGAL_STATUS);
|
Valid.eq(record.getStatus(), UploadTaskStatusEnum.WAITING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||||
// 执行任务
|
// 执行任务
|
||||||
FileUploadTasks.start(id);
|
FileUploadTasks.start(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelUploadTask(Long id) {
|
public void cancelUploadTask(UploadTaskRequest request) {
|
||||||
// 查询任务
|
// 查询任务
|
||||||
UploadTaskDO record = uploadTaskDAO.selectById(id);
|
UploadTaskDO record = uploadTaskDAO.selectById(request.getId());
|
||||||
Valid.notNull(record, ErrorMessage.TASK_ABSENT);
|
Valid.notNull(record, ErrorMessage.TASK_ABSENT);
|
||||||
// 检查状态
|
// 检查状态
|
||||||
Valid.isTrue(UploadTaskStatusEnum.of(record.getStatus()).isCancelable(), ErrorMessage.ILLEGAL_STATUS);
|
Valid.isTrue(UploadTaskStatusEnum.of(record.getStatus()).isCancelable(), ErrorMessage.ILLEGAL_STATUS);
|
||||||
// 取消任务
|
// 取消任务
|
||||||
this.checkCancelTask(Lists.singleton(record));
|
if (Booleans.isTrue(request.getFailed())) {
|
||||||
|
this.checkCancelTask(Lists.singleton(record), UploadTaskStatusEnum.FAILED);
|
||||||
|
} else {
|
||||||
|
this.checkCancelTask(Lists.singleton(record), UploadTaskStatusEnum.CANCELED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -318,8 +324,9 @@ public class UploadTaskServiceImpl implements UploadTaskService {
|
|||||||
* 检查需要取消的任务
|
* 检查需要取消的任务
|
||||||
*
|
*
|
||||||
* @param records records
|
* @param records records
|
||||||
|
* @param status status
|
||||||
*/
|
*/
|
||||||
private void checkCancelTask(List<UploadTaskDO> records) {
|
private void checkCancelTask(List<UploadTaskDO> records, UploadTaskStatusEnum status) {
|
||||||
// 需要取消的记录
|
// 需要取消的记录
|
||||||
List<UploadTaskDO> cancelableRecords = records.stream()
|
List<UploadTaskDO> cancelableRecords = records.stream()
|
||||||
.filter(s -> UploadTaskStatusEnum.of(s.getStatus()).isCancelable())
|
.filter(s -> UploadTaskStatusEnum.of(s.getStatus()).isCancelable())
|
||||||
@@ -341,7 +348,7 @@ public class UploadTaskServiceImpl implements UploadTaskService {
|
|||||||
// 更新状态
|
// 更新状态
|
||||||
if (!updateIdList.isEmpty()) {
|
if (!updateIdList.isEmpty()) {
|
||||||
UploadTaskDO update = new UploadTaskDO();
|
UploadTaskDO update = new UploadTaskDO();
|
||||||
update.setStatus(UploadTaskStatusEnum.CANCELED.name());
|
update.setStatus(status.name());
|
||||||
update.setEndTime(new Date());
|
update.setEndTime(new Date());
|
||||||
// 更新
|
// 更新
|
||||||
uploadTaskDAO.update(update, Conditions.in(UploadTaskDO::getId, updateIdList));
|
uploadTaskDAO.update(update, Conditions.in(UploadTaskDO::getId, updateIdList));
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ public class FileUploadMessageDispatcher extends AbstractWebSocketHandler {
|
|||||||
} else if (FileUploadOperatorType.FINISH.equals(type)) {
|
} else if (FileUploadOperatorType.FINISH.equals(type)) {
|
||||||
// 上传完成
|
// 上传完成
|
||||||
handler.finish();
|
handler.finish();
|
||||||
|
} else if (FileUploadOperatorType.ERROR.equals(type)) {
|
||||||
|
// 上传失败
|
||||||
|
handler.error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ public enum FileUploadOperatorType {
|
|||||||
*/
|
*/
|
||||||
FINISH("finish"),
|
FINISH("finish"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传失败
|
||||||
|
*/
|
||||||
|
ERROR("error"),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
|
|||||||
@@ -106,6 +106,18 @@ public class FileUploadHandler implements IFileUploadHandler {
|
|||||||
this.send(resp);
|
this.send(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error() {
|
||||||
|
// 释放资源
|
||||||
|
this.close();
|
||||||
|
// 返回上传路径
|
||||||
|
FileUploadResponse resp = FileUploadResponse.builder()
|
||||||
|
.type(FileUploadReceiverType.ERROR.getType())
|
||||||
|
.fileId(this.fileId)
|
||||||
|
.build();
|
||||||
|
this.send(resp);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
|
|||||||
@@ -30,4 +30,9 @@ public interface IFileUploadHandler extends SafeCloseable {
|
|||||||
*/
|
*/
|
||||||
void finish();
|
void finish();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传失败
|
||||||
|
*/
|
||||||
|
void error();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,6 @@ export interface UploadTaskCreateResponse {
|
|||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传任务请求
|
|
||||||
*/
|
|
||||||
export interface UploadTaskRequest {
|
|
||||||
id?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传任务查询请求
|
* 上传任务查询请求
|
||||||
*/
|
*/
|
||||||
@@ -92,15 +85,15 @@ export function createUploadTask(request: UploadTaskCreateRequest) {
|
|||||||
/**
|
/**
|
||||||
* 创建上传任务
|
* 创建上传任务
|
||||||
*/
|
*/
|
||||||
export function startUploadTask(request: UploadTaskRequest) {
|
export function startUploadTask(id: number) {
|
||||||
return axios.post('/asset/upload-task/start', request);
|
return axios.post('/asset/upload-task/start', { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建上传任务
|
* 创建上传任务
|
||||||
*/
|
*/
|
||||||
export function cancelUploadTask(request: UploadTaskRequest) {
|
export function cancelUploadTask(id: number, failed: boolean) {
|
||||||
return axios.post('/asset/upload-task/cancel', request);
|
return axios.post('/asset/upload-task/cancel', { id, failed });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { createAppWebSocket } from '@/utils/http';
|
|||||||
* 打开文件上传 websocket
|
* 打开文件上传 websocket
|
||||||
*/
|
*/
|
||||||
export const openFileUploadChannel = (uploadToken: string) => {
|
export const openFileUploadChannel = (uploadToken: string) => {
|
||||||
return createAppWebSocket(`"/file/upload/${uploadToken}`);
|
return createAppWebSocket(`/file/upload/${uploadToken}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import type { IDisposable, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from 'xterm';
|
||||||
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import type { SearchAddon } from 'xterm-addon-search';
|
||||||
|
import type { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
|
import type { WebglAddon } from 'xterm-addon-webgl';
|
||||||
|
|
||||||
// 执行类型
|
// 执行类型
|
||||||
export type ExecType = 'BATCH' | 'JOB';
|
export type ExecType = 'BATCH' | 'JOB';
|
||||||
|
|
||||||
@@ -37,3 +43,97 @@ export const execHostStatusKey = 'execHostStatus';
|
|||||||
|
|
||||||
// 加载的字典值
|
// 加载的字典值
|
||||||
export const dictKeys = [execStatusKey, execHostStatusKey];
|
export const dictKeys = [execStatusKey, execHostStatusKey];
|
||||||
|
|
||||||
|
// appender 配置
|
||||||
|
export const LogAppenderOptions: ITerminalOptions & ITerminalInitOnlyOptions = {
|
||||||
|
theme: {
|
||||||
|
foreground: '#FFFFFF',
|
||||||
|
background: '#1C1C1C',
|
||||||
|
selectionBackground: '#444444',
|
||||||
|
},
|
||||||
|
cols: 30,
|
||||||
|
rows: 8,
|
||||||
|
rightClickSelectsWord: true,
|
||||||
|
disableStdin: true,
|
||||||
|
cursorStyle: 'bar',
|
||||||
|
cursorBlink: false,
|
||||||
|
fastScrollModifier: 'alt',
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 1.12,
|
||||||
|
convertEol: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// dom 引用
|
||||||
|
export interface LogDomRef {
|
||||||
|
id: number;
|
||||||
|
el: HTMLElement;
|
||||||
|
openSearch: () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// appender 配置
|
||||||
|
export interface LogAppenderConf {
|
||||||
|
id: number;
|
||||||
|
el: HTMLElement;
|
||||||
|
openSearch: () => {};
|
||||||
|
terminal: Terminal;
|
||||||
|
addons: LogAddons;
|
||||||
|
}
|
||||||
|
|
||||||
|
// appender 插件
|
||||||
|
export interface LogAddons extends Record<string, IDisposable> {
|
||||||
|
fit: FitAddon;
|
||||||
|
webgl: WebglAddon;
|
||||||
|
search: SearchAddon;
|
||||||
|
weblink: WebLinksAddon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行日志 appender 定义
|
||||||
|
export interface ILogAppender {
|
||||||
|
// 初始化
|
||||||
|
init(refs: Array<LogDomRef>): Promise<void>;
|
||||||
|
|
||||||
|
// 设置当前元素
|
||||||
|
setCurrent(id: number): void;
|
||||||
|
|
||||||
|
// 打开搜索
|
||||||
|
openSearch(): void;
|
||||||
|
|
||||||
|
// 查找关键字
|
||||||
|
find(word: string, next: boolean, options: any): void;
|
||||||
|
|
||||||
|
// 聚焦
|
||||||
|
focus(): void;
|
||||||
|
|
||||||
|
// 自适应
|
||||||
|
fitAll(): void;
|
||||||
|
|
||||||
|
// 去顶部
|
||||||
|
toTop(): void;
|
||||||
|
|
||||||
|
// 去底部
|
||||||
|
toBottom(): void;
|
||||||
|
|
||||||
|
// 添加字体大小
|
||||||
|
addFontSize(addSize: number): void;
|
||||||
|
|
||||||
|
// 复制
|
||||||
|
copy(): void;
|
||||||
|
|
||||||
|
// 复制全部
|
||||||
|
copyAll(): void;
|
||||||
|
|
||||||
|
// 选中全部
|
||||||
|
selectAll(): void;
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
clear(): void;
|
||||||
|
|
||||||
|
// 关闭 client
|
||||||
|
closeClient(): void;
|
||||||
|
|
||||||
|
// 关闭 view
|
||||||
|
closeView(): void;
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import type { IDisposable, ITerminalInitOnlyOptions, ITerminalOptions, Terminal } from 'xterm';
|
|
||||||
import type { FitAddon } from 'xterm-addon-fit';
|
|
||||||
import type { SearchAddon } from 'xterm-addon-search';
|
|
||||||
import type { WebLinksAddon } from 'xterm-addon-web-links';
|
|
||||||
import type { WebglAddon } from 'xterm-addon-webgl';
|
|
||||||
|
|
||||||
// appender 配置
|
|
||||||
export const LogAppenderOptions: ITerminalOptions & ITerminalInitOnlyOptions = {
|
|
||||||
theme: {
|
|
||||||
foreground: '#FFFFFF',
|
|
||||||
background: '#1C1C1C',
|
|
||||||
selectionBackground: '#444444',
|
|
||||||
},
|
|
||||||
cols: 30,
|
|
||||||
rows: 8,
|
|
||||||
rightClickSelectsWord: true,
|
|
||||||
disableStdin: true,
|
|
||||||
cursorStyle: 'bar',
|
|
||||||
cursorBlink: false,
|
|
||||||
fastScrollModifier: 'alt',
|
|
||||||
fontSize: 13,
|
|
||||||
lineHeight: 1.12,
|
|
||||||
convertEol: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// dom 引用
|
|
||||||
export interface LogDomRef {
|
|
||||||
id: number;
|
|
||||||
el: HTMLElement;
|
|
||||||
openSearch: () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// appender 配置
|
|
||||||
export interface LogAppenderConf {
|
|
||||||
id: number;
|
|
||||||
el: HTMLElement;
|
|
||||||
openSearch: () => {};
|
|
||||||
terminal: Terminal;
|
|
||||||
addons: LogAddons;
|
|
||||||
}
|
|
||||||
|
|
||||||
// appender 插件
|
|
||||||
export interface LogAddons extends Record<string, IDisposable> {
|
|
||||||
fit: FitAddon;
|
|
||||||
webgl: WebglAddon;
|
|
||||||
search: SearchAddon;
|
|
||||||
weblink: WebLinksAddon;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行日志 appender 定义
|
|
||||||
export interface ILogAppender {
|
|
||||||
// 初始化
|
|
||||||
init(refs: Array<LogDomRef>): Promise<void>;
|
|
||||||
|
|
||||||
// 设置当前元素
|
|
||||||
setCurrent(id: number): void;
|
|
||||||
|
|
||||||
// 打开搜索
|
|
||||||
openSearch(): void;
|
|
||||||
|
|
||||||
// 查找关键字
|
|
||||||
find(word: string, next: boolean, options: any): void;
|
|
||||||
|
|
||||||
// 聚焦
|
|
||||||
focus(): void;
|
|
||||||
|
|
||||||
// 自适应
|
|
||||||
fitAll(): void;
|
|
||||||
|
|
||||||
// 去顶部
|
|
||||||
toTop(): void;
|
|
||||||
|
|
||||||
// 去底部
|
|
||||||
toBottom(): void;
|
|
||||||
|
|
||||||
// 添加字体大小
|
|
||||||
addFontSize(addSize: number): void;
|
|
||||||
|
|
||||||
// 复制
|
|
||||||
copy(): void;
|
|
||||||
|
|
||||||
// 复制全部
|
|
||||||
copyAll(): void;
|
|
||||||
|
|
||||||
// 选中全部
|
|
||||||
selectAll(): void;
|
|
||||||
|
|
||||||
// 清空
|
|
||||||
clear(): void;
|
|
||||||
|
|
||||||
// 关闭 client
|
|
||||||
closeClient(): void;
|
|
||||||
|
|
||||||
// 关闭 view
|
|
||||||
closeView(): void;
|
|
||||||
|
|
||||||
// 关闭
|
|
||||||
close(): void;
|
|
||||||
}
|
|
||||||
@@ -25,8 +25,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||||
import type { ILogAppender } from './appender-const';
|
import type { ExecType, ILogAppender } from '../const';
|
||||||
import type { ExecType } from '../const';
|
|
||||||
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
|
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
|
||||||
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
|
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
|
||||||
import { getExecJobLogStatus } from '@/api/job/exec-job-log';
|
import { getExecJobLogStatus } from '@/api/job/exec-job-log';
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from './appender-const';
|
import type { ExecType, ILogAppender, LogAddons, LogAppenderConf, LogDomRef } from '../const';
|
||||||
import { LogAppenderOptions } from './appender-const';
|
|
||||||
import type { ExecType } from '../const';
|
|
||||||
import type { ExecLogTailRequest } from '@/api/exec/exec-log';
|
import type { ExecLogTailRequest } from '@/api/exec/exec-log';
|
||||||
import { openExecLogChannel } from '@/api/exec/exec-log';
|
import { openExecLogChannel } from '@/api/exec/exec-log';
|
||||||
import { getExecCommandLogTailToken } from '@/api/exec/exec-command-log';
|
import { getExecCommandLogTailToken } from '@/api/exec/exec-command-log';
|
||||||
import { getExecJobLogTailToken } from '@/api/job/exec-job-log';
|
import { getExecJobLogTailToken } from '@/api/job/exec-job-log';
|
||||||
|
import { LogAppenderOptions } from '../const';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
import { addEventListen, removeEventListen } from '@/utils/event';
|
import { addEventListen, removeEventListen } from '@/utils/event';
|
||||||
|
|||||||
@@ -167,8 +167,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
|
import type { ExecLogQueryResponse, ExecHostLogQueryResponse } from '@/api/exec/exec-log';
|
||||||
import type { ILogAppender } from './appender-const';
|
import type { ExecType, ILogAppender } from '../const';
|
||||||
import type { ExecType } from '../const';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { execHostStatus, execHostStatusKey } from '../const';
|
import { execHostStatus, execHostStatusKey } from '../const';
|
||||||
import { formatDuration } from '@/utils';
|
import { formatDuration } from '@/utils';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VNodeRef } from 'vue';
|
import type { VNodeRef } from 'vue';
|
||||||
import type { LogDomRef, ILogAppender } from './appender-const';
|
import type { LogDomRef, ILogAppender } from '../const';
|
||||||
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||||
import type { ExecType } from '../const';
|
import type { ExecType } from '../const';
|
||||||
import { nextTick, ref, watch } from 'vue';
|
import { nextTick, ref, watch } from 'vue';
|
||||||
|
|||||||
44
orion-ops-ui/src/components/system/uploader/const.ts
Normal file
44
orion-ops-ui/src/components/system/uploader/const.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// 上传操作类型
|
||||||
|
export const UploadOperatorType = {
|
||||||
|
// 开始上传
|
||||||
|
START: 'start',
|
||||||
|
// 上传完成
|
||||||
|
FINISH: 'finish',
|
||||||
|
// 上传失败
|
||||||
|
ERROR: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传响应类型
|
||||||
|
export const UploadReceiverType = {
|
||||||
|
// 请求下一块数据
|
||||||
|
NEXT: 'next',
|
||||||
|
// 上传完成
|
||||||
|
FINISH: 'finish',
|
||||||
|
// 上传失败
|
||||||
|
ERROR: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 请求消息体
|
||||||
|
export interface RequestMessageBody {
|
||||||
|
type: string;
|
||||||
|
fileId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应消息体
|
||||||
|
export interface ResponseMessageBody {
|
||||||
|
type: string;
|
||||||
|
fileId: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件上传器 定义
|
||||||
|
export interface IFileUploader {
|
||||||
|
// 开始
|
||||||
|
start(): Promise<void>;
|
||||||
|
|
||||||
|
// 设置 hook
|
||||||
|
setHook(hook: Function): void;
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
146
orion-ops-ui/src/components/system/uploader/file-uploader.ts
Normal file
146
orion-ops-ui/src/components/system/uploader/file-uploader.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import type { IFileUploader, ResponseMessageBody } from './const';
|
||||||
|
import type { FileItem } from '@arco-design/web-vue';
|
||||||
|
import { openFileUploadChannel } from '@/api/system/upload';
|
||||||
|
import { UploadOperatorType, UploadReceiverType } from './const';
|
||||||
|
|
||||||
|
// 512 KB
|
||||||
|
export const PART_SIZE = 512 * 1024;
|
||||||
|
|
||||||
|
// 文件上传器 实现
|
||||||
|
export default class FileUploader implements IFileUploader {
|
||||||
|
|
||||||
|
private readonly token: string;
|
||||||
|
|
||||||
|
private readonly fileList: Array<FileItem>;
|
||||||
|
|
||||||
|
private currentIndex: number;
|
||||||
|
|
||||||
|
private currentFileItem: FileItem;
|
||||||
|
|
||||||
|
private currentFile: File;
|
||||||
|
|
||||||
|
private currentFileSize: number;
|
||||||
|
|
||||||
|
private currentPart: number;
|
||||||
|
|
||||||
|
private totalPart: number;
|
||||||
|
|
||||||
|
private client?: WebSocket;
|
||||||
|
|
||||||
|
private hook?: Function;
|
||||||
|
|
||||||
|
constructor(token: string, fileList: Array<FileItem>) {
|
||||||
|
this.token = token;
|
||||||
|
this.fileList = fileList;
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.currentFileItem = undefined as unknown as FileItem;
|
||||||
|
this.currentFile = undefined as unknown as File;
|
||||||
|
this.currentFileSize = 0;
|
||||||
|
this.currentPart = 0;
|
||||||
|
this.totalPart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始
|
||||||
|
async start(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 打开管道
|
||||||
|
this.client = await openFileUploadChannel(this.token);
|
||||||
|
this.client.onclose = () => {
|
||||||
|
this.hook && this.hook();
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// 修改状态
|
||||||
|
this.fileList.forEach(s => s.status = 'error');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// 处理消息
|
||||||
|
this.client.onmessage = this.resolveMessage.bind(this);
|
||||||
|
// 打开后自动上传下一个文件
|
||||||
|
this.uploadNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传下一个文件
|
||||||
|
private uploadNextFile() {
|
||||||
|
// 获取文件
|
||||||
|
if (this.fileList.length > this.currentIndex) {
|
||||||
|
this.currentFileItem = this.fileList[this.currentIndex++];
|
||||||
|
this.currentFile = this.currentFileItem.file as File;
|
||||||
|
this.currentFileSize = 0;
|
||||||
|
this.currentPart = 0;
|
||||||
|
this.totalPart = Math.ceil(this.currentFile.size / PART_SIZE);
|
||||||
|
// 开始上传 发送开始上传信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
type: UploadOperatorType.START,
|
||||||
|
fileId: this.currentFileItem.uid,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// 无文件关闭会话
|
||||||
|
this.client?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传下一块数据
|
||||||
|
private async uploadNextPart() {
|
||||||
|
try {
|
||||||
|
if (this.currentPart < this.totalPart) {
|
||||||
|
// 有下一个分片则上传
|
||||||
|
const start = this.currentPart++ * PART_SIZE;
|
||||||
|
const end = Math.min(this.currentFile.size, start + PART_SIZE);
|
||||||
|
const chunk = this.currentFile.slice(start, end);
|
||||||
|
const reader = new FileReader();
|
||||||
|
// 读取数据
|
||||||
|
const arrayBuffer = await new Promise((resolve, reject) => {
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
reader.readAsArrayBuffer(chunk);
|
||||||
|
});
|
||||||
|
// 发送数据
|
||||||
|
this.client?.send(arrayBuffer as ArrayBuffer);
|
||||||
|
// 计算进度
|
||||||
|
this.currentFileSize += (end - start);
|
||||||
|
this.currentFileItem.percent = (this.currentFileSize / this.currentFile.size);
|
||||||
|
} else {
|
||||||
|
// 没有下一个分片则发送完成
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
type: UploadOperatorType.FINISH,
|
||||||
|
fileId: this.currentFileItem.uid,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 读取文件失败
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
type: UploadOperatorType.ERROR,
|
||||||
|
fileId: this.currentFileItem.uid,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收消息
|
||||||
|
private async resolveMessage(message: MessageEvent) {
|
||||||
|
// 文本消息
|
||||||
|
const data = JSON.parse(message.data) as ResponseMessageBody;
|
||||||
|
if (data.type === UploadReceiverType.NEXT) {
|
||||||
|
// 上传下一块数据
|
||||||
|
await this.uploadNextPart();
|
||||||
|
} else if (data.type === UploadReceiverType.FINISH) {
|
||||||
|
this.currentFileItem.status = 'done';
|
||||||
|
// 上传下一个文件
|
||||||
|
this.uploadNextFile();
|
||||||
|
} else if (data.type === UploadReceiverType.ERROR) {
|
||||||
|
this.currentFileItem.status = 'error';
|
||||||
|
// 上传下一个文件
|
||||||
|
this.uploadNextFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 hook
|
||||||
|
setHook(hook: Function): void {
|
||||||
|
this.hook = hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
close(): void {
|
||||||
|
this.client?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3>文件列表</h3>
|
<h3>文件列表</h3>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<a-button-group size="small">
|
<a-button-group size="small" :disabled="startStatus">
|
||||||
<a-button @click="clear">清空</a-button>
|
<a-button @click="clear">清空</a-button>
|
||||||
<!-- 选择文件 -->
|
<!-- 选择文件 -->
|
||||||
<a-upload v-model:file-list="fileList"
|
<a-upload v-model:file-list="fileList"
|
||||||
@@ -29,11 +29,12 @@
|
|||||||
<!-- 文件列表 -->
|
<!-- 文件列表 -->
|
||||||
<div v-if="fileList.length" class="files-container">
|
<div v-if="fileList.length" class="files-container">
|
||||||
<a-upload class="files-wrapper"
|
<a-upload class="files-wrapper"
|
||||||
:class="['waiting-files-wrapper']"
|
:class="[ startStatus ? 'uploading-files-wrapper' : 'waiting-files-wrapper' ]"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
:show-cancel-button="false"
|
:show-cancel-button="false"
|
||||||
:show-remove-button="true"
|
:show-retry-button="false"
|
||||||
|
:show-remove-button="!startStatus"
|
||||||
:show-file-list="true">
|
:show-file-list="true">
|
||||||
<template #upload-button />
|
<template #upload-button />
|
||||||
<template #file-name="{ fileItem }">
|
<template #file-name="{ fileItem }">
|
||||||
@@ -71,15 +72,61 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { FileItem } from '@arco-design/web-vue';
|
import type { FileItem } from '@arco-design/web-vue';
|
||||||
|
import type { UploadTaskFileCreateRequest } from '@/api/exec/upload-task';
|
||||||
|
import type { IFileUploader } from '@/components/system/uploader/const';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { getFileSize } from '@/utils/file';
|
import { getFileSize } from '@/utils/file';
|
||||||
|
import FileUploader from '@/components/system/uploader/file-uploader';
|
||||||
|
|
||||||
|
const emits = defineEmits(['end', 'error']);
|
||||||
|
|
||||||
|
const startStatus = ref(false);
|
||||||
const fileList = ref<FileItem[]>([]);
|
const fileList = ref<FileItem[]>([]);
|
||||||
|
const uploader = ref<IFileUploader>();
|
||||||
|
|
||||||
|
// 获取上传的文件
|
||||||
|
const getFiles = (): Array<UploadTaskFileCreateRequest> => {
|
||||||
|
return fileList.value
|
||||||
|
.map(s => {
|
||||||
|
return {
|
||||||
|
fileId: s.uid,
|
||||||
|
filePath: s.file?.webkitRelativePath || s.file?.name,
|
||||||
|
fileSize: s.file?.size,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始上传
|
||||||
|
const startUpload = async (token: string) => {
|
||||||
|
// 修改状态
|
||||||
|
startStatus.value = true;
|
||||||
|
fileList.value.forEach(s => s.status = 'uploading');
|
||||||
|
// 开始上传
|
||||||
|
try {
|
||||||
|
uploader.value = new FileUploader(token, fileList.value);
|
||||||
|
uploader.value?.setHook(() => {
|
||||||
|
emits('end');
|
||||||
|
});
|
||||||
|
await uploader.value?.start();
|
||||||
|
} catch (e) {
|
||||||
|
emits('error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 清空
|
// 清空
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
|
fileList.value = [];
|
||||||
|
startStatus.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 关闭
|
||||||
|
const close = () => {
|
||||||
|
startStatus.value = false;
|
||||||
|
uploader.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ getFiles, startUpload, close });
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -127,11 +174,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-upload-list) {
|
:deep(.arco-upload-list) {
|
||||||
max-height: 100%;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.arco-upload-list-item-error) {
|
||||||
|
.arco-upload-list-item-name {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-upload-progress {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.arco-upload-list-item-name-text) {
|
:deep(.arco-upload-list-item-name-text) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,24 @@
|
|||||||
<h3>批量上传</h3>
|
<h3>批量上传</h3>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<a-button-group size="small">
|
<a-button-group size="small">
|
||||||
<a-button>重置</a-button>
|
<!-- 重置 -->
|
||||||
<a-button type="primary">上传</a-button>
|
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.value"
|
||||||
|
@click="emits('clear')">
|
||||||
|
重置
|
||||||
|
</a-button>
|
||||||
|
<!-- 取消上传 -->
|
||||||
|
<a-button v-if="status.value === UploadTaskStatus.REQUESTING.value
|
||||||
|
|| status.value === UploadTaskStatus.UPLOADING.value"
|
||||||
|
@click="emits('cancel')">
|
||||||
|
取消上传
|
||||||
|
</a-button>
|
||||||
|
<!-- 开始上传 -->
|
||||||
|
<a-button v-if="status.value !== UploadTaskStatus.REQUESTING.value
|
||||||
|
&& status.value !== UploadTaskStatus.UPLOADING.value"
|
||||||
|
type="primary"
|
||||||
|
@click="submit">
|
||||||
|
开始上传
|
||||||
|
</a-button>
|
||||||
</a-button-group>
|
</a-button-group>
|
||||||
</div>
|
</div>
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
@@ -35,7 +51,7 @@
|
|||||||
<span class="usn" v-if="formModel.hostIdList?.length">
|
<span class="usn" v-if="formModel.hostIdList?.length">
|
||||||
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
|
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
|
||||||
</span>
|
</span>
|
||||||
<span class="usn pointer span-blue" @click="openSelectHost">
|
<span class="usn pointer span-blue" @click="emits('openHost')">
|
||||||
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
|
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,28 +68,27 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
|
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
|
||||||
|
import type { UploadTaskStatusType } from '../types/const';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import formRules from '../types/form.rules';
|
import formRules from '../types/form.rules';
|
||||||
import useLoading from '@/hooks/loading';
|
import { UploadTaskStatus } from '../types/const';
|
||||||
|
|
||||||
const defaultForm = (): UploadTaskCreateRequest => {
|
const emits = defineEmits(['upload', 'openHost', 'cancel', 'clear']);
|
||||||
return {
|
const props = defineProps<{
|
||||||
description: '',
|
status: UploadTaskStatusType;
|
||||||
remotePath: '',
|
formModel: UploadTaskCreateRequest;
|
||||||
hostIdList: [],
|
}>();
|
||||||
files: []
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading();
|
|
||||||
|
|
||||||
const formRef = ref<any>();
|
const formRef = ref<any>();
|
||||||
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
|
|
||||||
const hostModal = ref<any>();
|
|
||||||
|
|
||||||
// 打开选择主机
|
// 提交表单
|
||||||
const openSelectHost = () => {
|
const submit = async () => {
|
||||||
hostModal.value.open(formModel.value.hostIdList);
|
// 验证参数
|
||||||
|
let error = await formRef.value.validate();
|
||||||
|
if (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
emits('upload');
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="panel-container">
|
<a-spin class="panel-container full" :loading="loading">
|
||||||
<!-- 上传步骤 -->
|
<!-- 上传步骤 -->
|
||||||
<batch-upload-step class="panel-item step-panel-container"
|
<batch-upload-step class="panel-item step-panel-container"
|
||||||
:step="step"
|
:status="status" />
|
||||||
:status="stepStatus" />
|
|
||||||
<!-- 上传表单 -->
|
<!-- 上传表单 -->
|
||||||
<batch-upload-form class="panel-item form-panel-container" />
|
<batch-upload-form class="panel-item form-panel-container"
|
||||||
|
:form-model="formModel"
|
||||||
|
:status="status"
|
||||||
|
@upload="doCreateUploadTask"
|
||||||
|
@cancel="doCancelUploadTask"
|
||||||
|
@open-host="openHostModal"
|
||||||
|
@clear="clear" />
|
||||||
<!-- 上传文件 -->
|
<!-- 上传文件 -->
|
||||||
<batch-upload-files class="panel-item files-panel-container" />
|
<batch-upload-files class="panel-item files-panel-container"
|
||||||
</div>
|
ref="filesRef"
|
||||||
|
@end="uploadRequestEnd"
|
||||||
|
@error="uploadRequestError" />
|
||||||
|
<!-- 主机模态框 -->
|
||||||
|
<authorized-host-modal ref="hostModal"
|
||||||
|
@selected="setSelectedHost" />
|
||||||
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -18,14 +29,125 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
|
||||||
|
import type { UploadTaskStatusType } from '../types/const';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { UploadStep, UploadStepStatus } from '../types/const';
|
import { UploadTaskStatus } from '../types/const';
|
||||||
|
import { cancelUploadTask, createUploadTask, startUploadTask } from '@/api/exec/upload-task';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
import BatchUploadStep from './batch-upload-step.vue';
|
import BatchUploadStep from './batch-upload-step.vue';
|
||||||
import BatchUploadForm from './batch-upload-form.vue';
|
import BatchUploadForm from './batch-upload-form.vue';
|
||||||
import BatchUploadFiles from '@/views/exec/batch-upload/components/batch-upload-files.vue';
|
import BatchUploadFiles from './batch-upload-files.vue';
|
||||||
|
import AuthorizedHostModal from '@/components/asset/host/authorized-host-modal/index.vue';
|
||||||
|
|
||||||
const step = ref(UploadStep.PREPARATION);
|
const defaultForm = (): UploadTaskCreateRequest => {
|
||||||
const stepStatus = ref(UploadStepStatus.PROCESS);
|
return {
|
||||||
|
description: '',
|
||||||
|
remotePath: '/root/batch',
|
||||||
|
hostIdList: [1],
|
||||||
|
files: []
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
|
const taskId = ref();
|
||||||
|
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
|
||||||
|
const status = ref<UploadTaskStatusType>(UploadTaskStatus.WAITING);
|
||||||
|
const filesRef = ref();
|
||||||
|
const hostModal = ref<any>();
|
||||||
|
|
||||||
|
// TODO pullstatus
|
||||||
|
// TODO 测试 error 情况
|
||||||
|
|
||||||
|
// 设置选中主机
|
||||||
|
const setSelectedHost = (hosts: Array<number>) => {
|
||||||
|
formModel.value.hostIdList = hosts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建上传任务
|
||||||
|
const doCreateUploadTask = async () => {
|
||||||
|
// 获取文件
|
||||||
|
const files = filesRef.value?.getFiles();
|
||||||
|
if (!files || !files.length) {
|
||||||
|
Message.error('请先选择需要上传的文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 创建任务
|
||||||
|
setLoading(true);
|
||||||
|
status.value = UploadTaskStatus.WAITING;
|
||||||
|
try {
|
||||||
|
formModel.value.files = files;
|
||||||
|
const { data } = await createUploadTask(formModel.value);
|
||||||
|
taskId.value = data.id;
|
||||||
|
status.value = UploadTaskStatus.REQUESTING;
|
||||||
|
// 上传文件
|
||||||
|
await filesRef.value.startUpload(data.token);
|
||||||
|
} catch (e) {
|
||||||
|
status.value = UploadTaskStatus.FAILED;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消上传任务
|
||||||
|
const doCancelUploadTask = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
filesRef.value?.close();
|
||||||
|
try {
|
||||||
|
// 取消上传
|
||||||
|
await cancelUploadTask(taskId.value, false);
|
||||||
|
status.value = UploadTaskStatus.CANCELED;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传请求结束
|
||||||
|
const uploadRequestEnd = async () => {
|
||||||
|
if (status.value.value !== UploadTaskStatus.REQUESTING.value) {
|
||||||
|
// 手动停止或者其他原因
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果结束后还是请求中则代表请求完毕
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 开始上传
|
||||||
|
await startUploadTask(taskId.value);
|
||||||
|
status.value = UploadTaskStatus.UPLOADING;
|
||||||
|
} catch (e) {
|
||||||
|
// 设置失败
|
||||||
|
await uploadRequestError();
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传请求失败
|
||||||
|
const uploadRequestError = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// 开始上传
|
||||||
|
await cancelUploadTask(taskId.value, true);
|
||||||
|
status.value = UploadTaskStatus.FAILED;
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开主机模态框
|
||||||
|
const openHostModal = () => {
|
||||||
|
hostModal.value.open(formModel.value.hostIdList);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
const clear = () => {
|
||||||
|
status.value = UploadTaskStatus.WAITING;
|
||||||
|
formModel.value = { ...defaultForm() };
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a-steps :current="step"
|
<a-steps :current="status.step"
|
||||||
:status="status as any"
|
:status="status.status as any"
|
||||||
direction="vertical">
|
direction="vertical">
|
||||||
<a-step description="创建上传任务">创建任务</a-step>
|
<a-step description="创建上传任务">创建任务</a-step>
|
||||||
<a-step description="将文件上传到临时分区">上传文件</a-step>
|
<a-step>
|
||||||
<a-step description="将文件分发到目标服务器">分发文件</a-step>
|
上传文件
|
||||||
|
<template #description>
|
||||||
|
<span>将文件上传到临时分区</span><br>
|
||||||
|
<span class="span-red">在此期间请不要关闭页面</span>
|
||||||
|
</template>
|
||||||
|
</a-step>
|
||||||
|
<a-step>
|
||||||
|
分发文件
|
||||||
|
<template #description>
|
||||||
|
<span>将文件分发到目标服务器</span><br>
|
||||||
|
<span>可以关闭页面</span>
|
||||||
|
</template>
|
||||||
|
</a-step>
|
||||||
<a-step description="上传完成并释放资源">上传完成</a-step>
|
<a-step description="上传完成并释放资源">上传完成</a-step>
|
||||||
</a-steps>
|
</a-steps>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,13 +30,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { UploadTaskStatusType } from '../types/const';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
step: number;
|
status: UploadTaskStatusType;
|
||||||
status: string;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.container {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
|
// 上传任务状态定义
|
||||||
|
export interface UploadTaskStatusType {
|
||||||
|
value: string,
|
||||||
|
step: number,
|
||||||
|
status: string,
|
||||||
|
}
|
||||||
|
|
||||||
// 上传任务状态
|
// 上传任务状态
|
||||||
export const UploadTaskStatus = {
|
export const UploadTaskStatus = {
|
||||||
// 准备中
|
// 等待中
|
||||||
PREPARATION: 'PREPARATION',
|
WAITING: {
|
||||||
// 上传中
|
value: 'WAITING',
|
||||||
UPLOADING: 'UPLOADING',
|
step: 1,
|
||||||
// 已完成
|
status: 'process',
|
||||||
FINISHED: 'FINISHED',
|
},
|
||||||
// 已取消
|
|
||||||
CANCELED: 'CANCELED',
|
|
||||||
};
|
|
||||||
|
|
||||||
// 上传步骤
|
|
||||||
export const UploadStep = {
|
|
||||||
// 准备中
|
|
||||||
PREPARATION: 1,
|
|
||||||
// 请求中
|
// 请求中
|
||||||
REQUESTING: 2,
|
REQUESTING: {
|
||||||
// 分发中
|
value: 'REQUESTING',
|
||||||
UPLOADING: 3,
|
step: 2,
|
||||||
|
status: 'process',
|
||||||
|
},
|
||||||
|
// 上传中
|
||||||
|
UPLOADING: {
|
||||||
|
value: 'UPLOADING',
|
||||||
|
step: 3,
|
||||||
|
status: 'process',
|
||||||
|
},
|
||||||
// 已完成
|
// 已完成
|
||||||
FINISHED: 4,
|
FINISHED: {
|
||||||
};
|
value: 'FINISHED',
|
||||||
|
step: 4,
|
||||||
// 上传步骤状态
|
status: 'finish',
|
||||||
export const UploadStepStatus = {
|
},
|
||||||
// 处理中
|
// 已失败
|
||||||
PROCESS: 'process',
|
FAILED: {
|
||||||
// 上传完成
|
value: 'FAILED',
|
||||||
FINISH: 'finish',
|
step: 4,
|
||||||
// 上传失败
|
status: 'error',
|
||||||
ERROR: 'error',
|
},
|
||||||
|
// 已取消
|
||||||
|
CANCELED: {
|
||||||
|
value: 'CANCELED',
|
||||||
|
step: 4,
|
||||||
|
status: 'error',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 上传任务状态 字典项
|
// 上传任务状态 字典项
|
||||||
|
|||||||
@@ -18,14 +18,8 @@ export const remotePath = [{
|
|||||||
message: '上传路径长度不能大于1024位'
|
message: '上传路径长度不能大于1024位'
|
||||||
}] as FieldRule[];
|
}] as FieldRule[];
|
||||||
|
|
||||||
export const files = [{
|
|
||||||
required: true,
|
|
||||||
message: '请选择文件'
|
|
||||||
}] as FieldRule[];
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
description,
|
description,
|
||||||
hostIdList,
|
hostIdList,
|
||||||
remotePath,
|
remotePath,
|
||||||
files,
|
|
||||||
} as Record<string, FieldRule | FieldRule[]>;
|
} as Record<string, FieldRule | FieldRule[]>;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 添加到上传列表
|
// 添加到上传列表
|
||||||
const files = fileList.value.map(s => s.file);
|
const files = fileList.value.map(s => s.file as File);
|
||||||
transferManager.addUpload(hostId.value, parentPath.value, files);
|
transferManager.addUpload(hostId.value, parentPath.value, files);
|
||||||
Message.success('已开始上传, 点击右侧传输列表查看进度');
|
Message.success('已开始上传, 点击右侧传输列表查看进度');
|
||||||
// 清空
|
// 清空
|
||||||
@@ -158,9 +158,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-upload-list) {
|
:deep(.arco-upload-list) {
|
||||||
max-height: calc(100vh - 386px);
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0 12px 0 0;
|
padding: 0 12px 0 0;
|
||||||
|
max-height: calc(100vh - 386px);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-upload-list-item-name) {
|
:deep(.arco-upload-list-item-name) {
|
||||||
|
|||||||
Reference in New Issue
Block a user