refactor: 机器文件操作优化

This commit is contained in:
meilin.huang
2023-07-05 00:26:00 +08:00
parent 3266039aaf
commit f4ac6d8360
12 changed files with 164 additions and 50 deletions

View File

@@ -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">&nbsp;[{{ 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"),

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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, "/") {

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
View File

@@ -0,0 +1,7 @@
package utils
import "time"
func DefaultTimeFormat(time time.Time) string {
return time.Format("2006-01-02 15:04:05")
}