diff --git a/internal/biz/backup.go b/internal/biz/backup.go index ac61a3278b..04d327cb0f 100644 --- a/internal/biz/backup.go +++ b/internal/biz/backup.go @@ -2,12 +2,23 @@ package biz import "github.com/TheTNB/panel/pkg/types" +type BackupType string + +const ( + BackupTypePath BackupType = "path" + BackupTypeWebsite BackupType = "website" + BackupTypeMySQL BackupType = "mysql" + BackupTypePostgres BackupType = "postgres" + BackupTypeRedis BackupType = "redis" + BackupTypePanel BackupType = "panel" +) + type BackupRepo interface { - List(typ string) ([]*types.BackupFile, error) - Create(typ, target string, path ...string) error - Delete(typ, name string) error - Restore(typ, backup, target string) error + List(typ BackupType) ([]*types.BackupFile, error) + Create(typ BackupType, target string, path ...string) error + Delete(typ BackupType, name string) error + Restore(typ BackupType, backup, target string) error ClearExpired(path, prefix string, save int) error CutoffLog(path, target string) error - GetPath(typ string) (string, error) + GetPath(typ BackupType) (string, error) } diff --git a/internal/data/backup.go b/internal/data/backup.go index 185f2a6bd7..ea6351ffbc 100644 --- a/internal/data/backup.go +++ b/internal/data/backup.go @@ -34,37 +34,38 @@ func NewBackupRepo() biz.BackupRepo { } // List 备份列表 -func (r *backupRepo) List(typ string) ([]*types.BackupFile, error) { - backupPath, err := r.GetPath(typ) +func (r *backupRepo) List(typ biz.BackupType) ([]*types.BackupFile, error) { + path, err := r.GetPath(typ) if err != nil { return nil, err } - files, err := io.ReadDir(backupPath) + files, err := io.ReadDir(path) if err != nil { return nil, err } - var backupList []*types.BackupFile + list := make([]*types.BackupFile, 0) for _, file := range files { info, err := file.Info() if err != nil { continue } - backupList = append(backupList, &types.BackupFile{ + list = append(list, &types.BackupFile{ Name: file.Name(), + Path: filepath.Join(path, file.Name()), Size: str.FormatBytes(float64(info.Size())), }) } - return backupList, nil + return list, nil } // Create 创建备份 // typ 备份类型 // target 目标名称 // path 可选备份保存路径 -func (r *backupRepo) Create(typ, target string, path ...string) error { +func (r *backupRepo) Create(typ biz.BackupType, target string, path ...string) error { defPath, err := r.GetPath(typ) if err != nil { return err @@ -74,13 +75,13 @@ func (r *backupRepo) Create(typ, target string, path ...string) error { } switch typ { - case "website": + case biz.BackupTypeWebsite: return r.createWebsite(defPath, target) - case "mysql": + case biz.BackupTypeMySQL: return r.createMySQL(defPath, target) - case "postgres": + case biz.BackupTypePostgres: return r.createPostgres(defPath, target) - case "panel": + case biz.BackupTypePanel: return r.createPanel(defPath) } @@ -89,7 +90,7 @@ func (r *backupRepo) Create(typ, target string, path ...string) error { } // Delete 删除备份 -func (r *backupRepo) Delete(typ, name string) error { +func (r *backupRepo) Delete(typ biz.BackupType, name string) error { path, err := r.GetPath(typ) if err != nil { return err @@ -103,7 +104,7 @@ func (r *backupRepo) Delete(typ, name string) error { // typ 备份类型 // backup 备份压缩包,可以是绝对路径或者相对路径 // target 目标名称 -func (r *backupRepo) Restore(typ, backup, target string) error { +func (r *backupRepo) Restore(typ biz.BackupType, backup, target string) error { if !io.Exists(backup) { path, err := r.GetPath(typ) if err != nil { @@ -113,11 +114,11 @@ func (r *backupRepo) Restore(typ, backup, target string) error { } switch typ { - case "website": + case biz.BackupTypeWebsite: return r.restoreWebsite(backup, target) - case "mysql": + case biz.BackupTypeMySQL: return r.restoreMySQL(backup, target) - case "postgres": + case biz.BackupTypePostgres: return r.restorePostgres(backup, target) } @@ -189,13 +190,16 @@ func (r *backupRepo) ClearExpired(path, prefix string, save int) error { } // GetPath 获取备份路径 -func (r *backupRepo) GetPath(typ string) (string, error) { +func (r *backupRepo) GetPath(typ biz.BackupType) (string, error) { backupPath, err := r.setting.Get(biz.SettingKeyBackupPath) if err != nil { return "", err } + if !slices.Contains([]biz.BackupType{biz.BackupTypePath, biz.BackupTypeWebsite, biz.BackupTypeMySQL, biz.BackupTypePostgres, biz.BackupTypeRedis, biz.BackupTypePanel}, typ) { + return "", errors.New("未知备份类型") + } - backupPath = filepath.Join(backupPath, typ) + backupPath = filepath.Join(backupPath, string(typ)) if !io.Exists(backupPath) { if err = io.Mkdir(backupPath, 0644); err != nil { return "", err @@ -216,11 +220,22 @@ func (r *backupRepo) createWebsite(to string, name string) error { return err } + var paths []string + dirs, err := io.ReadDir(website.Path) + if err != nil { + return err + } + for _, item := range dirs { + paths = append(paths, filepath.Join(website.Path, item.Name())) + } + + start := time.Now() backup := filepath.Join(to, fmt.Sprintf("%s_%s.zip", website.Name, time.Now().Format("20060102150405"))) - if _, err = shell.Execf(`cd '%s' && zip -r '%s' .`, website.Path, backup); err != nil { + if err = io.Compress(paths, backup, io.Zip); err != nil { return err } + color.Greenln(fmt.Sprintf("|-备份耗时:%s", time.Since(start).String())) color.Greenln(fmt.Sprintf("|-已备份至文件:%s", backup)) return nil } @@ -249,6 +264,7 @@ func (r *backupRepo) createMySQL(to string, name string) error { if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil { return err } + start := time.Now() backup := filepath.Join(to, fmt.Sprintf("%s_%s.sql", name, time.Now().Format("20060102150405"))) if _, err = shell.Execf(`mysqldump -u root '%s' > '%s'`, name, backup); err != nil { return err @@ -264,6 +280,7 @@ func (r *backupRepo) createMySQL(to string, name string) error { return err } + color.Greenln(fmt.Sprintf("|-备份耗时:%s", time.Since(start).String())) color.Greenln(fmt.Sprintf("|-已备份至文件:%s", backup+".zip")) return nil } @@ -285,6 +302,7 @@ func (r *backupRepo) createPostgres(to string, name string) error { return err } + start := time.Now() backup := filepath.Join(to, fmt.Sprintf("%s_%s.sql", name, time.Now().Format("20060102150405"))) if _, err = shell.Execf(`su - postgres -c "pg_dump '%s'" > '%s'`, name, backup); err != nil { return err @@ -297,6 +315,7 @@ func (r *backupRepo) createPostgres(to string, name string) error { return err } + color.Greenln(fmt.Sprintf("|-备份耗时:%s", time.Since(start).String())) color.Greenln(fmt.Sprintf("|-已备份至文件:%s", backup+".zip")) return nil } @@ -309,6 +328,7 @@ func (r *backupRepo) createPanel(to string) error { return err } + start := time.Now() if err := io.Compress([]string{ filepath.Join(app.Root, "panel"), "/usr/local/sbin/panel-cli", @@ -317,6 +337,7 @@ func (r *backupRepo) createPanel(to string) error { return err } + color.Greenln(fmt.Sprintf("|-备份耗时:%s", time.Since(start).String())) color.Greenln(fmt.Sprintf("|-已备份至文件:%s", backup)) return nil } diff --git a/internal/http/request/backup.go b/internal/http/request/backup.go new file mode 100644 index 0000000000..38b3188dbd --- /dev/null +++ b/internal/http/request/backup.go @@ -0,0 +1,22 @@ +package request + +type BackupList struct { + Type string `json:"type" form:"type" validate:"required,oneof=path website mysql postgres redis panel"` +} + +type BackupCreate struct { + Type string `json:"type" form:"type" validate:"required,oneof=website mysql postgres redis panel"` + Target string `json:"target" form:"target" validate:"required"` + Path string `json:"path" form:"path"` +} + +type BackupFile struct { + Type string `json:"type" form:"type" validate:"required,oneof=website mysql postgres redis panel"` + File string `json:"file" form:"file" validate:"required"` +} + +type BackupRestore struct { + Type string `json:"type" form:"type" validate:"required,oneof=website mysql postgres redis panel"` + File string `json:"file" form:"file" validate:"required"` + Target string `json:"target" form:"target" validate:"required"` +} diff --git a/internal/http/request/file.go b/internal/http/request/file.go index 141dd06232..ed403306ab 100644 --- a/internal/http/request/file.go +++ b/internal/http/request/file.go @@ -14,11 +14,6 @@ type FileSave struct { Content string `form:"content" json:"content"` } -type FileUpload struct { - Path string `json:"path" form:"path"` - File []byte `json:"file" form:"file"` -} - type FileMove struct { Source string `form:"source" json:"source"` Target string `form:"target" json:"target"` diff --git a/internal/job/panel_task.go b/internal/job/panel_task.go index 29805f5fe9..38eacd92b0 100644 --- a/internal/job/panel_task.go +++ b/internal/job/panel_task.go @@ -35,7 +35,7 @@ func (receiver *PanelTask) Run() { } // 备份面板 - if err := receiver.backupRepo.Create("panel", ""); err != nil { + if err := receiver.backupRepo.Create(biz.BackupTypePanel, ""); err != nil { app.Logger.Error("备份面板失败", zap.Error(err)) } diff --git a/internal/route/http.go b/internal/route/http.go index 8ccabf91f3..bf5c37e7e3 100644 --- a/internal/route/http.go +++ b/internal/route/http.go @@ -62,16 +62,14 @@ func Http(r chi.Router) { r.Post("/{id}/status", website.UpdateStatus) }) - // TODO r.Route("/backup", func(r chi.Router) { r.Use(middleware.MustLogin) backup := service.NewBackupService() - r.Get("/backup", backup.List) - r.Post("/create", backup.Create) - r.Post("/update", backup.Update) - r.Get("/{id}", backup.Get) - r.Delete("/{id}", backup.Delete) - r.Delete("/{id}/restore", backup.Restore) + r.Get("/{type}", backup.List) + r.Post("/{type}", backup.Create) + r.Post("/{type}/upload", backup.Upload) + r.Delete("/{type}/delete", backup.Delete) + r.Post("/{type}/restore", backup.Restore) }) r.Route("/cert", func(r chi.Router) { diff --git a/internal/service/backup.go b/internal/service/backup.go index 431570d6db..1492577a77 100644 --- a/internal/service/backup.go +++ b/internal/service/backup.go @@ -1,10 +1,17 @@ package service import ( + stdio "io" "net/http" + "os" + "path/filepath" + + "github.com/go-rat/chix" "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/internal/data" + "github.com/TheTNB/panel/internal/http/request" + "github.com/TheTNB/panel/pkg/io" ) type BackupService struct { @@ -18,25 +25,98 @@ func NewBackupService() *BackupService { } func (s *BackupService) List(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.BackupList](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + list, _ := s.backupRepo.List(biz.BackupType(req.Type)) + paged, total := Paginate(r, list) + Success(w, chix.M{ + "total": total, + "items": paged, + }) } func (s *BackupService) Create(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.BackupCreate](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + if err = s.backupRepo.Create(biz.BackupType(req.Type), req.Target, req.Path); err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + Success(w, nil) } -func (s *BackupService) Update(w http.ResponseWriter, r *http.Request) { +func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(2 << 30); err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } -} + _, handler, err := r.FormFile("file") + path, err := s.backupRepo.GetPath(biz.BackupType(r.FormValue("type"))) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + if !io.Exists(filepath.Dir(path)) { + if err = io.Mkdir(filepath.Dir(path), 0755); err != nil { + Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err) + return + } + } -func (s *BackupService) Get(w http.ResponseWriter, r *http.Request) { + src, _ := handler.Open() + out, err := os.OpenFile(filepath.Join(path, handler.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) + if err != nil { + Error(w, http.StatusInternalServerError, "打开文件失败:%v", err) + return + } + if _, err = stdio.Copy(out, src); err != nil { + Error(w, http.StatusInternalServerError, "写入文件失败:%v", err) + return + } + + _ = src.Close() + Success(w, nil) } func (s *BackupService) Delete(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.BackupFile](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + if err = s.backupRepo.Delete(biz.BackupType(req.Type), req.File); err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, nil) } func (s *BackupService) Restore(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.BackupRestore](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + if err = s.backupRepo.Restore(biz.BackupType(req.Type), req.File, req.Target); err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + Success(w, nil) } diff --git a/internal/service/cli.go b/internal/service/cli.go index d218acb68e..6dc4373af7 100644 --- a/internal/service/cli.go +++ b/internal/service/cli.go @@ -421,7 +421,7 @@ func (s *CliService) BackupWebsite(ctx context.Context, cmd *cli.Command) error color.Greenln(s.hr) color.Greenln("|-备份类型:网站") color.Greenln(fmt.Sprintf("|-备份目标:%s", cmd.String("name"))) - if err := s.backupRepo.Create("website", cmd.String("name"), cmd.String("path")); err != nil { + if err := s.backupRepo.Create(biz.BackupTypeWebsite, cmd.String("name"), cmd.String("path")); err != nil { return fmt.Errorf("|-备份失败:%v", err) } color.Greenln(s.hr) @@ -437,7 +437,7 @@ func (s *CliService) BackupDatabase(ctx context.Context, cmd *cli.Command) error color.Greenln("|-备份类型:数据库") color.Greenln(fmt.Sprintf("|-数据库:%s", cmd.String("type"))) color.Greenln(fmt.Sprintf("|-备份目标:%s", cmd.String("name"))) - if err := s.backupRepo.Create(cmd.String("type"), cmd.String("name"), cmd.String("path")); err != nil { + if err := s.backupRepo.Create(biz.BackupType(cmd.String("type")), cmd.String("name"), cmd.String("path")); err != nil { return fmt.Errorf("|-备份失败:%v", err) } color.Greenln(s.hr) @@ -451,7 +451,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error { color.Greenln(fmt.Sprintf("★ 开始备份 [%s]", time.Now().Format(time.DateTime))) color.Greenln(s.hr) color.Greenln("|-备份类型:面板") - if err := s.backupRepo.Create("panel", "", cmd.String("path")); err != nil { + if err := s.backupRepo.Create(biz.BackupTypePanel, "", cmd.String("path")); err != nil { return fmt.Errorf("|-备份失败:%v", err) } color.Greenln(s.hr) @@ -461,7 +461,7 @@ func (s *CliService) BackupPanel(ctx context.Context, cmd *cli.Command) error { } func (s *CliService) BackupClear(ctx context.Context, cmd *cli.Command) error { - path, err := s.backupRepo.GetPath(cmd.String("type")) + path, err := s.backupRepo.GetPath(biz.BackupType(cmd.String("type"))) if err != nil { return err } diff --git a/internal/service/file.go b/internal/service/file.go index e85dfae6c2..0057225803 100644 --- a/internal/service/file.go +++ b/internal/service/file.go @@ -4,6 +4,7 @@ package service import ( "fmt" + stdio "io" "net/http" stdos "os" "path/filepath" @@ -119,18 +120,43 @@ func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) { } func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) { - req, err := Bind[request.FileUpload](r) + if err := r.ParseMultipartForm(2 << 30); err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + path := r.FormValue("path") + _, handler, err := r.FormFile("file") if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return } + if io.Exists(path) { + Error(w, http.StatusForbidden, "目标路径%s已存在", path) + return + } - if err = io.Write(req.Path, string(req.File), 0755); err != nil { - Error(w, http.StatusInternalServerError, "%v", err) + if !io.Exists(filepath.Dir(path)) { + if err = io.Mkdir(filepath.Dir(path), 0755); err != nil { + Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err) + return + } + } + + src, _ := handler.Open() + out, err := stdos.OpenFile(path, stdos.O_CREATE|stdos.O_RDWR|stdos.O_TRUNC, 0644) + if err != nil { + Error(w, http.StatusInternalServerError, "打开文件失败:%v", err) return } - s.setPermission(req.Path, 0755, "www", "www") + if _, err = stdio.Copy(out, src); err != nil { + Error(w, http.StatusInternalServerError, "写入文件失败:%v", err) + return + } + + _ = src.Close() + s.setPermission(path, 0755, "www", "www") Success(w, nil) } diff --git a/internal/service/file_windows.go b/internal/service/file_windows.go index ea8b6aee7c..97b5c794fb 100644 --- a/internal/service/file_windows.go +++ b/internal/service/file_windows.go @@ -6,6 +6,7 @@ package service import ( "fmt" + stdio "io" "net/http" stdos "os" "path/filepath" @@ -119,18 +120,43 @@ func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) { } func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) { - req, err := Bind[request.FileUpload](r) + if err := r.ParseMultipartForm(2 << 30); err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + path := r.FormValue("path") + _, handler, err := r.FormFile("file") if err != nil { Error(w, http.StatusInternalServerError, "%v", err) return } + if io.Exists(path) { + Error(w, http.StatusForbidden, "目标路径%s已存在", path) + return + } - if err = io.Write(req.Path, string(req.File), 0755); err != nil { - Error(w, http.StatusInternalServerError, "%v", err) + if !io.Exists(filepath.Dir(path)) { + if err = io.Mkdir(filepath.Dir(path), 0755); err != nil { + Error(w, http.StatusInternalServerError, "创建文件夹失败:%v", err) + return + } + } + + src, _ := handler.Open() + out, err := stdos.OpenFile(path, stdos.O_CREATE|stdos.O_RDWR|stdos.O_TRUNC, 0644) + if err != nil { + Error(w, http.StatusInternalServerError, "打开文件失败:%v", err) return } - s.setPermission(req.Path, 0755, "www", "www") + if _, err = stdio.Copy(out, src); err != nil { + Error(w, http.StatusInternalServerError, "写入文件失败:%v", err) + return + } + + _ = src.Close() + s.setPermission(path, 0755, "www", "www") Success(w, nil) } diff --git a/pkg/types/backup.go b/pkg/types/backup.go index 151af976ff..5b94fe2485 100644 --- a/pkg/types/backup.go +++ b/pkg/types/backup.go @@ -2,5 +2,6 @@ package types type BackupFile struct { Name string `json:"name"` + Path string `json:"path"` Size string `json:"size"` } diff --git a/web/src/api/panel/backup/index.ts b/web/src/api/panel/backup/index.ts new file mode 100644 index 0000000000..a78bc3a326 --- /dev/null +++ b/web/src/api/panel/backup/index.ts @@ -0,0 +1,24 @@ +import type { AxiosResponse } from 'axios' + +import { request } from '@/utils' + +export default { + // 获取备份列表 + list: (type: string, page: number, limit: number): Promise> => + request.get(`/backup/${type}`, { params: { page, limit } }), + // 创建备份 + create: (type: string, target: string, path: string): Promise> => + request.post(`/backup/${type}`, { target, path }), + // 上传备份 + upload: (type: string, formData: FormData): Promise> => { + return request.post(`/backup/${type}/upload`, formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + // 删除备份 + delete: (type: string, file: string): Promise> => + request.delete(`/backup/${type}/delete`, { params: { file } }), + // 恢复备份 + restore: (type: string, file: string, target: string): Promise> => + request.post(`/backup/${type}/restore`, { file, target }) +} diff --git a/web/src/views/app/route.ts b/web/src/views/app/route.ts index caf6225934..58edbc25d0 100644 --- a/web/src/views/app/route.ts +++ b/web/src/views/app/route.ts @@ -7,7 +7,7 @@ export default { path: '/app', component: Layout, meta: { - order: 8 + order: 90 }, children: [ { @@ -16,7 +16,7 @@ export default { component: () => import('./IndexView.vue'), meta: { title: 'appIndex.title', - icon: 'mdi:puzzle-outline', + icon: 'mdi:apps', role: ['admin'], requireAuth: true } diff --git a/web/src/views/apps/pureftpd/IndexView.vue b/web/src/views/apps/pureftpd/IndexView.vue index bb07b6699b..8a7decdb11 100644 --- a/web/src/views/apps/pureftpd/IndexView.vue +++ b/web/src/views/apps/pureftpd/IndexView.vue @@ -300,8 +300,8 @@ onMounted(() => { - - + + +import backup from '@/api/panel/backup' +import ListView from '@/views/backup/ListView.vue' +import { NButton, NInput } from 'naive-ui' + +const currentTab = ref('website') +const createModal = ref(false) +const createModel = ref({ + target: '', + path: '' +}) +const oldTab = ref('') + +const handleCreate = () => { + backup.create(currentTab.value, createModel.value.target, createModel.value.path).then(() => { + createModal.value = false + window.$message.success('创建成功') + // 有点low,但是没找到更好的办法 + oldTab.value = currentTab.value + currentTab.value = '' + setTimeout(() => { + currentTab.value = oldTab.value + }, 0) + }) +} + + + diff --git a/web/src/views/backup/ListView.vue b/web/src/views/backup/ListView.vue new file mode 100644 index 0000000000..7fb6eb03c1 --- /dev/null +++ b/web/src/views/backup/ListView.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/web/src/views/backup/route.ts b/web/src/views/backup/route.ts new file mode 100644 index 0000000000..c4947ff148 --- /dev/null +++ b/web/src/views/backup/route.ts @@ -0,0 +1,25 @@ +import type { RouteType } from '~/types/router' + +const Layout = () => import('@/layout/IndexView.vue') + +export default { + name: 'backup', + path: '/backup', + component: Layout, + meta: { + order: 60 + }, + children: [ + { + name: 'backup-index', + path: '', + component: () => import('./IndexView.vue'), + meta: { + title: '数据备份', + icon: 'mdi:backup-outline', + role: ['admin'], + requireAuth: true + } + } + ] +} as RouteType diff --git a/web/src/views/backup/types.ts b/web/src/views/backup/types.ts new file mode 100644 index 0000000000..cc9bfe4d6c --- /dev/null +++ b/web/src/views/backup/types.ts @@ -0,0 +1,5 @@ +export interface Backup { + name: string + path: string + size: string +} diff --git a/web/src/views/cert/route.ts b/web/src/views/cert/route.ts index 22af1eb370..7067c1b8f1 100644 --- a/web/src/views/cert/route.ts +++ b/web/src/views/cert/route.ts @@ -7,7 +7,7 @@ export default { path: '/cert', component: Layout, meta: { - order: 2 + order: 10 }, children: [ { diff --git a/web/src/views/container/IndexView.vue b/web/src/views/container/IndexView.vue index cc1f88128b..7d6b56fe7a 100644 --- a/web/src/views/container/IndexView.vue +++ b/web/src/views/container/IndexView.vue @@ -11,16 +11,16 @@ const currentTab = ref('container') - + - + - + - + diff --git a/web/src/views/container/route.ts b/web/src/views/container/route.ts index 97b79a2d37..db9186b4f7 100644 --- a/web/src/views/container/route.ts +++ b/web/src/views/container/route.ts @@ -7,7 +7,7 @@ export default { path: '/container', component: Layout, meta: { - order: 5 + order: 40 }, children: [ { diff --git a/web/src/views/cron/IndexView.vue b/web/src/views/cron/IndexView.vue index 062d30bf87..272e3394ff 100644 --- a/web/src/views/cron/IndexView.vue +++ b/web/src/views/cron/IndexView.vue @@ -329,7 +329,7 @@ onMounted(() => { 网站目录 MySQL 数据库 - + PostgreSQL 数据库 diff --git a/web/src/views/cron/route.ts b/web/src/views/cron/route.ts index 8462492fa9..621954eb6c 100644 --- a/web/src/views/cron/route.ts +++ b/web/src/views/cron/route.ts @@ -7,7 +7,7 @@ export default { path: '/cron', component: Layout, meta: { - order: 5 + order: 70 }, children: [ { diff --git a/web/src/views/file/ListTable.vue b/web/src/views/file/ListTable.vue index e5c8da040e..21e8c00ef6 100644 --- a/web/src/views/file/ListTable.vue +++ b/web/src/views/file/ListTable.vue @@ -127,7 +127,7 @@ const columns: DataTableColumns = [ selected.value = [row.full] compress.value = true } else { - window.open('/api/panel/file/download?path=' + encodeURIComponent(row.full)) + window.open('/api/file/download?path=' + encodeURIComponent(row.full)) } } }, diff --git a/web/src/views/file/route.ts b/web/src/views/file/route.ts index 087571cea5..4d01f62518 100644 --- a/web/src/views/file/route.ts +++ b/web/src/views/file/route.ts @@ -7,7 +7,7 @@ export default { path: '/file', component: Layout, meta: { - order: 6 + order: 50 }, children: [ { diff --git a/web/src/views/monitor/route.ts b/web/src/views/monitor/route.ts index 6c2bc18a88..019815007e 100644 --- a/web/src/views/monitor/route.ts +++ b/web/src/views/monitor/route.ts @@ -7,7 +7,7 @@ export default { path: '/monitor', component: Layout, meta: { - order: 3 + order: 20 }, children: [ { diff --git a/web/src/views/safe/route.ts b/web/src/views/safe/route.ts index e002d75372..d430c7c9bc 100644 --- a/web/src/views/safe/route.ts +++ b/web/src/views/safe/route.ts @@ -7,7 +7,7 @@ export default { path: '/safe', component: Layout, meta: { - order: 4 + order: 30 }, children: [ { diff --git a/web/src/views/setting/route.ts b/web/src/views/setting/route.ts index 8cba41c497..4dbba7f5e4 100644 --- a/web/src/views/setting/route.ts +++ b/web/src/views/setting/route.ts @@ -7,7 +7,7 @@ export default { path: '/setting', component: Layout, meta: { - order: 10 + order: 999 }, children: [ { diff --git a/web/src/views/ssh/route.ts b/web/src/views/ssh/route.ts index deabfed8b6..978a7f7063 100644 --- a/web/src/views/ssh/route.ts +++ b/web/src/views/ssh/route.ts @@ -7,7 +7,7 @@ export default { path: '/ssh', component: Layout, meta: { - order: 7 + order: 80 }, children: [ { diff --git a/web/src/views/task/route.ts b/web/src/views/task/route.ts index 369d16d048..089c26e64a 100644 --- a/web/src/views/task/route.ts +++ b/web/src/views/task/route.ts @@ -7,7 +7,7 @@ export default { path: '/task', component: Layout, meta: { - order: 9 + order: 100 }, children: [ {