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

feat: 增加创建文件夹功能

This commit is contained in:
zhengkunwang223 2022-08-25 17:54:52 +08:00
parent e0757f4d65
commit f48ae22a15
12 changed files with 246 additions and 23 deletions

View File

@ -34,3 +34,17 @@ func (b *BaseApi) GetFileTree(c *gin.Context) {
} }
helper.SuccessWithData(c, tree) helper.SuccessWithData(c, tree)
} }
func (b *BaseApi) CreateFile(c *gin.Context) {
var req dto.FileCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := fileService.Create(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -1,6 +1,8 @@
package dto package dto
import "github.com/1Panel-dev/1Panel/utils/files" import (
"github.com/1Panel-dev/1Panel/utils/files"
)
type FileOption struct { type FileOption struct {
files.FileOption files.FileOption
@ -16,3 +18,10 @@ type FileTree struct {
Path string `json:"path"` Path string `json:"path"`
Children []FileTree `json:"children"` Children []FileTree `json:"children"`
} }
type FileCreate struct {
Path string
Content string
IsDir bool
Mode int64
}

View File

@ -6,19 +6,12 @@ import (
"github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/utils/files" "github.com/1Panel-dev/1Panel/utils/files"
"io" "io"
"io/fs"
) )
type FileService struct { 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) { func (f FileService) GetFileList(op dto.FileOption) (dto.FileInfo, error) {
var fileInfo dto.FileInfo var fileInfo dto.FileInfo
info, err := files.NewFileInfo(op.FileOption) info, err := files.NewFileInfo(op.FileOption)
@ -52,6 +45,16 @@ func (f FileService) GetFileTree(op dto.FileOption) ([]dto.FileTree, error) {
return append(treeArray, node), nil return append(treeArray, node), nil
} }
func (f FileService) Create(op dto.FileCreate) error {
fo := files.NewFileOp()
if op.IsDir {
return fo.CreateDir(op.Path, fs.FileMode(op.Mode))
}
return nil
}
func getUuid() string { func getUuid() string {
b := make([]byte, 16) b := make([]byte, 16)
io.ReadFull(rand.Reader, b) io.ReadFull(rand.Reader, b)

View File

@ -1,18 +1,75 @@
package cache package cache
import ( import (
"fmt"
"github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/init/cache/badger_db" "github.com/1Panel-dev/1Panel/init/cache/badger_db"
"github.com/dgraph-io/badger/v3" "github.com/dgraph-io/badger/v3"
"time"
) )
func Init() { func Init() {
c := global.CONF.Cache c := global.CONF.Cache
cache, err := badger.Open(badger.DefaultOptions(c.Path)) options := badger.Options{
Dir: c.Path,
ValueDir: c.Path,
ValueLogFileSize: 102400000,
ValueLogMaxEntries: 100000,
VLogPercentile: 0.1,
MemTableSize: 64 << 20,
BaseTableSize: 2 << 20,
BaseLevelSize: 10 << 20,
TableSizeMultiplier: 2,
LevelSizeMultiplier: 10,
MaxLevels: 7,
NumGoroutines: 8,
MetricsEnabled: true,
NumCompactors: 4,
NumLevelZeroTables: 5,
NumLevelZeroTablesStall: 15,
NumMemtables: 5,
BloomFalsePositive: 0.01,
BlockSize: 4 * 1024,
SyncWrites: false,
NumVersionsToKeep: 1,
CompactL0OnClose: false,
VerifyValueChecksum: false,
BlockCacheSize: 256 << 20,
IndexCacheSize: 0,
ZSTDCompressionLevel: 1,
EncryptionKey: []byte{},
EncryptionKeyRotationDuration: 10 * 24 * time.Hour, // Default 10 days.
DetectConflicts: true,
NamespaceOffset: -1,
}
cache, err := badger.Open(options)
if err != nil { if err != nil {
panic(err) panic(err)
} }
global.CACHE = badger_db.NewCacheDB(cache) global.CACHE = badger_db.NewCacheDB(cache)
err = cache.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
k := item.Key()
fmt.Printf("key=%s\n", k)
}
return nil
})
if err != nil {
panic(err)
}
fmt.Printf("run gc")
err = cache.RunValueLogGC(0.01)
if err != nil {
fmt.Printf(err.Error())
}
} }

View File

@ -17,6 +17,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
{ {
fileRouter.POST("/search", baseApi.ListFiles) fileRouter.POST("/search", baseApi.ListFiles)
fileRouter.POST("/tree", baseApi.GetFileTree) fileRouter.POST("/tree", baseApi.GetFileTree)
fileRouter.POST("", baseApi.CreateFile)
} }
} }

View File

@ -0,0 +1,44 @@
package files
import (
"github.com/spf13/afero"
"io"
"io/fs"
"os"
"path"
)
type FileOp struct {
Fs afero.Fs
}
func NewFileOp() FileOp {
return FileOp{
Fs: afero.NewOsFs(),
}
}
func (f FileOp) CreateDir(dst string, mode fs.FileMode) error {
return f.Fs.MkdirAll(dst, mode)
}
func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error {
dir, _ := path.Split(dst)
if err := f.Fs.MkdirAll(dir, mode); err != nil {
return err
}
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}
defer file.Close()
if _, err = io.Copy(file, in); err != nil {
return err
}
if _, err = file.Stat(); err != nil {
return err
}
return nil
}

View File

@ -1,6 +1,4 @@
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
// import { showFullScreenLoading, tryHideFullScreenLoading } from '@/config/service-loading';
import { AxiosCanceler } from './helper/axios-cancel';
import { ResultData } from '@/api/interface'; import { ResultData } from '@/api/interface';
import { ResultEnum } from '@/enums/http-enum'; import { ResultEnum } from '@/enums/http-enum';
import { checkStatus } from './helper/check-status'; import { checkStatus } from './helper/check-status';
@ -9,7 +7,6 @@ import router from '@/routers';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const axiosCanceler = new AxiosCanceler();
const config = { const config = {
baseURL: import.meta.env.VITE_API_URL as string, baseURL: import.meta.env.VITE_API_URL as string,
@ -30,8 +27,7 @@ class RequestHttp {
...config.headers, ...config.headers,
}; };
} }
axiosCanceler.addPending(config);
// config.headers!.noLoading || showFullScreenLoading();
return { return {
...config, ...config,
}; };
@ -43,12 +39,10 @@ class RequestHttp {
this.service.interceptors.response.use( this.service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
const { data, config } = response; const { data } = response;
if (response.headers['x-csrf-token']) { if (response.headers['x-csrf-token']) {
globalStore.setCsrfToken(response.headers['x-csrf-token']); globalStore.setCsrfToken(response.headers['x-csrf-token']);
} }
axiosCanceler.removePending(config);
// tryHideFullScreenLoading();
if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) { if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) {
ElMessage.error(data.msg); ElMessage.error(data.msg);
router.replace({ router.replace({
@ -64,7 +58,6 @@ class RequestHttp {
}, },
async (error: AxiosError) => { async (error: AxiosError) => {
const { response } = error; const { response } = error;
// tryHideFullScreenLoading();
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时请您稍后重试'); if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时请您稍后重试');
if (response) checkStatus(response.status); if (response) checkStatus(response.status);
if (!window.navigator.onLine) router.replace({ path: '/500' }); if (!window.navigator.onLine) router.replace({ path: '/500' });

View File

@ -29,4 +29,10 @@ export namespace File {
path: string; path: string;
children?: FileTree[]; children?: FileTree[];
} }
export interface FileCreate {
path: string;
isDir: Boolean;
mode: number;
}
} }

View File

@ -8,3 +8,7 @@ export const GetFilesList = (params: File.ReqFile) => {
export const GetFilesTree = (params: File.ReqFile) => { export const GetFilesTree = (params: File.ReqFile) => {
return http.post<File.FileTree[]>('files/tree', params); return http.post<File.FileTree[]>('files/tree', params);
}; };
export const CreateFile = (form: File.FileCreate) => {
return http.post<File.File>('files', form);
};

View File

@ -176,5 +176,6 @@ export default {
zip: '压缩', zip: '压缩',
user: '用户', user: '用户',
group: '组', group: '组',
path: '路径',
}, },
}; };

View File

@ -0,0 +1,70 @@
<template>
<el-dialog
v-model="open"
:before-close="handleClose"
:title="$t('commons.button.create')"
width="30%"
@open="onOpen"
v-loading="loading"
>
<el-form ref="fileForm" label-position="left" :model="form">
<el-form-item :label="$t('file.path')"> <el-input v-model="form.path" /></el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(fileForm)">{{ $t('commons.button.confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, toRefs, ref } from 'vue';
import { File } from '@/api/interface/file';
import { ElMessage, FormInstance } from 'element-plus';
import { CreateFile } from '@/api/modules/files';
import i18n from '@/lang';
const fileForm = ref<FormInstance>();
let loading = ref<Boolean>(false);
const props = defineProps({
open: Boolean,
file: Object,
});
const { open, file } = toRefs(props);
let form = ref<File.FileCreate>({ path: '', isDir: false, mode: 0o755 });
const em = defineEmits(['close']);
const handleClose = () => {
em('close', open);
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
CreateFile(form.value)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.createSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
});
};
const onOpen = () => {
const f = file?.value as File.FileCreate;
form.value.isDir = f.isDir;
form.value.path = f.path;
};
// function PrefixInteger(num: number, length: number) {
// return (Array(length).join('0') + num).slice(-length);
// }
</script>

View File

@ -43,17 +43,17 @@
v-loading="loading" v-loading="loading"
> >
<template #toolbar> <template #toolbar>
<el-dropdown split-button type="primary"> <el-dropdown split-button type="primary" @command="handleCreate">
{{ $t('commons.button.create') }} {{ $t('commons.button.create') }}
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item> <el-dropdown-item command="dir">
<svg-icon iconName="p-file-folder"></svg-icon>{{ $t('file.dir') }} <svg-icon iconName="p-file-folder"></svg-icon>{{ $t('file.dir') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item> <el-dropdown-item command="file">
<svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.file') }} <svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.file') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item> <el-dropdown-item command="link">
<svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.linkFile') }} <svg-icon iconName="p-file-normal"></svg-icon>{{ $t('file.linkFile') }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
@ -94,6 +94,7 @@
/> />
</ComplexTable> </ComplexTable>
</el-col> </el-col>
<CreateFile :open="openCreate" :file="fileCreate" @close="close"></CreateFile>
</el-row> </el-row>
</LayoutContent> </LayoutContent>
</template> </template>
@ -108,6 +109,7 @@ import { dateFromat } from '@/utils/util';
import { File } from '@/api/interface/file'; import { File } from '@/api/interface/file';
import BreadCrumbs from '@/components/bread-crumbs/index.vue'; import BreadCrumbs from '@/components/bread-crumbs/index.vue';
import BreadCrumbItem from '@/components/bread-crumbs/bread-crumbs-item.vue'; import BreadCrumbItem from '@/components/bread-crumbs/bread-crumbs-item.vue';
import CreateFile from './create.vue';
let data = ref(); let data = ref();
let selects = ref<any>([]); let selects = ref<any>([]);
@ -117,6 +119,8 @@ let treeLoading = ref<boolean>(false);
let paths = ref<string[]>([]); let paths = ref<string[]>([]);
let fileTree = ref<File.FileTree[]>([]); let fileTree = ref<File.FileTree[]>([]);
let expandKeys = ref<string[]>([]); let expandKeys = ref<string[]>([]);
let openCreate = ref<boolean>(false);
let fileCreate = ref<File.FileCreate>({ path: '/', isDir: false, mode: 0o755 });
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
@ -205,6 +209,23 @@ const loadNode = (node: any, resolve: (data: File.FileTree[]) => void) => {
} }
resolve([]); resolve([]);
}; };
const handleCreate = (commnad: string) => {
fileCreate.value.path = req.path;
fileCreate.value.isDir = false;
if (commnad === 'dir') {
fileCreate.value.isDir = true;
console.log(fileCreate.value);
}
console.log(commnad);
openCreate.value = true;
};
const close = () => {
openCreate.value = false;
search(req);
};
const buttons = [ const buttons = [
{ {
label: i18n.global.t('file.open'), label: i18n.global.t('file.open'),