mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-11-30 02:48:17 +08:00
vue ssh 修改目录、ssh 文件管理支持上传压缩包
This commit is contained in:
parent
83ccc4d28e
commit
2ecd9a167a
@ -5,10 +5,12 @@
|
||||
### 新增功能
|
||||
|
||||
1. 脚本模版新增日志管理
|
||||
2. 【server】ssh 文件管理新增导入压缩包自动解压(感谢@刘志远)
|
||||
|
||||
### 解决BUG、优化功能
|
||||
|
||||
1. 【server】节点分发数据新增状态字段,启动程序时候触发修护异常数据
|
||||
2. 【server】定时执行相关 cron 表达式输入提示示例数据
|
||||
|
||||
------
|
||||
|
||||
|
@ -33,11 +33,7 @@
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.69</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.21</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jpom.plugins</groupId>
|
||||
<artifactId>webhook</artifactId>
|
||||
|
@ -44,6 +44,12 @@
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.21</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.jpom.controller.node.ssh;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
@ -26,6 +27,9 @@ import io.jpom.plugin.Feature;
|
||||
import io.jpom.plugin.MethodFeature;
|
||||
import io.jpom.service.node.ssh.SshService;
|
||||
import io.jpom.system.ServerConfigBean;
|
||||
import io.jpom.util.CommandUtil;
|
||||
import io.jpom.util.CompressionFileUtil;
|
||||
import io.jpom.util.StringUtil;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -122,7 +126,6 @@ public class SshFileController extends BaseServerController {
|
||||
}
|
||||
|
||||
@RequestMapping(value = "list_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public String listData(String id, String path, String children) throws SftpException {
|
||||
SshModel sshModel = this.check(id, path, children);
|
||||
@ -132,7 +135,6 @@ public class SshFileController extends BaseServerController {
|
||||
}
|
||||
|
||||
@RequestMapping(value = "read_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public String readFileData(String id, String path, String children) {
|
||||
SshModel sshModel = this.check(id, path, children);
|
||||
@ -145,7 +147,6 @@ public class SshFileController extends BaseServerController {
|
||||
}
|
||||
|
||||
@RequestMapping(value = "update_file_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
@Feature(method = MethodFeature.EDIT)
|
||||
public String updateFileData(String id, String path, String children, String content) {
|
||||
SshModel sshModel = this.check(id, path, children);
|
||||
@ -413,23 +414,48 @@ public class SshFileController extends BaseServerController {
|
||||
|
||||
@RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.UPLOAD)
|
||||
public String upload(String id, String path, String name) {
|
||||
public String upload(String id, String path, String name, String unzip) {
|
||||
SshModel sshModel = sshService.getByKey(id, false);
|
||||
Assert.notNull(sshModel, "ssh error");
|
||||
List<String> fileDirs = sshModel.fileDirs();
|
||||
Assert.state(CollUtil.contains(fileDirs, path), "没有配置此文件夹");
|
||||
String remotePath = FileUtil.normalize(path + StrUtil.SLASH + name);
|
||||
Session session = null;
|
||||
ChannelSftp channel = null;
|
||||
String localPath = null;
|
||||
try {
|
||||
session = SshService.getSessionByModel(sshModel);
|
||||
channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
|
||||
MultipartFileBuilder multipartFileBuilder = createMultipart().addFieldName("file").setUseOriginalFilename(true);
|
||||
localPath = multipartFileBuilder.save();
|
||||
File file = FileUtil.file(localPath);
|
||||
String normalize = FileUtil.normalize(path + StrUtil.SLASH + name);
|
||||
channel.cd(normalize);
|
||||
channel.put(IoUtil.toStream(file), file.getName());
|
||||
MultipartFileBuilder multipart = createMultipart();
|
||||
// 保存路径
|
||||
File tempPath = ServerConfigBean.getInstance().getUserTempPath();
|
||||
File savePath = FileUtil.file(tempPath, "ssh", sshModel.getId());
|
||||
multipart.setSavePath(FileUtil.getAbsolutePath(savePath));
|
||||
multipart.addFieldName("file")
|
||||
.setUseOriginalFilename(true);
|
||||
//
|
||||
if (Convert.toBool(unzip, false)) {
|
||||
multipart.setFileExt(StringUtil.PACKAGE_EXT);
|
||||
localPath = multipart.save();
|
||||
// 解压
|
||||
File file = new File(localPath);
|
||||
File tempUnzipPath = FileUtil.file(savePath, IdUtil.fastSimpleUUID());
|
||||
try {
|
||||
CompressionFileUtil.unCompress(file, tempUnzipPath);
|
||||
// 同步上传文件
|
||||
sshService.uploadDir(sshModel, remotePath, tempUnzipPath);
|
||||
} finally {
|
||||
// 删除临时文件
|
||||
CommandUtil.systemFastDel(file);
|
||||
CommandUtil.systemFastDel(tempUnzipPath);
|
||||
}
|
||||
} else {
|
||||
localPath = multipart.save();
|
||||
File file = FileUtil.file(localPath);
|
||||
channel.cd(remotePath);
|
||||
channel.put(IoUtil.toStream(file), file.getName());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
DefaultSystemLog.getLog().error("ssh上传文件异常", e);
|
||||
return JsonMessage.getString(400, "上传失败");
|
||||
|
@ -181,7 +181,7 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
<style scoped>
|
||||
#app-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ import { getNodeList, getNodeStatus, editNode, deleteNode, syncProject, unLockWo
|
||||
import { getSshListAll } from "@/api/ssh";
|
||||
import { syncScript } from "@/api/node-other";
|
||||
import NodeLayout from "./node-layout";
|
||||
import Terminal from "./terminal";
|
||||
import Terminal from "@/pages/ssh/terminal";
|
||||
import { parseTime } from "@/utils/time";
|
||||
import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL, PAGE_DEFAULT_LIST_QUERY } from "@/utils/const";
|
||||
import { getWorkSpaceListAll } from "@/api/workspace";
|
||||
|
@ -63,6 +63,8 @@
|
||||
</a-drawer>
|
||||
<!-- 上传文件 -->
|
||||
<a-modal v-model="uploadFileVisible" width="300px" title="上传 bat|bash 文件" :footer="null" :maskClosable="true">
|
||||
<a-alert message=" 导入文件将自动安装文件名去重、如果已经存在的将自动覆盖内容" banner />
|
||||
<br />
|
||||
<a-upload :file-list="uploadFileList" :remove="handleRemove" :before-upload="beforeUpload" :accept="'.bat,.sh'">
|
||||
<a-button><a-icon type="upload" />选择 bat|bash 文件</a-button>
|
||||
</a-upload>
|
||||
|
@ -72,7 +72,7 @@
|
||||
</a-modal>
|
||||
<!-- 上传压缩文件 -->
|
||||
<a-modal v-model="uploadZipFileVisible" width="300px" title="上传压缩文件" :footer="null" :maskClosable="true">
|
||||
<a-upload :file-list="uploadFileList" :remove="handleZipRemove" :before-upload="beforeZipUpload" accept=".tar,.bz2,.gz,.zip,.tar.bz2,.tar.gz">
|
||||
<a-upload :file-list="uploadFileList" :remove="handleZipRemove" :before-upload="beforeZipUpload" :accept="ZIP_ACCEPT">
|
||||
<a-button><a-icon type="upload" />选择压缩文件</a-button>
|
||||
</a-upload>
|
||||
<br />
|
||||
@ -105,7 +105,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { getFileList, downloadProjectFile, deleteProjectFile, uploadProjectFile, readFile, updateFile, remoteDownload } from "../../../../api/node-project";
|
||||
|
||||
import { ZIP_ACCEPT } from "@/utils/const";
|
||||
import codeEditor from "@/components/codeEditor";
|
||||
|
||||
export default {
|
||||
@ -132,6 +132,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ZIP_ACCEPT: ZIP_ACCEPT,
|
||||
loading: false,
|
||||
treeList: [],
|
||||
fileList: [],
|
||||
@ -311,7 +312,6 @@ export default {
|
||||
if (Object.keys(this.tempNode).length === 0) {
|
||||
this.$notification.error({
|
||||
message: "请选择一个节点",
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -334,7 +334,6 @@ export default {
|
||||
startUpload() {
|
||||
this.$notification.info({
|
||||
message: "正在上传文件,请稍后...",
|
||||
|
||||
});
|
||||
// 设置上传状态
|
||||
this.uploading = true;
|
||||
@ -356,7 +355,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.successSize++;
|
||||
}
|
||||
@ -379,7 +377,6 @@ export default {
|
||||
if (Object.keys(this.tempNode).length === 0) {
|
||||
this.$notification.error({
|
||||
message: "请选择一个节点",
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -398,7 +395,6 @@ export default {
|
||||
startZipUpload() {
|
||||
this.$notification.info({
|
||||
message: "正在上传文件,请稍后...",
|
||||
|
||||
});
|
||||
// 设置上传状态
|
||||
this.uploading = true;
|
||||
@ -421,7 +417,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.successSize++;
|
||||
this.percentage = 100;
|
||||
@ -461,7 +456,6 @@ export default {
|
||||
if (res.code == 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.remoteDownloadData = {};
|
||||
this.uploadRemoteFileVisible = false;
|
||||
@ -478,7 +472,6 @@ export default {
|
||||
if (Object.keys(this.tempNode).length === 0) {
|
||||
this.$notification.warn({
|
||||
message: "请选择一个节点",
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@ -525,7 +518,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.loadFileList();
|
||||
}
|
||||
@ -537,7 +529,6 @@ export default {
|
||||
handleDownload(record) {
|
||||
this.$notification.info({
|
||||
message: "正在下载,请稍等...",
|
||||
|
||||
});
|
||||
// 请求参数
|
||||
const params = {
|
||||
@ -577,7 +568,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.loadData();
|
||||
this.loadFileList();
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 布局 -->
|
||||
<a-layout class="file-layout">
|
||||
<a-layout class="ssh-file-layout">
|
||||
<!-- 目录树 -->
|
||||
<a-layout-sider theme="light" class="sider" width="25%">
|
||||
<a-empty v-if="treeList.length === 0" />
|
||||
@ -10,21 +10,13 @@
|
||||
<a-layout-content class="file-content">
|
||||
<div ref="filter" class="filter">
|
||||
<a-button :disabled="!this.tempNode.parentDir" type="primary" @click="handleUpload">上传文件</a-button>
|
||||
<a-button :disabled="!this.tempNode.parentDir" type="primary" @click="handleUploadZip">上传压缩文件(自动解压)</a-button>
|
||||
<a-button :disabled="!this.tempNode.parentDir" type="primary" @click="loadFileList()">刷新</a-button>
|
||||
<a-button :disabled="!this.tempNode.parentDir" type="danger" @click="handleDeletePath()">删除</a-button>
|
||||
<span v-if="this.tempNode.parentDir">当前目录:{{ this.tempNode.path }}</span
|
||||
><span v-if="this.tempNode.parentDir">{{ this.tempNode.parentDir }}</span>
|
||||
</div>
|
||||
<a-table
|
||||
:data-source="fileList"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
bordered
|
||||
:style="{ 'max-height': tableHeight + 'px' }"
|
||||
:scroll="{ x: 790, y: tableHeight - 60 }"
|
||||
:rowKey="(record, index) => index"
|
||||
>
|
||||
<a-table :data-source="fileList" :loading="loading" :columns="columns" :pagination="false" bordered :rowKey="(record, index) => index">
|
||||
<a-tooltip slot="name" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
@ -45,8 +37,11 @@
|
||||
</a-table>
|
||||
<!-- 上传文件 -->
|
||||
<a-modal v-model="uploadFileVisible" width="300px" title="上传文件" :footer="null" :maskClosable="true">
|
||||
<a-upload :file-list="uploadFileList" :remove="handleRemove" :before-upload="beforeUpload" multiple>
|
||||
<a-button><a-icon type="upload" />选择文件</a-button>
|
||||
<a-upload :file-list="uploadFileList" :remove="handleRemove" :before-upload="beforeUpload" :accept="`${uploadFileZip ? ZIP_ACCEPT : ''}`" :multiple="!uploadFileZip">
|
||||
<a-button>
|
||||
<a-icon type="upload" />选择文件
|
||||
{{ uploadFileZip ? "压缩包" : "" }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<br />
|
||||
<a-button type="primary" :disabled="uploadFileList.length === 0" @click="startUpload">开始上传</a-button>
|
||||
@ -67,6 +62,7 @@
|
||||
import { getRootFileList, getFileList, downloadFile, deleteFile, uploadFile, readFile, updateFileData } from "@/api/ssh";
|
||||
import Terminal from "./terminal";
|
||||
import codeEditor from "@/components/codeEditor";
|
||||
import { ZIP_ACCEPT } from "@/utils/const";
|
||||
export default {
|
||||
props: {
|
||||
ssh: {
|
||||
@ -86,6 +82,8 @@ export default {
|
||||
tempNode: {},
|
||||
temp: {},
|
||||
uploadFileVisible: false,
|
||||
uploadFileZip: false,
|
||||
ZIP_ACCEPT: ZIP_ACCEPT,
|
||||
terminalVisible: false,
|
||||
tableHeight: "80vh",
|
||||
replaceFields: {
|
||||
@ -138,11 +136,15 @@ export default {
|
||||
if (Object.keys(this.tempNode).length === 0) {
|
||||
this.$notification.error({
|
||||
message: "请选择一个节点",
|
||||
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.uploadFileVisible = true;
|
||||
this.uploadFileZip = false;
|
||||
},
|
||||
handleUploadZip() {
|
||||
this.handleUpload();
|
||||
this.uploadFileZip = true;
|
||||
},
|
||||
handleRemove(file) {
|
||||
const index = this.uploadFileList.indexOf(file);
|
||||
@ -161,17 +163,15 @@ export default {
|
||||
formData.append("file", file);
|
||||
formData.append("id", this.ssh.id);
|
||||
formData.append("name", this.tempNode.parentDir);
|
||||
formData.append("unzip", this.uploadFileZip);
|
||||
formData.append("path", this.tempNode.path);
|
||||
// 上传文件
|
||||
uploadFile(formData).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
|
||||
this.loadFileList();
|
||||
|
||||
this.uploadFileList = [];
|
||||
this.uploadFileVisible = false;
|
||||
}
|
||||
@ -232,7 +232,6 @@ export default {
|
||||
if (Object.keys(this.tempNode).length === 0) {
|
||||
this.$notification.warn({
|
||||
message: "请选择一个节点",
|
||||
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@ -248,15 +247,17 @@ export default {
|
||||
getFileList(params).then((res) => {
|
||||
if (res.code === 200) {
|
||||
// 区分目录和文件
|
||||
res.data.forEach((element) => {
|
||||
if (!element.dir) {
|
||||
this.fileList = res.data
|
||||
.filter((element) => {
|
||||
return !element.dir;
|
||||
})
|
||||
.map((element) => {
|
||||
// 设置文件表格
|
||||
this.fileList.push({
|
||||
return {
|
||||
path: this.tempNode.path,
|
||||
...element,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
@ -287,7 +288,6 @@ export default {
|
||||
updateFileData(params).then((res) => {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
if (res.code == 200) {
|
||||
this.editFileVisible = false;
|
||||
@ -337,7 +337,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
// 刷新树
|
||||
this.loadData();
|
||||
@ -365,7 +364,6 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
|
||||
});
|
||||
this.loadFileList();
|
||||
}
|
||||
@ -376,18 +374,19 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.file-layout {
|
||||
<style scoped lang="stylus">
|
||||
.ssh-file-layout {
|
||||
padding: 0;
|
||||
min-height calc(100vh - 75px);
|
||||
}
|
||||
.sider {
|
||||
border: 1px solid #e2e2e2;
|
||||
height: calc(100vh - 80px);
|
||||
overflow-y: auto;
|
||||
/* height: calc(100vh - 80px); */
|
||||
/* overflow-y: auto; */
|
||||
}
|
||||
.file-content {
|
||||
height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
/* height: calc(100vh - 100px); */
|
||||
/* overflow-y: auto; */
|
||||
margin: 10px 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
@ -232,8 +232,8 @@
|
||||
</template>
|
||||
<script>
|
||||
import { deleteSsh, editSsh, getSshList, getSshCheckAgent, getSshOperationLogList, installAgentNode } from "@/api/ssh";
|
||||
import SshFile from "./ssh-file";
|
||||
import Terminal from "./terminal";
|
||||
import SshFile from "@/pages/ssh/ssh-file";
|
||||
import Terminal from "@/pages/ssh/terminal";
|
||||
import { parseTime } from "@/utils/time";
|
||||
import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL, PAGE_DEFAULT_LIST_QUERY } from "@/utils/const";
|
||||
|
@ -21,11 +21,6 @@ const children = [
|
||||
name: "node-list",
|
||||
component: () => import("../pages/node/list"),
|
||||
},
|
||||
{
|
||||
path: "/node/ssh",
|
||||
name: "node-ssh",
|
||||
component: () => import("../pages/node/ssh"),
|
||||
},
|
||||
{
|
||||
path: "/node/update",
|
||||
name: "node-update",
|
||||
@ -42,14 +37,19 @@ const children = [
|
||||
component: () => import("../pages/node/script-list"),
|
||||
},
|
||||
{
|
||||
path: "/node/ssh/command",
|
||||
name: "node-command",
|
||||
component: () => import("../pages/node/ssh/command"),
|
||||
path: "/ssh",
|
||||
name: "node-ssh",
|
||||
component: () => import("../pages/ssh/ssh"),
|
||||
},
|
||||
{
|
||||
path: "/node/ssh/command-log",
|
||||
path: "/ssh/command",
|
||||
name: "node-command",
|
||||
component: () => import("../pages/node/ssh/command-log"),
|
||||
component: () => import("../pages/ssh/command"),
|
||||
},
|
||||
{
|
||||
path: "/ssh/command-log",
|
||||
name: "node-command",
|
||||
component: () => import("../pages/ssh/command-log"),
|
||||
},
|
||||
{
|
||||
path: "/dispatch/list",
|
||||
|
@ -5,9 +5,9 @@
|
||||
*/
|
||||
const routeMenuMap = {
|
||||
nodeList: "/node/list",
|
||||
sshList: "/node/ssh",
|
||||
commandList: "/node/ssh/command",
|
||||
commandLogList: "/node/ssh/command-log",
|
||||
sshList: "/ssh",
|
||||
commandList: "/ssh/command",
|
||||
commandLogList: "/ssh/command-log",
|
||||
nodeUpdate: "/node/update",
|
||||
outgiving: "/dispatch/list",
|
||||
outgivingLog: "/dispatch/log",
|
||||
|
@ -62,7 +62,7 @@ export const RESTART_UPGRADE_WAIT_TIME_COUNT = 80;
|
||||
|
||||
/**
|
||||
* 定时 cron 默认提示
|
||||
*
|
||||
*
|
||||
* https://www.npmjs.com/package/cron-parser
|
||||
*/
|
||||
export const CRON_DATA_SOURCE = [
|
||||
@ -119,3 +119,8 @@ export const CRON_DATA_SOURCE = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 压缩文件格式
|
||||
*/
|
||||
export const ZIP_ACCEPT = ".tar,.bz2,.gz,.zip,.tar.bz2,.tar.gz";
|
||||
|
Loading…
Reference in New Issue
Block a user