mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 文件管理页面列表
This commit is contained in:
parent
07af78758f
commit
58c5af686c
@ -14,4 +14,5 @@ var (
|
||||
groupService = service.ServiceGroupApp.GroupService
|
||||
commandService = service.ServiceGroupApp.CommandService
|
||||
operationService = service.ServiceGroupApp.OperationService
|
||||
fileService = service.ServiceGroupApp.FileService
|
||||
)
|
||||
|
22
backend/app/api/v1/file.go
Normal file
22
backend/app/api/v1/file.go
Normal 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
11
backend/app/dto/file.go
Normal 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
|
||||
}
|
@ -8,6 +8,7 @@ type ServiceGroup struct {
|
||||
GroupService
|
||||
CommandService
|
||||
OperationService
|
||||
FileService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
|
27
backend/app/service/file.go
Normal file
27
backend/app/service/file.go
Normal 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
|
||||
}
|
@ -44,6 +44,7 @@ func Routers() *gin.Engine {
|
||||
systemRouter.InitCommandRouter(PrivateGroup)
|
||||
systemRouter.InitTerminalRouter(PrivateGroup)
|
||||
systemRouter.InitOperationLogRouter(PrivateGroup)
|
||||
systemRouter.InitFileRouter(PrivateGroup)
|
||||
}
|
||||
|
||||
return Router
|
||||
|
@ -7,6 +7,7 @@ type RouterGroup struct {
|
||||
GroupRouter
|
||||
CommandRouter
|
||||
OperationLogRouter
|
||||
FileRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
|
21
backend/router/ro_file.go
Normal file
21
backend/router/ro_file.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
123
backend/utils/files/fileinfo.go
Normal file
123
backend/utils/files/fileinfo.go
Normal 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
|
||||
}
|
27
backend/utils/files/utils.go
Normal file
27
backend/utils/files/utils.go
Normal 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
|
||||
}
|
@ -1,16 +1,24 @@
|
||||
import { CommonModel } from '.';
|
||||
export namespace File {
|
||||
export interface File extends CommonModel {
|
||||
path: string;
|
||||
name: string;
|
||||
mode: number;
|
||||
user: string;
|
||||
group: string;
|
||||
updateDate: string;
|
||||
isDir: boolean;
|
||||
isLink: boolean;
|
||||
path: string;
|
||||
content: string;
|
||||
size: number;
|
||||
accessTime: string;
|
||||
changeTime: string;
|
||||
isDir: boolean;
|
||||
isSymlink: boolean;
|
||||
type: string;
|
||||
updateTime: string;
|
||||
modTime: string;
|
||||
mode: number;
|
||||
items: File[];
|
||||
}
|
||||
|
||||
export interface ReqFile {
|
||||
path: string;
|
||||
search?: string;
|
||||
expand: boolean;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// import { File } from '@/api/interface/file';
|
||||
import files from '@/api/interface/files.json';
|
||||
import { File } from '@/api/interface/file';
|
||||
import http from '@/api';
|
||||
import { ResultData } from '@/api/interface';
|
||||
|
||||
export const GetFilesList = () => {
|
||||
// return http.post<Login.ResLogin>(`/auth/login`, params);
|
||||
return files;
|
||||
export const GetFilesList = (params: File.ReqFile) => {
|
||||
return http.post<ResultData<File.File>>('files/search', params);
|
||||
};
|
||||
|
@ -174,5 +174,7 @@ export default {
|
||||
terminal: '终端',
|
||||
shareList: '分享列表',
|
||||
zip: '压缩',
|
||||
user: '用户',
|
||||
group: '组',
|
||||
},
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
<LayoutContent :header="$t('menu.files')">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-tree :data="dataSource" show-checkbox node-key="id">
|
||||
<el-tree :data="dataSource" node-key="id">
|
||||
<template #default="{ node }">
|
||||
<el-icon v-if="node.data.isDir && node.expanded"><FolderOpened /></el-icon>
|
||||
<el-icon v-if="node.data.isDir && !node.expanded"><Folder /></el-icon>
|
||||
@ -15,14 +15,20 @@
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="path">
|
||||
<el-breadcrumb class="child" :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item>root</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>var</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>log</el-breadcrumb-item>
|
||||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item @click="jump(-1)">root</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="(item, key) in paths" :key="key" @click="jump(key)">{{
|
||||
item
|
||||
}}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</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>
|
||||
<el-dropdown split-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
@ -51,18 +57,23 @@
|
||||
<template #default="{ row }">
|
||||
<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>
|
||||
<el-link :underline="false">{{ row.name }}</el-link>
|
||||
<el-link :underline="false" @click="open(row.name)">{{ row.name }}</el-link>
|
||||
</template>
|
||||
</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.updateTime')" prop="updateTime" :formatter="dateFromat">
|
||||
<el-table-column
|
||||
:label="$t('file.updateTime')"
|
||||
prop="modTime"
|
||||
:formatter="dateFromat"
|
||||
min-width="150"
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<fu-table-operations
|
||||
min-width="300"
|
||||
:ellipsis="4"
|
||||
:ellipsis="1"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
@ -82,6 +93,7 @@ import i18n from '@/lang';
|
||||
import { GetFilesList } from '@/api/modules/files';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { ArrowRight } from '@element-plus/icons-vue';
|
||||
import { File } from '@/api/interface/file';
|
||||
interface Tree {
|
||||
id: number;
|
||||
label: string;
|
||||
@ -90,6 +102,9 @@ interface Tree {
|
||||
}
|
||||
let data = ref();
|
||||
let selects = ref<any>([]);
|
||||
let req = reactive({ path: '/', expand: true });
|
||||
let loading = ref<boolean>(false);
|
||||
let paths = ref<string[]>([]);
|
||||
const paginationConfig = reactive({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
@ -116,10 +131,37 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const search = async () => {
|
||||
const res = await GetFilesList();
|
||||
const search = (req: File.ReqFile) => {
|
||||
loading.value = true;
|
||||
GetFilesList(req)
|
||||
.then((res) => {
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.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[]>([
|
||||
@ -167,7 +209,7 @@ const dataSource = ref<Tree[]>([
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
search(req);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -175,16 +217,6 @@ onMounted(() => {
|
||||
.path {
|
||||
margin-top: -50px;
|
||||
height: 30px;
|
||||
width: 800px;
|
||||
background-color: #f1f1f1;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.child {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user