1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +08:00

feat: 文件管理页面列表

This commit is contained in:
zhengkunwang223 2022-08-24 11:10:50 +08:00
parent 07af78758f
commit 58c5af686c
14 changed files with 315 additions and 38 deletions

View File

@ -14,4 +14,5 @@ var (
groupService = service.ServiceGroupApp.GroupService groupService = service.ServiceGroupApp.GroupService
commandService = service.ServiceGroupApp.CommandService commandService = service.ServiceGroupApp.CommandService
operationService = service.ServiceGroupApp.OperationService operationService = service.ServiceGroupApp.OperationService
fileService = service.ServiceGroupApp.FileService
) )

View File

@ -0,0 +1,22 @@
package v1
import (
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/constant"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) ListFiles(c *gin.Context) {
var req dto.FileOption
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
files, err := fileService.GetFileList(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, files)
}

11
backend/app/dto/file.go Normal file
View File

@ -0,0 +1,11 @@
package dto
import "github.com/1Panel-dev/1Panel/utils/files"
type FileOption struct {
files.FileOption
}
type FileInfo struct {
files.FileInfo
}

View File

@ -8,6 +8,7 @@ type ServiceGroup struct {
GroupService GroupService
CommandService CommandService
OperationService OperationService
FileService
} }
var ServiceGroupApp = new(ServiceGroup) var ServiceGroupApp = new(ServiceGroup)

View File

@ -0,0 +1,27 @@
package service
import (
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/utils/files"
)
type FileService struct {
}
type IFileService interface {
GetFileList(op dto.FileOption) (dto.FileInfo, error)
}
func NewFileService() IFileService {
return FileService{}
}
func (f FileService) GetFileList(op dto.FileOption) (dto.FileInfo, error) {
var fileInfo dto.FileInfo
info, err := files.NewFileInfo(op.FileOption)
if err != nil {
return fileInfo, err
}
fileInfo.FileInfo = *info
return fileInfo, nil
}

View File

@ -44,6 +44,7 @@ func Routers() *gin.Engine {
systemRouter.InitCommandRouter(PrivateGroup) systemRouter.InitCommandRouter(PrivateGroup)
systemRouter.InitTerminalRouter(PrivateGroup) systemRouter.InitTerminalRouter(PrivateGroup)
systemRouter.InitOperationLogRouter(PrivateGroup) systemRouter.InitOperationLogRouter(PrivateGroup)
systemRouter.InitFileRouter(PrivateGroup)
} }
return Router return Router

View File

@ -7,6 +7,7 @@ type RouterGroup struct {
GroupRouter GroupRouter
CommandRouter CommandRouter
OperationLogRouter OperationLogRouter
FileRouter
} }
var RouterGroupApp = new(RouterGroup) var RouterGroupApp = new(RouterGroup)

21
backend/router/ro_file.go Normal file
View File

@ -0,0 +1,21 @@
package router
import (
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
"github.com/1Panel-dev/1Panel/middleware"
"github.com/gin-gonic/gin"
)
type FileRouter struct {
}
func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
fileRouter := Router.Group("files")
fileRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
//withRecordRouter := fileRouter.Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi
{
fileRouter.POST("/search", baseApi.ListFiles)
}
}

View File

@ -0,0 +1,123 @@
package files
import (
"fmt"
"github.com/spf13/afero"
"os"
"path"
"path/filepath"
"syscall"
"time"
)
type FileInfo struct {
Fs afero.Fs `json:"-"`
Path string `json:"path"`
Name string `json:"name"`
User string `json:"user"`
Group string `json:"group"`
Extension string `json:"extension"`
Content string `json:"content"`
Size int64 `json:"size"`
IsDir bool `json:"isDir"`
IsSymlink bool `json:"isSymlink"`
Type string `json:"type"`
Mode string `json:"mode"`
UpdateTime time.Time `json:"updateTime"`
ModTime time.Time `json:"modTime"`
FileMode os.FileMode `json:"-"`
Items []*FileInfo `json:"items"`
}
type FileOption struct {
Path string `json:"path"`
Search string `json:"search"`
Expand bool `json:"expand"`
}
func NewFileInfo(op FileOption) (*FileInfo, error) {
var appFs = afero.NewOsFs()
info, err := appFs.Stat(op.Path)
if err != nil {
return nil, err
}
file := &FileInfo{
Fs: appFs,
Path: op.Path,
Name: info.Name(),
IsDir: info.IsDir(),
FileMode: info.Mode(),
ModTime: info.ModTime(),
Size: info.Size(),
IsSymlink: IsSymlink(info.Mode()),
Extension: filepath.Ext(info.Name()),
Mode: fmt.Sprintf("%04o", info.Mode().Perm()),
User: GetUsername(info.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(info.Sys().(*syscall.Stat_t).Gid),
}
if op.Expand {
if file.IsDir {
if err := file.listChildren(); err != nil {
return nil, err
}
return file, nil
} else {
if err := file.getContent(); err != nil {
return nil, err
}
}
}
return file, nil
}
func (f *FileInfo) listChildren() error {
afs := &afero.Afero{Fs: f.Fs}
dir, err := afs.ReadDir(f.Path)
if err != nil {
return err
}
var items []*FileInfo
for _, df := range dir {
name := df.Name()
fPath := path.Join(f.Path, df.Name())
isSymlink, isInvalidLink := false, false
if IsSymlink(df.Mode()) {
isSymlink = true
info, err := f.Fs.Stat(fPath)
if err == nil {
df = info
} else {
isInvalidLink = true
}
}
file := &FileInfo{
Fs: f.Fs,
Name: name,
Size: df.Size(),
ModTime: df.ModTime(),
FileMode: df.Mode(),
IsDir: df.IsDir(),
IsSymlink: isSymlink,
Extension: filepath.Ext(name),
Path: fPath,
Mode: fmt.Sprintf("%04o", df.Mode().Perm()),
User: GetUsername(df.Sys().(*syscall.Stat_t).Uid),
Group: GetGroup(df.Sys().(*syscall.Stat_t).Gid),
}
if isInvalidLink {
file.Type = "invalid_link"
}
items = append(items, file)
}
f.Items = items
return nil
}
func (f *FileInfo) getContent() error {
return nil
}

View File

@ -0,0 +1,27 @@
package files
import (
"os"
"os/user"
"strconv"
)
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
func GetUsername(uid uint32) string {
usr, err := user.LookupId(strconv.Itoa(int(uid)))
if err != nil {
return ""
}
return usr.Username
}
func GetGroup(gid uint32) string {
usr, err := user.LookupGroupId(strconv.Itoa(int(gid)))
if err != nil {
return ""
}
return usr.Name
}

View File

@ -1,16 +1,24 @@
import { CommonModel } from '.'; import { CommonModel } from '.';
export namespace File { export namespace File {
export interface File extends CommonModel { export interface File extends CommonModel {
path: string;
name: string; name: string;
mode: number;
user: string; user: string;
group: string; group: string;
updateDate: string; content: string;
isDir: boolean;
isLink: boolean;
path: string;
size: number; size: number;
accessTime: string; isDir: boolean;
changeTime: string; isSymlink: boolean;
type: string;
updateTime: string;
modTime: string;
mode: number;
items: File[];
}
export interface ReqFile {
path: string;
search?: string;
expand: boolean;
} }
} }

View File

@ -1,7 +1,7 @@
// import { File } from '@/api/interface/file'; import { File } from '@/api/interface/file';
import files from '@/api/interface/files.json'; import http from '@/api';
import { ResultData } from '@/api/interface';
export const GetFilesList = () => { export const GetFilesList = (params: File.ReqFile) => {
// return http.post<Login.ResLogin>(`/auth/login`, params); return http.post<ResultData<File.File>>('files/search', params);
return files;
}; };

View File

@ -174,5 +174,7 @@ export default {
terminal: '终端', terminal: '终端',
shareList: '分享列表', shareList: '分享列表',
zip: '压缩', zip: '压缩',
user: '用户',
group: '组',
}, },
}; };

View File

@ -2,7 +2,7 @@
<LayoutContent :header="$t('menu.files')"> <LayoutContent :header="$t('menu.files')">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="6"> <el-col :span="6">
<el-tree :data="dataSource" show-checkbox node-key="id"> <el-tree :data="dataSource" node-key="id">
<template #default="{ node }"> <template #default="{ node }">
<el-icon v-if="node.data.isDir && node.expanded"><FolderOpened /></el-icon> <el-icon v-if="node.data.isDir && node.expanded"><FolderOpened /></el-icon>
<el-icon v-if="node.data.isDir && !node.expanded"><Folder /></el-icon> <el-icon v-if="node.data.isDir && !node.expanded"><Folder /></el-icon>
@ -15,14 +15,20 @@
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<div class="path"> <div class="path">
<el-breadcrumb class="child" :separator-icon="ArrowRight"> <el-breadcrumb :separator-icon="ArrowRight">
<el-breadcrumb-item>root</el-breadcrumb-item> <el-breadcrumb-item @click="jump(-1)">root</el-breadcrumb-item>
<el-breadcrumb-item>var</el-breadcrumb-item> <el-breadcrumb-item v-for="(item, key) in paths" :key="key" @click="jump(key)">{{
<el-breadcrumb-item>log</el-breadcrumb-item> item
}}</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</div> </div>
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data"> <ComplexTable
:pagination-config="paginationConfig"
v-model:selects="selects"
:data="data"
:loading="loading"
>
<template #toolbar> <template #toolbar>
<el-dropdown split-button type="primary"> <el-dropdown split-button type="primary">
{{ $t('commons.button.create') }} {{ $t('commons.button.create') }}
@ -51,18 +57,23 @@
<template #default="{ row }"> <template #default="{ row }">
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon> <svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
<svg-icon v-else className="table-icon" iconName="p-file-normal"></svg-icon> <svg-icon v-else className="table-icon" iconName="p-file-normal"></svg-icon>
<el-link :underline="false">{{ row.name }}</el-link> <el-link :underline="false" @click="open(row.name)">{{ row.name }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('file.mode')" prop="mode"> </el-table-column> <el-table-column :label="$t('file.mode')" prop="mode"> </el-table-column>
<el-table-column :label="$t('file.owner')" prop="user"> </el-table-column> <el-table-column :label="$t('file.user')" prop="user"> </el-table-column>
<el-table-column :label="$t('file.group')" prop="group"> </el-table-column>
<el-table-column :label="$t('file.size')" prop="size"> </el-table-column> <el-table-column :label="$t('file.size')" prop="size"> </el-table-column>
<el-table-column :label="$t('file.updateTime')" prop="updateTime" :formatter="dateFromat"> <el-table-column
:label="$t('file.updateTime')"
prop="modTime"
:formatter="dateFromat"
min-width="150"
>
</el-table-column> </el-table-column>
<fu-table-operations <fu-table-operations
min-width="300" :ellipsis="1"
:ellipsis="4"
:buttons="buttons" :buttons="buttons"
:label="$t('commons.table.operate')" :label="$t('commons.table.operate')"
fixed="right" fixed="right"
@ -82,6 +93,7 @@ import i18n from '@/lang';
import { GetFilesList } from '@/api/modules/files'; import { GetFilesList } from '@/api/modules/files';
import { dateFromat } from '@/utils/util'; import { dateFromat } from '@/utils/util';
import { ArrowRight } from '@element-plus/icons-vue'; import { ArrowRight } from '@element-plus/icons-vue';
import { File } from '@/api/interface/file';
interface Tree { interface Tree {
id: number; id: number;
label: string; label: string;
@ -90,6 +102,9 @@ interface Tree {
} }
let data = ref(); let data = ref();
let selects = ref<any>([]); let selects = ref<any>([]);
let req = reactive({ path: '/', expand: true });
let loading = ref<boolean>(false);
let paths = ref<string[]>([]);
const paginationConfig = reactive({ const paginationConfig = reactive({
page: 1, page: 1,
pageSize: 5, pageSize: 5,
@ -116,10 +131,37 @@ const buttons = [
}, },
]; ];
const search = async () => { const search = (req: File.ReqFile) => {
const res = await GetFilesList(); loading.value = true;
data.value = res.data.items; GetFilesList(req)
paginationConfig.total = res.data.total; .then((res) => {
data.value = res.data.items;
})
.finally(() => {
loading.value = false;
});
};
const open = async (name: string) => {
paths.value.push(name);
if (req.path === '/') {
req.path = req.path + name;
} else {
req.path = req.path + '/' + name;
}
search(req);
};
const jump = async (index: number) => {
let path = '/';
if (index != -1) {
const jPaths = paths.value.slice(0, index + 1);
for (let i in jPaths) {
path = path + '/' + jPaths[i];
}
}
req.path = path;
search(req);
}; };
const dataSource = ref<Tree[]>([ const dataSource = ref<Tree[]>([
@ -167,7 +209,7 @@ const dataSource = ref<Tree[]>([
]); ]);
onMounted(() => { onMounted(() => {
search(); search(req);
}); });
</script> </script>
@ -175,16 +217,6 @@ onMounted(() => {
.path { .path {
margin-top: -50px; margin-top: -50px;
height: 30px; height: 30px;
width: 800px;
background-color: #f1f1f1;
margin-bottom: 5px; margin-bottom: 5px;
border: 1px solid #e8e8e8;
}
.child {
position: relative;
top: 50%;
transform: translateY(-50%);
width: 80%;
} }
</style> </style>