mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +08:00
refactor: 机器文件操作优化
This commit is contained in:
@@ -3,21 +3,21 @@
|
||||
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="800px">
|
||||
<div class="toolbar">
|
||||
<div style="float: right">
|
||||
<el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" size="small" plain>添加
|
||||
<el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" plain>添加
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="fileTable" stripe style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="name" label="名称" width>
|
||||
<el-table-column prop="name" label="名称" min-width="70px">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.name" size="small" :disabled="scope.row.id != null" clearable>
|
||||
<el-input v-model="scope.row.name" :disabled="scope.row.id != null" clearable>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="类型" min-width="50px">
|
||||
<template #default="scope">
|
||||
<el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type"
|
||||
style="width: 100px" placeholder="请选择">
|
||||
<el-select :disabled="scope.row.id != null" v-model="scope.row.type" style="width: 100px"
|
||||
placeholder="请选择">
|
||||
<el-option v-for="item in enums.FileTypeEnum as any" :key="item.value" :label="item.label"
|
||||
:value="item.value"></el-option>
|
||||
</el-select>
|
||||
@@ -25,18 +25,18 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="path" label="路径" width>
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.path" :disabled="scope.row.id != null" size="small" clearable>
|
||||
<el-input v-model="scope.row.path" :disabled="scope.row.id != null" clearable>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width>
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success"
|
||||
icon="success-filled" size="small" plain>确定</el-button>
|
||||
icon="success-filled" plain>确定</el-button>
|
||||
<el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets"
|
||||
size="small" plain>查看</el-button>
|
||||
plain>查看</el-button>
|
||||
<el-button v-auth="'machine:file:del'" type="danger" @click="deleteRow(scope.$index, scope.row)"
|
||||
icon="delete" size="small" plain>删除</el-button>
|
||||
icon="delete" plain>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -50,24 +50,24 @@
|
||||
<el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="70%">
|
||||
<el-progress v-if="uploadProgressShow" style="width: 90%; margin-left: 20px" :text-inside="true"
|
||||
:stroke-width="20" :percentage="progressNum" />
|
||||
<div style="height: 45vh; overflow: auto">
|
||||
<div style="height: 55vh; overflow: auto">
|
||||
<el-tree v-if="tree.visible" ref="fileTree" :highlight-current="true" :load="loadNode" :props="treeProps"
|
||||
lazy node-key="id" :expand-on-click-node="true">
|
||||
lazy node-key="id" :expand-on-click-node="false">
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<el-dropdown size="small" @visible-change="getFilePath(data, $event)" trigger="contextmenu">
|
||||
<span class="el-dropdown-link">
|
||||
<span v-if="data.type == 'd' && !node.expanded">
|
||||
<SvgIcon name="folder" />
|
||||
<SvgIcon :size="15" name="folder" />
|
||||
</span>
|
||||
<span v-if="data.type == 'd' && node.expanded">
|
||||
<SvgIcon name="folder-opened" />
|
||||
<SvgIcon :size="15" name="folder-opened" />
|
||||
</span>
|
||||
<span v-if="data.type == '-'">
|
||||
<SvgIcon name="document" />
|
||||
<SvgIcon :size="15" name="document" />
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<span class="ml5" style="font-weight: bold;">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -114,10 +114,28 @@
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span style="display: inline-block" class="ml15">
|
||||
<span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size)
|
||||
}}]</span>
|
||||
<span v-if="data.mode" style="color: #67c23a"> [{{ data.mode }} {{ data.modTime
|
||||
}}]</span>
|
||||
<span style="color: #67c23a;font-weight: bold;" v-if="data.type == '-'">
|
||||
[{{ formatFileSize(data.size) }}]
|
||||
</span>
|
||||
<span style="color: #67c23a;font-weight: bold;" v-if="data.type == 'd' && data.dirSize">
|
||||
[{{ data.dirSize }}]
|
||||
</span>
|
||||
<span style="color: #67c23a;font-weight: bold;" v-if="data.type == 'd' && !data.dirSize">
|
||||
[<el-button @click="getDirSize(data)" type="primary" link
|
||||
:loading="data.loadingDirSize">size</el-button>]
|
||||
</span>
|
||||
|
||||
<el-popover placement="top-start" :title="`${data.path}-文件详情`" :width="520" trigger="click"
|
||||
@show="showFileStat(data)">
|
||||
<template #reference>
|
||||
<span style="color: #67c23a;font-weight: bold;">
|
||||
[<el-button @click="showFileStat(data)" type="primary" link
|
||||
:loading="data.loadingStat">stat</el-button>]
|
||||
</span>
|
||||
</template>
|
||||
<el-input :input-style="{ color: 'black' }" disabled autosize v-model="data.stat"
|
||||
type="textarea" />
|
||||
</el-popover>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
@@ -444,6 +462,37 @@ const loadNode = async (node: any, resolve: any) => {
|
||||
return resolve(res);
|
||||
};
|
||||
|
||||
const getDirSize = async (data: any) => {
|
||||
try {
|
||||
data.loadingDirSize = true;
|
||||
const res = await machineApi.dirSize.request({
|
||||
machineId: props.machineId,
|
||||
fileId: state.tree.folder.id,
|
||||
path: data.path
|
||||
})
|
||||
data.dirSize = res;
|
||||
} finally {
|
||||
data.loadingDirSize = false;
|
||||
}
|
||||
}
|
||||
|
||||
const showFileStat = async (data: any) => {
|
||||
try {
|
||||
if (data.stat) {
|
||||
return;
|
||||
}
|
||||
data.loadingStat = true;
|
||||
const res = await machineApi.fileStat.request({
|
||||
machineId: props.machineId,
|
||||
fileId: state.tree.folder.id,
|
||||
path: data.path
|
||||
})
|
||||
data.stat = res;
|
||||
} finally {
|
||||
data.loadingStat = false;
|
||||
}
|
||||
}
|
||||
|
||||
const showCreateFileDialog = (node: any) => {
|
||||
isTrue(node.expanded, '请先点击展开该节点后再创建');
|
||||
state.createFileDialog.node = node;
|
||||
@@ -595,4 +644,4 @@ const formatFileSize = (size: any) => {
|
||||
return '-';
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss"></style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="mock-data-dialog">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :before-close="cancel"
|
||||
:show-close="true" :destroy-on-close="true" width="900px">
|
||||
<el-form :model="form" ref="scriptForm" label-width="auto" size="small">
|
||||
<el-form :model="form" ref="scriptForm" label-width="auto" >
|
||||
<el-form-item prop="method" label="名称">
|
||||
<el-input v-model="form.name" placeholder="请输入名称"></el-input>
|
||||
</el-form-item>
|
||||
@@ -19,7 +19,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-row style="margin-left: 30px; margin-bottom: 5px">
|
||||
<el-button @click="onAddParam" size="small" type="success">新增占位符参数</el-button>
|
||||
<el-button @click="onAddParam" type="success">新增占位符参数</el-button>
|
||||
</el-row>
|
||||
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
|
||||
<el-row>
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
</span>
|
||||
<el-col :span="2">
|
||||
<el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
|
||||
<el-button @click="onDeleteParam(index)" type="danger">删除</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
@@ -4,23 +4,22 @@
|
||||
:before-close="handleClose" width="60%">
|
||||
<div class="toolbar">
|
||||
<div style="float: left">
|
||||
<el-select v-model="type" @change="getScripts" size="small" placeholder="请选择">
|
||||
<el-select v-model="type" @change="getScripts" placeholder="请选择">
|
||||
<el-option :key="0" label="私有" :value="0"> </el-option>
|
||||
<el-option :key="1" label="公共" :value="1"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary" icon="tickets"
|
||||
size="small" plain>查看</el-button>
|
||||
plain>查看</el-button>
|
||||
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus"
|
||||
size="small" plain>添加</el-button>
|
||||
plain>添加</el-button>
|
||||
<el-button v-auth="'machine:script:del'" :disabled="currentId == null" type="danger"
|
||||
@click="deleteRow(currentData)" icon="delete" size="small" plain>删除</el-button>
|
||||
@click="deleteRow(currentData)" icon="delete" plain>删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="scriptTable" @current-change="choose" stripe border size="small" v-loading="loading"
|
||||
style="width: 100%">
|
||||
<el-table :data="scriptTable" @current-change="choose" stripe border v-loading="loading" style="width: 100%">
|
||||
<el-table-column label="选择" width="55px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="currentId" :label="scope.row.id">
|
||||
@@ -37,11 +36,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.id == null" type="success" icon="el-icon-success" size="small" plain>
|
||||
<el-button v-if="scope.row.id == null" type="success" icon="el-icon-success" plain>
|
||||
确定</el-button>
|
||||
|
||||
<el-button v-auth="'machine:script:run'" v-if="scope.row.id != null" @click="runScript(scope.row)"
|
||||
type="primary" icon="video-play" size="small" plain>执行
|
||||
type="primary" icon="video-play" plain>执行
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -54,7 +53,7 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
|
||||
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="auto" size="small">
|
||||
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="auto">
|
||||
<el-form-item v-for="item in scriptParamsDialog.paramsFormItem as any" :key="item.name" :prop="item.model"
|
||||
:label="item.name" required>
|
||||
<el-input v-if="!item.options" v-model="scriptParamsDialog.params[item.model]"
|
||||
@@ -68,7 +67,7 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="hasParamsRun(currentData)" size="small">确 定</el-button>
|
||||
<el-button type="primary" @click="hasParamsRun(currentData)">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -24,6 +24,8 @@ export const machineApi = {
|
||||
// 获取配置文件列表
|
||||
files: Api.newGet("/machines/{id}/files"),
|
||||
lsFile: Api.newGet("/machines/{machineId}/files/{fileId}/read-dir"),
|
||||
dirSize: Api.newGet("/machines/{machineId}/files/{fileId}/dir-size"),
|
||||
fileStat: Api.newGet("/machines/{machineId}/files/{fileId}/file-stat"),
|
||||
rmFile: Api.newDelete("/machines/{machineId}/files/{fileId}/remove"),
|
||||
uploadFile: Api.newPost("/machines/{machineId}/files/{fileId}/upload?token={token}"),
|
||||
fileContent: Api.newGet("/machines/{machineId}/files/{fileId}/read"),
|
||||
|
||||
@@ -76,7 +76,6 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
|
||||
m.MachineFileApp.CreateFile(fid, form.Path)
|
||||
rc.ReqParam = fmt.Sprintf("%s -> 创建文件: %s", mi.GetLogDesc(), path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
|
||||
@@ -121,12 +120,10 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
|
||||
fisVO := make([]vo.MachineFileInfo, 0)
|
||||
for _, fi := range fis {
|
||||
fisVO = append(fisVO, vo.MachineFileInfo{
|
||||
Name: fi.Name(),
|
||||
Size: fi.Size(),
|
||||
Path: readPath + fi.Name(),
|
||||
Type: getFileType(fi.Mode()),
|
||||
Mode: fi.Mode().String(),
|
||||
ModTime: fi.ModTime().Format("2006-01-02 15:04:05"),
|
||||
Name: fi.Name(),
|
||||
Size: fi.Size(),
|
||||
Path: readPath + fi.Name(),
|
||||
Type: getFileType(fi.Mode()),
|
||||
})
|
||||
}
|
||||
sort.Sort(vo.MachineFileInfos(fisVO))
|
||||
@@ -134,6 +131,22 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
|
||||
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
|
||||
}
|
||||
|
||||
func (m *MachineFile) GetDirSize(rc *req.Ctx) {
|
||||
g := rc.GinCtx
|
||||
fid := GetMachineFileId(g)
|
||||
readPath := g.Query("path")
|
||||
|
||||
rc.ResData = m.MachineFileApp.GetDirSize(fid, readPath)
|
||||
}
|
||||
|
||||
func (m *MachineFile) GetFileStat(rc *req.Ctx) {
|
||||
g := rc.GinCtx
|
||||
fid := GetMachineFileId(g)
|
||||
readPath := g.Query("path")
|
||||
|
||||
rc.ResData = m.MachineFileApp.FileStat(fid, readPath)
|
||||
}
|
||||
|
||||
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
|
||||
g := rc.GinCtx
|
||||
fid := GetMachineFileId(g)
|
||||
|
||||
@@ -70,7 +70,9 @@ func (m *MachineScript) RunMachineScript(rc *req.Ctx) {
|
||||
res, err := cli.Run(script)
|
||||
// 记录请求参数
|
||||
rc.ReqParam = fmt.Sprintf("[machine: %s, scriptId: %d, name: %s]", cli.GetMachine().GetLogDesc(), scriptId, ms.Name)
|
||||
biz.ErrIsNilAppendErr(err, "执行命令失败:%s")
|
||||
if res == "" {
|
||||
biz.ErrIsNilAppendErr(err, "执行命令失败:%s")
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,10 @@ type MachineFileVO struct {
|
||||
}
|
||||
|
||||
type MachineFileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Mode string `json:"mode"`
|
||||
ModTime string `json:"modTime"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type MachineFileInfos []MachineFileInfo
|
||||
|
||||
@@ -44,6 +44,12 @@ type MachineFile interface {
|
||||
// 读取目录
|
||||
ReadDir(fid uint64, path string) []fs.FileInfo
|
||||
|
||||
// 获取指定目录内容大小
|
||||
GetDirSize(fid uint64, path string) string
|
||||
|
||||
// 获取文件stat
|
||||
FileStat(fid uint64, path string) string
|
||||
|
||||
// 读取文件内容
|
||||
ReadFile(fileId uint64, path string) *sftp.File
|
||||
|
||||
@@ -110,6 +116,35 @@ func (m *machineFileAppImpl) ReadDir(fid uint64, path string) []fs.FileInfo {
|
||||
return fis
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) GetDirSize(fid uint64, path string) string {
|
||||
_, machineId := m.checkAndReturnPathMid(fid, path)
|
||||
res, err := GetMachineApp().GetCli(machineId).Run(fmt.Sprintf("du -sh %s", path))
|
||||
if err != nil {
|
||||
// 若存在目录为空,则可能会返回如下内容。最后一行即为真正目录内容所占磁盘空间大小
|
||||
//du: cannot access ‘/proc/19087/fd/3’: No such file or directory\n
|
||||
//du: cannot access ‘/proc/19087/fdinfo/3’: No such file or directory\n
|
||||
//18G /\n
|
||||
if res == "" {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("获取目录大小失败: %s", err.Error())))
|
||||
}
|
||||
strs := strings.Split(res, "\n")
|
||||
res = strs[len(strs)-2]
|
||||
|
||||
if !strings.Contains(res, "\t") {
|
||||
panic(biz.NewBizErr(res))
|
||||
}
|
||||
}
|
||||
// 返回 32K\t/tmp\n
|
||||
return strings.Split(res, "\t")[0]
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) FileStat(fid uint64, path string) string {
|
||||
_, machineId := m.checkAndReturnPathMid(fid, path)
|
||||
res, err := GetMachineApp().GetCli(machineId).Run(fmt.Sprintf("stat -L %s", path))
|
||||
biz.ErrIsNil(err, res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *machineFileAppImpl) MkDir(fid uint64, path string) {
|
||||
path, machineId := m.checkAndReturnPathMid(fid, path)
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
|
||||
@@ -119,6 +119,7 @@ func (c *Cli) GetSession() (*ssh.Session, error) {
|
||||
|
||||
// 执行shell
|
||||
// @param shell shell脚本命令
|
||||
// @return 返回执行成功或错误的消息
|
||||
func (c *Cli) Run(shell string) (string, error) {
|
||||
session, err := c.GetSession()
|
||||
if err != nil {
|
||||
@@ -128,7 +129,7 @@ func (c *Cli) Run(shell string) (string, error) {
|
||||
defer session.Close()
|
||||
buf, err := session.CombinedOutput(shell)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return string(buf), err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
@@ -52,6 +52,14 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
|
||||
Handle(mf.GetDirEntry)
|
||||
})
|
||||
|
||||
machineFile.GET(":machineId/files/:fileId/dir-size", func(c *gin.Context) {
|
||||
req.NewCtxWithGin(c).Handle(mf.GetDirSize)
|
||||
})
|
||||
|
||||
machineFile.GET(":machineId/files/:fileId/file-stat", func(c *gin.Context) {
|
||||
req.NewCtxWithGin(c).Handle(mf.GetFileStat)
|
||||
})
|
||||
|
||||
writeFile := req.NewLogInfo("机器-修改文件内容").WithSave(true)
|
||||
wfP := req.NewPermission("machine:file:write")
|
||||
machineFile.POST(":machineId/files/:fileId/write", func(c *gin.Context) {
|
||||
|
||||
@@ -52,7 +52,7 @@ func PageQuery[T any](q *QueryCond, pageParam *model.PageParam, toModels T) *mod
|
||||
gdb := q.GenGdb()
|
||||
var count int64
|
||||
err := gdb.Count(&count).Error
|
||||
biz.ErrIsNilAppendErr(err, " 查询错误:%s")
|
||||
biz.ErrIsNilAppendErr(err, "查询错误: %s")
|
||||
if count == 0 {
|
||||
return model.EmptyPageResult[T]()
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func PageQuery[T any](q *QueryCond, pageParam *model.PageParam, toModels T) *mod
|
||||
page := pageParam.PageNum
|
||||
pageSize := pageParam.PageSize
|
||||
err = gdb.Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
|
||||
biz.ErrIsNil(err, "查询失败")
|
||||
biz.ErrIsNilAppendErr(err, "查询失败: %s")
|
||||
return &model.PageResult[T]{Total: count, List: toModels}
|
||||
}
|
||||
|
||||
|
||||
7
server/pkg/utils/time.go
Normal file
7
server/pkg/utils/time.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
func DefaultTimeFormat(time time.Time) string {
|
||||
return time.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
Reference in New Issue
Block a user