feat: 新增linux文件夹创建&删除&其他优化

This commit is contained in:
meilin.huang
2022-07-04 20:21:24 +08:00
parent b88923a128
commit 729a3d7028
13 changed files with 162 additions and 69 deletions

View File

@@ -44,7 +44,9 @@ export default defineComponent({
state.isShowSearch = true; state.isShowSearch = true;
initTageView(); initTageView();
nextTick(() => { nextTick(() => {
layoutMenuAutocompleteRef.value.focus(); setTimeout(() => {
layoutMenuAutocompleteRef.value.focus();
});
}); });
}; };
// 搜索弹窗关闭 // 搜索弹窗关闭

View File

@@ -341,7 +341,7 @@ export default defineComponent({
onMounted(async () => { onMounted(async () => {
search(); search();
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; state.projects = await projectApi.accountProjects.request(null);
}); });
const choose = (item: any) => { const choose = (item: any) => {

View File

@@ -135,7 +135,9 @@ export default defineComponent({
state.db = props.db; state.db = props.db;
state.dialogVisible = true; state.dialogVisible = true;
nextTick(() => { nextTick(() => {
remarkInputRef.value?.focus(); setTimeout(() => {
remarkInputRef.value?.focus();
});
}); });
}; };

View File

@@ -96,6 +96,12 @@
<el-link type="info" icon="view" :underline="false">查看</el-link> <el-link type="info" icon="view" :underline="false">查看</el-link>
</el-dropdown-item> </el-dropdown-item>
<span v-auth="'machine:file:write'">
<el-dropdown-item @click="showCreateFileDialog(node, data)" v-if="data.type == 'd'">
<el-link type="primary" icon="document" :underline="false" style="margin-left: 2px">新建</el-link>
</el-dropdown-item>
</span>
<span v-auth="'machine:file:upload'"> <span v-auth="'machine:file:upload'">
<el-dropdown-item v-if="data.type == 'd'"> <el-dropdown-item v-if="data.type == 'd'">
<el-upload <el-upload
@@ -133,6 +139,35 @@
</div> </div>
</el-dialog> </el-dialog>
<el-dialog
:destroy-on-close="true"
title="新建文件"
v-model="createFileDialog.visible"
:before-close="closeCreateFileDialog"
:close-on-click-modal="false"
top="5vh"
width="400px"
>
<div>
<el-form-item prop="name" label="名称:">
<el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型:">
<el-radio-group v-model="createFileDialog.type">
<el-radio label="d" size="small">文件夹</el-radio>
<el-radio label="-" size="small">文件</el-radio>
</el-radio-group>
</el-form-item>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeCreateFileDialog">关闭</el-button>
<el-button v-auth="'machine:file:write'" type="primary" @click="createFile">确定</el-button>
</div>
</template>
</el-dialog>
<el-dialog <el-dialog
:destroy-on-close="true" :destroy-on-close="true"
:title="fileContent.dialogTitle" :title="fileContent.dialogTitle"
@@ -164,6 +199,7 @@ import { codemirror } from '@/components/codemirror';
import { getSession } from '@/common/utils/storage'; import { getSession } from '@/common/utils/storage';
import enums from './enums'; import enums from './enums';
import config from '@/common/config'; import config from '@/common/config';
import { isTrue } from '@/common/assert';
export default defineComponent({ export default defineComponent({
name: 'FileManage', name: 'FileManage',
@@ -184,26 +220,8 @@ export default defineComponent({
const fileTree: any = ref(null); const fileTree: any = ref(null);
const token = getSession('token'); const token = getSession('token');
const cmOptions = { const folderType = 'd';
tabSize: 2, const fileType = '-';
mode: 'text/x-sh',
theme: 'panda-syntax',
line: true,
// 开启校验
lint: true,
gutters: ['CodeMirror-lint-markers'],
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
autofocus: true,
styleSelectedText: true,
styleActiveLine: true, // 高亮选中行
foldGutter: true, // 块槽
hintOptions: {
// 当匹配只有一项的时候是否自动补全
completeSingle: true,
},
};
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
@@ -250,6 +268,12 @@ export default defineComponent({
path: '', path: '',
type: '', type: '',
}, },
createFileDialog: {
visible: false,
name: '',
type: folderType,
node: null as any,
},
file: null as any, file: null as any,
}); });
@@ -272,18 +296,6 @@ export default defineComponent({
getFiles(); getFiles();
}; };
/**
* tab切换触发事件
* @param {Object} tab
* @param {Object} event
*/
// handleClick(tab, event) {
// // if (tab.name == 'file-manage') {
// // this.fileManage.node.childNodes = [];
// // this.loadNode(this.fileManage.node, this.fileManage.resolve);
// // }
// }
const add = () => { const add = () => {
// 往数组头部添加元素 // 往数组头部添加元素
state.fileTable = [{}].concat(state.fileTable); state.fileTable = [{}].concat(state.fileTable);
@@ -311,7 +323,6 @@ export default defineComponent({
}) })
.then(() => { .then(() => {
getFiles(); getFiles();
// state.fileTable.splice(idx, 1);
}); });
}); });
} else { } else {
@@ -388,7 +399,6 @@ export default defineComponent({
emit('update:visible', false); emit('update:visible', false);
emit('update:machineId', null); emit('update:machineId', null);
emit('cancel'); emit('cancel');
// state.activeName = 'conf-file'
state.fileTable = []; state.fileTable = [];
state.tree.folder = { id: 0 }; state.tree.folder = { id: 0 };
}; };
@@ -413,7 +423,7 @@ export default defineComponent({
return resolve([ return resolve([
{ {
name: path, name: path,
type: 'd', type: folderType,
path: path, path: path,
}, },
]); ]);
@@ -435,13 +445,42 @@ export default defineComponent({
}); });
for (const file of res) { for (const file of res) {
const type = file.type; const type = file.type;
if (type != 'd') { if (type == fileType) {
file.leaf = true; file.leaf = true;
} }
} }
return resolve(res); return resolve(res);
}; };
const showCreateFileDialog = (node: any) => {
isTrue(node.expanded, '请先点击展开该节点后再创建');
state.createFileDialog.node = node;
state.createFileDialog.visible = true;
};
const createFile = async () => {
const node = state.createFileDialog.node;
console.log(node.data);
const name = state.createFileDialog.name;
const type = state.createFileDialog.type;
const path = node.data.path + '/' + name;
await machineApi.createFile.request({
machineId: props.machineId,
id: state.tree.folder.id,
path,
type,
});
fileTree.value.append({ name: name, path: path, type: type, leaf: type === fileType, size: 0 }, node);
closeCreateFileDialog();
};
const closeCreateFileDialog = () => {
state.createFileDialog.visible = false;
state.createFileDialog.node = null;
state.createFileDialog.name = '';
state.createFileDialog.type = folderType;
};
const deleteFile = (node: any, data: any) => { const deleteFile = (node: any, data: any) => {
const file = data.path; const file = data.path;
ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', {
@@ -468,7 +507,6 @@ export default defineComponent({
const downloadFile = (node: any, data: any) => { const downloadFile = (node: any, data: any) => {
const a = document.createElement('a'); const a = document.createElement('a');
// a.setAttribute('target', '_blank')
a.setAttribute( a.setAttribute(
'href', 'href',
`${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}` `${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}`
@@ -516,7 +554,6 @@ export default defineComponent({
const beforeUpload = (file: File) => { const beforeUpload = (file: File) => {
state.file = file; state.file = file;
// ElMessage.success(`'${file.name}' 上传中,请关注结果通知`);
}; };
const getFilePath = (data: object, visible: boolean) => { const getFilePath = (data: object, visible: boolean) => {
if (visible) { if (visible) {
@@ -572,7 +609,6 @@ export default defineComponent({
fileTree, fileTree,
enums, enums,
token, token,
cmOptions,
add, add,
getFiles, getFiles,
handlePageChange, handlePageChange,
@@ -583,6 +619,9 @@ export default defineComponent({
updateContent, updateContent,
handleClose, handleClose,
loadNode, loadNode,
showCreateFileDialog,
closeCreateFileDialog,
createFile,
deleteFile, deleteFile,
downloadFile, downloadFile,
getUploadFile, getUploadFile,

View File

@@ -25,6 +25,7 @@ export const machineApi = {
rmFile: Api.create("/machines/{machineId}/files/{fileId}/remove", 'delete'), rmFile: Api.create("/machines/{machineId}/files/{fileId}/remove", 'delete'),
uploadFile: Api.create("/machines/{machineId}/files/{fileId}/upload?token={token}", 'post'), uploadFile: Api.create("/machines/{machineId}/files/{fileId}/upload?token={token}", 'post'),
fileContent: Api.create("/machines/{machineId}/files/{fileId}/read", 'get'), fileContent: Api.create("/machines/{machineId}/files/{fileId}/read", 'get'),
createFile: Api.create("/machines/{machineId}/files/{id}/create-file", 'post'),
// 修改文件内容 // 修改文件内容
updateFileContent: Api.create("/machines/{machineId}/files/{id}/write", 'post'), updateFileContent: Api.create("/machines/{machineId}/files/{id}/write", 'post'),
// 添加文件or目录 // 添加文件or目录

View File

@@ -250,7 +250,7 @@ export default defineComponent({
onMounted(async () => { onMounted(async () => {
search(); search();
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; state.projects = await projectApi.accountProjects.request(null);
}); });
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
@@ -266,12 +266,6 @@ export default defineComponent({
state.currentData = item; state.currentData = item;
}; };
// connect() {
// Req.post('/open/redis/connect', this.form, res => {
// this.redisInfo = res
// })
// }
const showDatabases = async (id: number) => { const showDatabases = async (id: number) => {
state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases; state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
state.databaseDialog.title = `数据库列表`; state.databaseDialog.title = `数据库列表`;
@@ -371,14 +365,6 @@ export default defineComponent({
} catch (err) {} } catch (err) {}
}; };
// const info = (redis: any) => {
// redisApi.redisInfo.request({ id: redis.id }).then((res: any) => {
// state.infoDialog.info = res;
// state.infoDialog.title = `'${redis.host}' info`;
// state.infoDialog.visible = true;
// });
// };
const search = async () => { const search = async () => {
const res = await mongoApi.mongoList.request(state.query); const res = await mongoApi.mongoList.request(state.query);
state.list = res.list; state.list = res.list;

View File

@@ -118,7 +118,7 @@ export default defineComponent({
onMounted(async () => { onMounted(async () => {
search(); search();
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; state.projects = await projectApi.accountProjects.request(null);
}); });
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {

View File

@@ -15,6 +15,13 @@ type DbForm struct {
EnvId uint64 `binding:"required" json:"envId"` EnvId uint64 `binding:"required" json:"envId"`
} }
type DbSqlSaveForm struct {
Name string
Sql string `binding:"required"`
Type int `binding:"required"`
Db string `binding:"required"`
}
// 数据库SQL执行表单 // 数据库SQL执行表单
type DbSqlExecForm struct { type DbSqlExecForm struct {
Db string `binding:"required" json:"db"` //数据库名 Db string `binding:"required" json:"db"` //数据库名

View File

@@ -38,11 +38,9 @@ type MachineScriptForm struct {
Script string `binding:"required"` Script string `binding:"required"`
} }
type DbSqlSaveForm struct { type MachineCreateFileForm struct {
Name string Path string `binding:"required"`
Sql string `binding:"required"` Type string `binding:"required"`
Type int `binding:"required"`
Db string `binding:"required"`
} }
type MachineFileUpdateForm struct { type MachineFileUpdateForm struct {

View File

@@ -60,6 +60,22 @@ func (m *MachineFile) DeleteFile(rc *ctx.ReqCtx) {
/*** sftp相关操作 */ /*** sftp相关操作 */
func (m *MachineFile) CreateFile(rc *ctx.ReqCtx) {
g := rc.GinCtx
fid := GetMachineFileId(g)
form := new(form.MachineCreateFileForm)
ginx.BindJsonAndValid(g, form)
path := form.Path
if form.Type == dir {
m.MachineFileApp.MkDir(fid, form.Path)
} else {
m.MachineFileApp.CreateFile(fid, form.Path)
}
rc.ReqParam = fmt.Sprintf("path: %s, type: %s", path, form.Type)
}
func (m *MachineFile) ReadFileContent(rc *ctx.ReqCtx) { func (m *MachineFile) ReadFileContent(rc *ctx.ReqCtx) {
g := rc.GinCtx g := rc.GinCtx
fid := GetMachineFileId(g) fid := GetMachineFileId(g)
@@ -104,6 +120,7 @@ func (m *MachineFile) GetDirEntry(rc *ctx.ReqCtx) {
Size: fi.Size(), Size: fi.Size(),
Path: readPath + fi.Name(), Path: readPath + fi.Name(),
Type: getFileType(fi.Mode()), Type: getFileType(fi.Mode()),
Mode: fi.Mode().String(),
}) })
} }
rc.ResData = fisVO rc.ResData = fisVO
@@ -155,7 +172,6 @@ func (m *MachineFile) UploadFile(rc *ctx.ReqCtx) {
func (m *MachineFile) RemoveFile(rc *ctx.ReqCtx) { func (m *MachineFile) RemoveFile(rc *ctx.ReqCtx) {
g := rc.GinCtx g := rc.GinCtx
fid := GetMachineFileId(g) fid := GetMachineFileId(g)
// mid := GetMachineId(g)
path := g.Query("path") path := g.Query("path")
m.MachineFileApp.RemoveFile(fid, path) m.MachineFileApp.RemoveFile(fid, path)
@@ -167,7 +183,10 @@ func getFileType(fm fs.FileMode) string {
if fm.IsDir() { if fm.IsDir() {
return dir return dir
} }
return file if fm.IsRegular() {
return file
}
return dir
} }
func GetMachineFileId(g *gin.Context) uint64 { func GetMachineFileId(g *gin.Context) uint64 {

View File

@@ -56,6 +56,7 @@ type MachineFileInfo struct {
Path string `json:"path"` Path string `json:"path"`
Size int64 `json:"size"` Size int64 `json:"size"`
Type string `json:"type"` Type string `json:"type"`
Mode string `json:"mode"`
} }
type RoleVO struct { type RoleVO struct {

View File

@@ -1,6 +1,7 @@
package application package application
import ( import (
"fmt"
"io" "io"
"io/fs" "io/fs"
"mayfly-go/internal/devops/domain/entity" "mayfly-go/internal/devops/domain/entity"
@@ -30,6 +31,12 @@ type MachineFile interface {
/** sftp 相关操作 **/ /** sftp 相关操作 **/
// 创建目录
MkDir(fid uint64, path string)
// 创建文件
CreateFile(fid uint64, path string)
// 读取目录 // 读取目录
ReadDir(fid uint64, path string) []fs.FileInfo ReadDir(fid uint64, path string) []fs.FileInfo
@@ -100,6 +107,25 @@ func (m *machineFileAppImpl) ReadDir(fid uint64, path string) []fs.FileInfo {
return fis return fis
} }
func (m *machineFileAppImpl) MkDir(fid uint64, path string) {
path, machineId := m.checkAndReturnPathMid(fid, path)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
sftpCli := m.getSftpCli(machineId)
err := sftpCli.Mkdir(path)
biz.ErrIsNilAppendErr(err, "创建目录失败: %s")
}
func (m *machineFileAppImpl) CreateFile(fid uint64, path string) {
path, machineId := m.checkAndReturnPathMid(fid, path)
sftpCli := m.getSftpCli(machineId)
file, err := sftpCli.Create(path)
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
defer file.Close()
}
func (m *machineFileAppImpl) ReadFile(fileId uint64, path string) *sftp.File { func (m *machineFileAppImpl) ReadFile(fileId uint64, path string) *sftp.File {
path, machineId := m.checkAndReturnPathMid(fileId, path) path, machineId := m.checkAndReturnPathMid(fileId, path)
sftpCli := m.getSftpCli(machineId) sftpCli := m.getSftpCli(machineId)
@@ -148,6 +174,11 @@ func (m *machineFileAppImpl) RemoveFile(fileId uint64, path string) {
fi, _ := file.Stat() fi, _ := file.Stat()
if fi.IsDir() { if fi.IsDir() {
err = sftpCli.RemoveDirectory(path) err = sftpCli.RemoveDirectory(path)
// 如果文件夹有内容会删除失败则使用rm -rf命令删除
if err != nil {
MachineApp.GetCli(machineId).Run(fmt.Sprintf("rm -rf %s", path))
err = nil
}
} else { } else {
err = sftpCli.Remove(path) err = sftpCli.Remove(path)
} }

View File

@@ -43,14 +43,14 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
getContent := ctx.NewLogInfo("读取机器文件内容") getContent := ctx.NewLogInfo("读取机器文件内容")
machineFile.GET(":machineId/files/:fileId/read", func(c *gin.Context) { machineFile.GET(":machineId/files/:fileId/read", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(getContent) ctx.NewReqCtxWithGin(c).WithLog(getContent).
rc.Handle(mf.ReadFileContent) Handle(mf.ReadFileContent)
}) })
getDir := ctx.NewLogInfo("读取机器目录") getDir := ctx.NewLogInfo("读取机器目录")
machineFile.GET(":machineId/files/:fileId/read-dir", func(c *gin.Context) { machineFile.GET(":machineId/files/:fileId/read-dir", func(c *gin.Context) {
rc := ctx.NewReqCtxWithGin(c).WithLog(getDir) ctx.NewReqCtxWithGin(c).WithLog(getDir).
rc.Handle(mf.GetDirEntry) Handle(mf.GetDirEntry)
}) })
writeFile := ctx.NewLogInfo("写入or下载文件内容") writeFile := ctx.NewLogInfo("写入or下载文件内容")
@@ -61,6 +61,13 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
Handle(mf.WriteFileContent) Handle(mf.WriteFileContent)
}) })
createFile := ctx.NewLogInfo("创建机器文件or目录")
machineFile.POST(":machineId/files/:fileId/create-file", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(createFile).
WithRequiredPermission(wfP).
Handle(mf.CreateFile)
})
uploadFile := ctx.NewLogInfo("文件上传") uploadFile := ctx.NewLogInfo("文件上传")
ufP := ctx.NewPermission("machine:file:upload") ufP := ctx.NewPermission("machine:file:upload")
machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) { machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) {