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
|
groupService = service.ServiceGroupApp.GroupService
|
||||||
commandService = service.ServiceGroupApp.CommandService
|
commandService = service.ServiceGroupApp.CommandService
|
||||||
operationService = service.ServiceGroupApp.OperationService
|
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
|
GroupService
|
||||||
CommandService
|
CommandService
|
||||||
OperationService
|
OperationService
|
||||||
|
FileService
|
||||||
}
|
}
|
||||||
|
|
||||||
var ServiceGroupApp = new(ServiceGroup)
|
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.InitCommandRouter(PrivateGroup)
|
||||||
systemRouter.InitTerminalRouter(PrivateGroup)
|
systemRouter.InitTerminalRouter(PrivateGroup)
|
||||||
systemRouter.InitOperationLogRouter(PrivateGroup)
|
systemRouter.InitOperationLogRouter(PrivateGroup)
|
||||||
|
systemRouter.InitFileRouter(PrivateGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Router
|
return Router
|
||||||
|
@ -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
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 '.';
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
@ -174,5 +174,7 @@ export default {
|
|||||||
terminal: '终端',
|
terminal: '终端',
|
||||||
shareList: '分享列表',
|
shareList: '分享列表',
|
||||||
zip: '压缩',
|
zip: '压缩',
|
||||||
|
user: '用户',
|
||||||
|
group: '组',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
GetFilesList(req)
|
||||||
|
.then((res) => {
|
||||||
data.value = res.data.items;
|
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[]>([
|
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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user