diff --git a/mayfly_go_web/src/views/layout/navBars/breadcrumb/search.vue b/mayfly_go_web/src/views/layout/navBars/breadcrumb/search.vue index 873ea177..f70cfaeb 100644 --- a/mayfly_go_web/src/views/layout/navBars/breadcrumb/search.vue +++ b/mayfly_go_web/src/views/layout/navBars/breadcrumb/search.vue @@ -44,7 +44,9 @@ export default defineComponent({ state.isShowSearch = true; initTageView(); nextTick(() => { - layoutMenuAutocompleteRef.value.focus(); + setTimeout(() => { + layoutMenuAutocompleteRef.value.focus(); + }); }); }; // 搜索弹窗关闭 diff --git a/mayfly_go_web/src/views/ops/db/DbList.vue b/mayfly_go_web/src/views/ops/db/DbList.vue index 6cd8ced0..d8842d33 100644 --- a/mayfly_go_web/src/views/ops/db/DbList.vue +++ b/mayfly_go_web/src/views/ops/db/DbList.vue @@ -341,7 +341,7 @@ export default defineComponent({ onMounted(async () => { search(); - state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; + state.projects = await projectApi.accountProjects.request(null); }); const choose = (item: any) => { diff --git a/mayfly_go_web/src/views/ops/db/component/SqlExecDialog.vue b/mayfly_go_web/src/views/ops/db/component/SqlExecDialog.vue index 1c8efa1e..0cecdf6f 100644 --- a/mayfly_go_web/src/views/ops/db/component/SqlExecDialog.vue +++ b/mayfly_go_web/src/views/ops/db/component/SqlExecDialog.vue @@ -135,7 +135,9 @@ export default defineComponent({ state.db = props.db; state.dialogVisible = true; nextTick(() => { - remarkInputRef.value?.focus(); + setTimeout(() => { + remarkInputRef.value?.focus(); + }); }); }; diff --git a/mayfly_go_web/src/views/ops/machine/FileManage.vue b/mayfly_go_web/src/views/ops/machine/FileManage.vue index 463426eb..00d6d30d 100755 --- a/mayfly_go_web/src/views/ops/machine/FileManage.vue +++ b/mayfly_go_web/src/views/ops/machine/FileManage.vue @@ -96,6 +96,12 @@ 查看 + + + 新建 + + + + +
+ + + + + + 文件夹 + 文件 + + +
+ + +
+ { // 往数组头部添加元素 state.fileTable = [{}].concat(state.fileTable); @@ -311,7 +323,6 @@ export default defineComponent({ }) .then(() => { getFiles(); - // state.fileTable.splice(idx, 1); }); }); } else { @@ -388,7 +399,6 @@ export default defineComponent({ emit('update:visible', false); emit('update:machineId', null); emit('cancel'); - // state.activeName = 'conf-file' state.fileTable = []; state.tree.folder = { id: 0 }; }; @@ -413,7 +423,7 @@ export default defineComponent({ return resolve([ { name: path, - type: 'd', + type: folderType, path: path, }, ]); @@ -435,13 +445,42 @@ export default defineComponent({ }); for (const file of res) { const type = file.type; - if (type != 'd') { + if (type == fileType) { file.leaf = true; } } 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 file = data.path; ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', { @@ -468,7 +507,6 @@ export default defineComponent({ const downloadFile = (node: any, data: any) => { const a = document.createElement('a'); - // a.setAttribute('target', '_blank') a.setAttribute( 'href', `${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) => { state.file = file; - // ElMessage.success(`'${file.name}' 上传中,请关注结果通知`); }; const getFilePath = (data: object, visible: boolean) => { if (visible) { @@ -572,7 +609,6 @@ export default defineComponent({ fileTree, enums, token, - cmOptions, add, getFiles, handlePageChange, @@ -583,6 +619,9 @@ export default defineComponent({ updateContent, handleClose, loadNode, + showCreateFileDialog, + closeCreateFileDialog, + createFile, deleteFile, downloadFile, getUploadFile, diff --git a/mayfly_go_web/src/views/ops/machine/api.ts b/mayfly_go_web/src/views/ops/machine/api.ts index 2107d72e..cc3ec7f8 100644 --- a/mayfly_go_web/src/views/ops/machine/api.ts +++ b/mayfly_go_web/src/views/ops/machine/api.ts @@ -25,6 +25,7 @@ export const machineApi = { rmFile: Api.create("/machines/{machineId}/files/{fileId}/remove", 'delete'), uploadFile: Api.create("/machines/{machineId}/files/{fileId}/upload?token={token}", 'post'), 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'), // 添加文件or目录 diff --git a/mayfly_go_web/src/views/ops/mongo/MongoList.vue b/mayfly_go_web/src/views/ops/mongo/MongoList.vue index 83c5c1e0..b398a78b 100644 --- a/mayfly_go_web/src/views/ops/mongo/MongoList.vue +++ b/mayfly_go_web/src/views/ops/mongo/MongoList.vue @@ -250,7 +250,7 @@ export default defineComponent({ onMounted(async () => { search(); - state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; + state.projects = await projectApi.accountProjects.request(null); }); const handlePageChange = (curPage: number) => { @@ -266,12 +266,6 @@ export default defineComponent({ state.currentData = item; }; - // connect() { - // Req.post('/open/redis/connect', this.form, res => { - // this.redisInfo = res - // }) - // } - const showDatabases = async (id: number) => { state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases; state.databaseDialog.title = `数据库列表`; @@ -371,14 +365,6 @@ export default defineComponent({ } 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 res = await mongoApi.mongoList.request(state.query); state.list = res.list; diff --git a/mayfly_go_web/src/views/ops/redis/RedisList.vue b/mayfly_go_web/src/views/ops/redis/RedisList.vue index 5a1b398c..84e7d573 100644 --- a/mayfly_go_web/src/views/ops/redis/RedisList.vue +++ b/mayfly_go_web/src/views/ops/redis/RedisList.vue @@ -118,7 +118,7 @@ export default defineComponent({ onMounted(async () => { search(); - state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list; + state.projects = await projectApi.accountProjects.request(null); }); const handlePageChange = (curPage: number) => { diff --git a/server/internal/devops/api/form/db.go b/server/internal/devops/api/form/db.go index bb13f949..869497ed 100644 --- a/server/internal/devops/api/form/db.go +++ b/server/internal/devops/api/form/db.go @@ -15,6 +15,13 @@ type DbForm struct { EnvId uint64 `binding:"required" json:"envId"` } +type DbSqlSaveForm struct { + Name string + Sql string `binding:"required"` + Type int `binding:"required"` + Db string `binding:"required"` +} + // 数据库SQL执行表单 type DbSqlExecForm struct { Db string `binding:"required" json:"db"` //数据库名 diff --git a/server/internal/devops/api/form/form.go b/server/internal/devops/api/form/machine.go similarity index 89% rename from server/internal/devops/api/form/form.go rename to server/internal/devops/api/form/machine.go index 47e429de..fac2342a 100644 --- a/server/internal/devops/api/form/form.go +++ b/server/internal/devops/api/form/machine.go @@ -38,11 +38,9 @@ type MachineScriptForm struct { Script string `binding:"required"` } -type DbSqlSaveForm struct { - Name string - Sql string `binding:"required"` - Type int `binding:"required"` - Db string `binding:"required"` +type MachineCreateFileForm struct { + Path string `binding:"required"` + Type string `binding:"required"` } type MachineFileUpdateForm struct { diff --git a/server/internal/devops/api/machine_file.go b/server/internal/devops/api/machine_file.go index f2801ed5..37b668c8 100644 --- a/server/internal/devops/api/machine_file.go +++ b/server/internal/devops/api/machine_file.go @@ -60,6 +60,22 @@ func (m *MachineFile) DeleteFile(rc *ctx.ReqCtx) { /*** 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) { g := rc.GinCtx fid := GetMachineFileId(g) @@ -104,6 +120,7 @@ func (m *MachineFile) GetDirEntry(rc *ctx.ReqCtx) { Size: fi.Size(), Path: readPath + fi.Name(), Type: getFileType(fi.Mode()), + Mode: fi.Mode().String(), }) } rc.ResData = fisVO @@ -155,7 +172,6 @@ func (m *MachineFile) UploadFile(rc *ctx.ReqCtx) { func (m *MachineFile) RemoveFile(rc *ctx.ReqCtx) { g := rc.GinCtx fid := GetMachineFileId(g) - // mid := GetMachineId(g) path := g.Query("path") m.MachineFileApp.RemoveFile(fid, path) @@ -167,7 +183,10 @@ func getFileType(fm fs.FileMode) string { if fm.IsDir() { return dir } - return file + if fm.IsRegular() { + return file + } + return dir } func GetMachineFileId(g *gin.Context) uint64 { diff --git a/server/internal/devops/api/vo/vo.go b/server/internal/devops/api/vo/vo.go index e1fc20c0..04753bb4 100644 --- a/server/internal/devops/api/vo/vo.go +++ b/server/internal/devops/api/vo/vo.go @@ -56,6 +56,7 @@ type MachineFileInfo struct { Path string `json:"path"` Size int64 `json:"size"` Type string `json:"type"` + Mode string `json:"mode"` } type RoleVO struct { diff --git a/server/internal/devops/application/machine_file_app.go b/server/internal/devops/application/machine_file_app.go index 55a1c2f5..eaaa3a20 100644 --- a/server/internal/devops/application/machine_file_app.go +++ b/server/internal/devops/application/machine_file_app.go @@ -1,6 +1,7 @@ package application import ( + "fmt" "io" "io/fs" "mayfly-go/internal/devops/domain/entity" @@ -30,6 +31,12 @@ type MachineFile interface { /** sftp 相关操作 **/ + // 创建目录 + MkDir(fid uint64, path string) + + // 创建文件 + CreateFile(fid uint64, path string) + // 读取目录 ReadDir(fid uint64, path string) []fs.FileInfo @@ -100,6 +107,25 @@ func (m *machineFileAppImpl) ReadDir(fid uint64, path string) []fs.FileInfo { 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 { path, machineId := m.checkAndReturnPathMid(fileId, path) sftpCli := m.getSftpCli(machineId) @@ -148,6 +174,11 @@ func (m *machineFileAppImpl) RemoveFile(fileId uint64, path string) { fi, _ := file.Stat() if fi.IsDir() { err = sftpCli.RemoveDirectory(path) + // 如果文件夹有内容会删除失败,则使用rm -rf命令删除 + if err != nil { + MachineApp.GetCli(machineId).Run(fmt.Sprintf("rm -rf %s", path)) + err = nil + } } else { err = sftpCli.Remove(path) } diff --git a/server/internal/devops/router/machine_file.go b/server/internal/devops/router/machine_file.go index 9ccd50aa..8a262c36 100644 --- a/server/internal/devops/router/machine_file.go +++ b/server/internal/devops/router/machine_file.go @@ -43,14 +43,14 @@ func InitMachineFileRouter(router *gin.RouterGroup) { getContent := ctx.NewLogInfo("读取机器文件内容") machineFile.GET(":machineId/files/:fileId/read", func(c *gin.Context) { - rc := ctx.NewReqCtxWithGin(c).WithLog(getContent) - rc.Handle(mf.ReadFileContent) + ctx.NewReqCtxWithGin(c).WithLog(getContent). + Handle(mf.ReadFileContent) }) getDir := ctx.NewLogInfo("读取机器目录") machineFile.GET(":machineId/files/:fileId/read-dir", func(c *gin.Context) { - rc := ctx.NewReqCtxWithGin(c).WithLog(getDir) - rc.Handle(mf.GetDirEntry) + ctx.NewReqCtxWithGin(c).WithLog(getDir). + Handle(mf.GetDirEntry) }) writeFile := ctx.NewLogInfo("写入or下载文件内容") @@ -61,6 +61,13 @@ func InitMachineFileRouter(router *gin.RouterGroup) { 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("文件上传") ufP := ctx.NewPermission("machine:file:upload") machineFile.POST(":machineId/files/:fileId/upload", func(c *gin.Context) {