mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-20 08:00:48 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e95b95f50 |
@@ -0,0 +1,84 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary List quick directories
|
||||
// @Success 200 {array} model.QuickDirectory
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [get]
|
||||
func (b *BaseApi) ListQuickDirectories(c *gin.Context) {
|
||||
items, err := dashboardService.ListQuickDirectories()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, items)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Create quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [post]
|
||||
func (b *BaseApi) CreateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.CreateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Update quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/update [post]
|
||||
func (b *BaseApi) UpdateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.UpdateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Delete quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/del [post]
|
||||
func (b *BaseApi) DeleteQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := dashboardService.DeleteQuickDirectory(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -31,7 +31,27 @@ type DashboardBase struct {
|
||||
}
|
||||
|
||||
type ChangeQuicks struct {
|
||||
Quicks []QuickJump `json:"quicks"`
|
||||
Quicks []QuickVisibility `json:"quicks" validate:"required,dive"`
|
||||
}
|
||||
|
||||
type QuickVisibility struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
IsShow bool `json:"isShow"`
|
||||
}
|
||||
|
||||
type QuickDirectoryCreate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
type QuickDirectoryDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Legacy bool `json:"legacy"`
|
||||
}
|
||||
|
||||
type QuickDirectoryUpdate struct {
|
||||
QuickDirectoryDelete
|
||||
QuickDirectoryCreate
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
|
||||
@@ -5,6 +5,13 @@ type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type QuickDirectory struct {
|
||||
BaseModel
|
||||
Path string `json:"path"`
|
||||
Alias string `json:"alias"`
|
||||
Legacy bool `json:"legacy" gorm:"-"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -3,6 +3,7 @@ package repo
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LauncherRepo struct{}
|
||||
@@ -17,6 +18,11 @@ type ILauncherRepo interface {
|
||||
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
|
||||
ListQuickJump(withAll bool) []model.QuickJump
|
||||
UpdateQuicks(quicks []model.QuickJump) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CountQuickDirectories() (int64, error)
|
||||
CreateQuickDirectory(item *model.QuickDirectory) error
|
||||
UpdateQuickDirectory(item *model.QuickDirectory) error
|
||||
DeleteQuickDirectory(id uint, legacy bool) error
|
||||
}
|
||||
|
||||
func NewILauncherRepo() ILauncherRepo {
|
||||
@@ -95,13 +101,73 @@ func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
|
||||
for _, item := range quicks {
|
||||
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||
"is_show": item.IsShow,
|
||||
"detail": item.Detail,
|
||||
"alias": item.Alias,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// Legacy entries remain in the overview configuration until explicitly deleted.
|
||||
func legacyQuickDirectories(db *gorm.DB) *gorm.DB {
|
||||
return db.Model(&model.QuickJump{}).Where("name = ? AND is_show = ? AND detail <> ?", "File", true, "")
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
items := make([]model.QuickDirectory, 0)
|
||||
if err := global.DB.Order("id ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var quicks []model.QuickJump
|
||||
if err := legacyQuickDirectories(global.DB).Order("id ASC").Find(&quicks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, quick := range quicks {
|
||||
items = append(items, model.QuickDirectory{BaseModel: quick.BaseModel, Path: quick.Detail, Alias: quick.Alias, Legacy: true})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
func (u *LauncherRepo) CountQuickDirectories() (int64, error) {
|
||||
var count, legacyCount int64
|
||||
if err := global.DB.Model(&model.QuickDirectory{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := legacyQuickDirectories(global.DB).Count(&legacyCount).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count + legacyCount, nil
|
||||
}
|
||||
func (u *LauncherRepo) CreateQuickDirectory(item *model.QuickDirectory) error {
|
||||
return global.DB.Create(item).Error
|
||||
}
|
||||
func (u *LauncherRepo) UpdateQuickDirectory(item *model.QuickDirectory) error {
|
||||
var result *gorm.DB
|
||||
if item.Legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", item.ID).Updates(map[string]interface{}{"detail": item.Path, "alias": item.Alias})
|
||||
} else {
|
||||
result = global.DB.Model(&model.QuickDirectory{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"path": item.Path, "alias": item.Alias})
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *LauncherRepo) DeleteQuickDirectory(id uint, legacy bool) error {
|
||||
var result *gorm.DB
|
||||
if legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", id).Updates(map[string]interface{}{"detail": nil, "alias": nil})
|
||||
} else {
|
||||
result = global.DB.Delete(&model.QuickDirectory{}, id)
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ type IDashboardService interface {
|
||||
|
||||
LoadQuickOptions() []dto.QuickJump
|
||||
ChangeQuick(req dto.ChangeQuicks) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error)
|
||||
UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error)
|
||||
DeleteQuickDirectory(req dto.QuickDirectoryDelete) error
|
||||
|
||||
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
|
||||
ChangeShow(req dto.SettingUpdate) error
|
||||
@@ -397,16 +401,15 @@ func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
|
||||
}
|
||||
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
|
||||
showCount := 0
|
||||
var quicks []model.QuickJump
|
||||
quicks := make([]model.QuickJump, 0, len(req.Quicks))
|
||||
for _, item := range req.Quicks {
|
||||
var quick model.QuickJump
|
||||
if item.IsShow {
|
||||
showCount++
|
||||
}
|
||||
if err := copier.Copy(&quick, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
quicks = append(quicks, quick)
|
||||
quicks = append(quicks, model.QuickJump{
|
||||
BaseModel: model.BaseModel{ID: item.ID},
|
||||
IsShow: item.IsShow,
|
||||
})
|
||||
}
|
||||
if showCount == 0 {
|
||||
return buserr.New("ErrMinQuickJump")
|
||||
@@ -706,6 +709,13 @@ func loadQuickJump(base *dto.DashboardBase) {
|
||||
quicks := launcherRepo.ListQuickJump(false)
|
||||
for i := 0; i < len(quicks); i++ {
|
||||
switch quicks[i].Name {
|
||||
case "File":
|
||||
count, err := launcherRepo.CountQuickDirectories()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load quick directory count: %v", err)
|
||||
}
|
||||
quicks[i].Detail = fmt.Sprintf("%d", count)
|
||||
quicks[i].Alias = ""
|
||||
case "Agent":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.AgentNumber)
|
||||
case "Website":
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
)
|
||||
|
||||
func (u *DashboardService) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
return launcherRepo.ListQuickDirectories()
|
||||
}
|
||||
|
||||
func quickDirectoryValues(req dto.QuickDirectoryCreate) (model.QuickDirectory, error) {
|
||||
item := model.QuickDirectory{Path: req.Path, Alias: strings.TrimSpace(req.Alias)}
|
||||
if strings.TrimSpace(item.Path) == "" {
|
||||
return item, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := launcherRepo.CreateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req.QuickDirectoryCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.ID = req.ID
|
||||
item.Legacy = req.Legacy
|
||||
if err := launcherRepo.UpdateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) DeleteQuickDirectory(req dto.QuickDirectoryDelete) error {
|
||||
return launcherRepo.DeleteQuickDirectory(req.ID, req.Legacy)
|
||||
}
|
||||
@@ -115,6 +115,7 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddMonitorSettings,
|
||||
migrations.AddVLLMRequestMetrics,
|
||||
migrations.AddVLLMSampleInterval,
|
||||
migrations.AddQuickDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1888,3 +1888,10 @@ var AddVLLMSampleInterval = &gormigrate.Migration{
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddQuickDirectory = &gormigrate.Migration{
|
||||
ID: "20261016-add-quick-directory",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.QuickDirectory{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/quick/option", baseApi.LoadQuickOption)
|
||||
cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump)
|
||||
cmdRouter.GET("/quick/directory", baseApi.ListQuickDirectories)
|
||||
cmdRouter.POST("/quick/directory", baseApi.CreateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/update", baseApi.UpdateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/del", baseApi.DeleteQuickDirectory)
|
||||
cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption)
|
||||
|
||||
@@ -8,6 +8,12 @@ export namespace Dashboard {
|
||||
|
||||
diskSize: number;
|
||||
}
|
||||
export interface QuickDirectory {
|
||||
id: number;
|
||||
legacy?: boolean;
|
||||
path: string;
|
||||
alias: string;
|
||||
}
|
||||
export interface QuickJump {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ export const loadOsInfo = () => {
|
||||
export const loadQuickOption = () => {
|
||||
return http.get<Array<Dashboard.QuickJump>>(`/dashboard/quick/option`);
|
||||
};
|
||||
export const changeQuick = (quicks: Array<Dashboard.QuickJump>) => {
|
||||
export const changeQuick = (quicks: Array<Pick<Dashboard.QuickJump, 'id' | 'isShow'>>) => {
|
||||
return http.post(`/dashboard/quick/change`, { quicks: quicks });
|
||||
};
|
||||
export const loadAppLauncher = () => {
|
||||
@@ -48,3 +48,11 @@ export const loadTopMem = () => {
|
||||
export const systemRestart = (operation: string) => {
|
||||
return http.post(`/dashboard/system/restart/${operation}`);
|
||||
};
|
||||
|
||||
export const loadQuickDirectories = () => http.get<Dashboard.QuickDirectory[]>('/dashboard/quick/directory');
|
||||
export const createQuickDirectory = (data: Omit<Dashboard.QuickDirectory, 'id'>) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory', data);
|
||||
export const updateQuickDirectory = (data: Dashboard.QuickDirectory) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory/update', data);
|
||||
export const deleteQuickDirectory = (id: number, legacy = false) =>
|
||||
http.post('/dashboard/quick/directory/del', { id, legacy });
|
||||
|
||||
@@ -196,7 +196,7 @@ const favorites = ref<File.Favorite[]>([]);
|
||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchableForSelect(paths);
|
||||
const oldUrl = ref<string>('');
|
||||
|
||||
const em = defineEmits(['choose']);
|
||||
const em = defineEmits(['choose', 'close']);
|
||||
|
||||
const form = reactive({
|
||||
path: '/',
|
||||
@@ -245,6 +245,7 @@ const handleClose = () => {
|
||||
open.value = false;
|
||||
selectRow.value = { path: '', name: '' };
|
||||
selectRows.value = [];
|
||||
em('close');
|
||||
};
|
||||
|
||||
const openPage = () => {
|
||||
|
||||
@@ -494,6 +494,7 @@ const message = {
|
||||
recommend: 'Recommended',
|
||||
dir: 'Directory',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Delete ({0})?',
|
||||
quickDir: 'Quick Access',
|
||||
minQuickJump: 'Add at least one quick access entry',
|
||||
maxQuickJump: 'You can add up to four quick access entries',
|
||||
|
||||
@@ -493,6 +493,7 @@ const message = {
|
||||
home: {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Directorio',
|
||||
quickDirDelete: '¿Confirmar la eliminación de ({0})?',
|
||||
quickDir: 'Acceso rápido',
|
||||
minQuickJump: 'Agrega al menos una entrada de acceso rápido',
|
||||
maxQuickJump: 'Puedes agregar hasta cuatro entradas de acceso rápido',
|
||||
|
||||
@@ -489,6 +489,7 @@ const message = {
|
||||
recommend: 'پیشنهادی',
|
||||
dir: 'دایرکتوری',
|
||||
alias: 'نام مستعار',
|
||||
quickDirDelete: 'حذف ({0}) را تأیید میکنید؟',
|
||||
quickDir: 'دسترسی سریع',
|
||||
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
|
||||
maxQuickJump: 'میتوانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
|
||||
|
||||
@@ -492,6 +492,7 @@ const message = {
|
||||
recommend: 'おすすめ',
|
||||
dir: 'ディレクトリ',
|
||||
alias: 'エイリアス',
|
||||
quickDirDelete: '({0})を削除しますか?',
|
||||
quickDir: 'クイックアクセス',
|
||||
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
|
||||
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
|
||||
|
||||
@@ -487,6 +487,7 @@ const message = {
|
||||
recommend: '추천',
|
||||
dir: '디렉토리',
|
||||
alias: '별칭',
|
||||
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
|
||||
quickDir: '빠른 액세스',
|
||||
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
|
||||
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
|
||||
|
||||
@@ -487,6 +487,7 @@ const message = {
|
||||
recommend: 'ແນະນຳ',
|
||||
dir: 'ໄດເຣັກທໍຣີ',
|
||||
alias: 'ຊື່ຫຼິ້ນ',
|
||||
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
|
||||
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
|
||||
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
|
||||
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
|
||||
|
||||
@@ -499,6 +499,7 @@ const message = {
|
||||
recommend: 'Disyorkan',
|
||||
dir: 'Direktori',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Sahkan pemadaman ({0})?',
|
||||
quickDir: 'Akses Pantas',
|
||||
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
|
||||
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
|
||||
|
||||
@@ -492,6 +492,7 @@ const message = {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Diretório',
|
||||
alias: 'Apelido',
|
||||
quickDirDelete: 'Confirmar a exclusão de ({0})?',
|
||||
quickDir: 'Acesso rápido',
|
||||
minQuickJump: 'Adicione pelo menos uma entrada de acesso rápido',
|
||||
maxQuickJump: 'Você pode adicionar até quatro entradas de acesso rápido',
|
||||
|
||||
@@ -491,6 +491,7 @@ const message = {
|
||||
recommend: 'Рекомендуемое',
|
||||
dir: 'Каталог',
|
||||
alias: 'Псевдоним',
|
||||
quickDirDelete: 'Подтвердить удаление ({0})?',
|
||||
quickDir: 'Быстрый доступ',
|
||||
minQuickJump: 'Добавьте хотя бы одну запись быстрого доступа',
|
||||
maxQuickJump: 'Вы можете добавить до четырех записей быстрого доступа',
|
||||
|
||||
@@ -495,6 +495,7 @@ const message = {
|
||||
recommend: 'Önerilen',
|
||||
dir: 'Dizin',
|
||||
alias: 'Takma Ad',
|
||||
quickDirDelete: '({0}) silinsin mi?',
|
||||
quickDir: 'Hızlı Erişim',
|
||||
minQuickJump: 'En az bir hızlı erişim girdisi ekleyin',
|
||||
maxQuickJump: 'En fazla dört hızlı erişim girdisi ekleyebilirsiniz',
|
||||
|
||||
@@ -473,6 +473,7 @@ const message = {
|
||||
recommend: '推薦',
|
||||
dir: '目錄',
|
||||
alias: '別名',
|
||||
quickDirDelete: '確定刪除({0})',
|
||||
quickDir: '快捷目錄',
|
||||
minQuickJump: '請至少設定一個快速跳轉入口',
|
||||
maxQuickJump: '最多可設定四個快速跳轉入口',
|
||||
|
||||
@@ -473,6 +473,7 @@ const message = {
|
||||
recommend: '推荐',
|
||||
dir: '目录',
|
||||
alias: '别名',
|
||||
quickDirDelete: '确定删除({0})',
|
||||
quickDir: '快捷目录',
|
||||
minQuickJump: '请至少设置一个快速跳转入口!',
|
||||
maxQuickJump: '最多可设置四个快速跳转入口!',
|
||||
|
||||
@@ -50,20 +50,12 @@
|
||||
<el-col :span="6" v-for="item in baseInfo.quickJump" :key="item.name">
|
||||
<span>{{ $t(item.title, 2) }}</span>
|
||||
<div class="count">
|
||||
<el-tooltip
|
||||
v-if="item.alias || item.detail.length > 20"
|
||||
:content="item.detail"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission('File')"
|
||||
type="primary"
|
||||
@click="quickJump(item)"
|
||||
>
|
||||
{{ item.alias || item.detail.substring(0, 18) + '...' }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<QuickDirectories
|
||||
v-if="item.name === 'File'"
|
||||
:count="item.detail"
|
||||
:disabled="!checkPermission('File')"
|
||||
@change="updateQuickDirectoryCount"
|
||||
/>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission(item.name)"
|
||||
@@ -450,6 +442,7 @@ import SystemStatus from '@/views/home/status/index.vue';
|
||||
import AppLauncher from '@/views/home/app/index.vue';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
import QuickJump from '@/views/home/quick/index.vue';
|
||||
import QuickDirectories from '@/views/home/quick/directories.vue';
|
||||
import CardWithHeader from '@/components/card-with-header/index.vue';
|
||||
import MarkDownEditor from '@/components/mkdown-editor/index.vue';
|
||||
import i18n from '@/lang';
|
||||
@@ -470,7 +463,7 @@ import {
|
||||
updateMemo,
|
||||
updateSetting,
|
||||
} from '@/api/modules/setting';
|
||||
import { routerToFileWithPath, routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { getWelcomePage } from '@/api/modules/auth';
|
||||
import {
|
||||
clearDashboardCache,
|
||||
@@ -823,10 +816,12 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateQuickDirectoryCount = (count: number) => {
|
||||
const item = baseInfo.value.quickJump.find((item) => item.name === 'File');
|
||||
if (item) item.detail = String(count);
|
||||
};
|
||||
|
||||
const quickJump = (item: any) => {
|
||||
if (item.name === 'File') {
|
||||
return routerToFileWithPath(item.detail);
|
||||
}
|
||||
return routerToPath(item.router);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<el-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:width="680"
|
||||
:popper-style="{ maxWidth: 'calc(100vw - 32px)' }"
|
||||
:visible="visible"
|
||||
@update:visible="setVisible"
|
||||
:disabled="disabled"
|
||||
:title="$t('home.quickDir')"
|
||||
@show="load"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="primary" :disabled="disabled">{{ count }}</el-button>
|
||||
</template>
|
||||
<div v-loading="loading" class="quick-directories">
|
||||
<div class="directory-search">
|
||||
<el-input
|
||||
v-model="searchInput"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
:placeholder="`${$t('file.path')} / ${$t('home.alias')}`"
|
||||
:aria-label="$t('commons.button.search')"
|
||||
@keyup.enter="search"
|
||||
@clear="search"
|
||||
/>
|
||||
<el-button :disabled="loading" @click="search">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</div>
|
||||
<div class="directory-row directory-header">
|
||||
<span>{{ $t('file.path') }}</span>
|
||||
<span>{{ $t('home.alias') }}</span>
|
||||
<span />
|
||||
</div>
|
||||
<div class="directory-list">
|
||||
<div v-if="!filteredRows.length" class="directory-empty">{{ $t('commons.msg.noneData') }}</div>
|
||||
<div v-for="row in filteredRows" :key="row.key" class="directory-row">
|
||||
<el-input
|
||||
v-model="row.path"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('file.path')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button
|
||||
icon="Folder"
|
||||
:disabled="!row.editing || row.busy"
|
||||
:aria-label="$t('file.list')"
|
||||
@click="selectPath(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="row.alias"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('home.alias')"
|
||||
/>
|
||||
<div class="directory-actions">
|
||||
<el-button
|
||||
link
|
||||
:type="row.editing ? 'success' : 'primary'"
|
||||
:icon="row.editing ? 'Check' : 'Edit'"
|
||||
:aria-label="$t(row.editing ? 'commons.button.save' : 'commons.button.edit')"
|
||||
:disabled="loading || row.busy || (row.editing && !row.path.trim())"
|
||||
@click="row.editing ? save(row) : (row.editing = true)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id || rows.length > 1"
|
||||
link
|
||||
:type="row.editing ? 'info' : 'danger'"
|
||||
:icon="row.editing ? 'Close' : 'Delete'"
|
||||
:aria-label="$t('commons.button.delete')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="remove(row)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id"
|
||||
link
|
||||
type="primary"
|
||||
icon="Position"
|
||||
:title="$t('firewall.quickJump')"
|
||||
:aria-label="$t('firewall.quickJump')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="routerToFileWithPath(row.savedPath)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="directory-add"
|
||||
link
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
:disabled="loading || !loaded"
|
||||
@click="add"
|
||||
>
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="directory-footer">
|
||||
<el-button @click="close">{{ $t('commons.button.close') }}</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<FileList ref="fileRef" @choose="loadPath" @close="selectingRow = undefined" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { Dashboard } from '@/api/interface/dashboard';
|
||||
import {
|
||||
loadQuickDirectories,
|
||||
createQuickDirectory,
|
||||
updateQuickDirectory,
|
||||
deleteQuickDirectory,
|
||||
} from '@/api/modules/dashboard';
|
||||
|
||||
defineProps<{ count: string; disabled: boolean }>();
|
||||
const emit = defineEmits<{ (e: 'change', count: number): void }>();
|
||||
type DirectoryRow = Dashboard.QuickDirectory & {
|
||||
key: number;
|
||||
savedPath: string;
|
||||
editing: boolean;
|
||||
busy: boolean;
|
||||
};
|
||||
const rows = ref<DirectoryRow[]>([]);
|
||||
const searchInput = ref('');
|
||||
const searchTerm = ref('');
|
||||
const filteredRows = computed(() =>
|
||||
rows.value.filter(
|
||||
(row) =>
|
||||
!row.id ||
|
||||
row.editing ||
|
||||
row.path.toLowerCase().includes(searchTerm.value) ||
|
||||
(row.alias ?? '').toLowerCase().includes(searchTerm.value),
|
||||
),
|
||||
);
|
||||
const search = () => {
|
||||
searchTerm.value = searchInput.value.trim().toLowerCase();
|
||||
};
|
||||
const fileRef = ref<InstanceType<typeof FileList>>();
|
||||
const selectingRow = ref<DirectoryRow>();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
let nextKey = 0;
|
||||
|
||||
const setVisible = (value: boolean) => {
|
||||
if (value) visible.value = true;
|
||||
};
|
||||
const close = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const selectPath = (row: DirectoryRow) => {
|
||||
if (!row.editing || row.busy) return;
|
||||
selectingRow.value = row;
|
||||
fileRef.value?.acceptParams({ path: row.path, dir: false, isAll: true });
|
||||
};
|
||||
const loadPath = (path: string) => {
|
||||
const row = selectingRow.value;
|
||||
if (row?.editing && !row.busy) row.path = path;
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
rows.value.push({
|
||||
key: nextKey++,
|
||||
id: 0,
|
||||
path: '',
|
||||
alias: '',
|
||||
savedPath: '',
|
||||
editing: true,
|
||||
busy: false,
|
||||
});
|
||||
};
|
||||
const updateCount = () => emit('change', rows.value.filter((row) => row.id).length);
|
||||
const load = async () => {
|
||||
if (loading.value || rows.value.some((row) => row.busy)) return;
|
||||
loading.value = true;
|
||||
loaded.value = false;
|
||||
searchInput.value = '';
|
||||
searchTerm.value = '';
|
||||
rows.value = [];
|
||||
try {
|
||||
const { data } = await loadQuickDirectories();
|
||||
rows.value = data.map((item) => ({
|
||||
...item,
|
||||
alias: item.alias ?? '',
|
||||
key: nextKey++,
|
||||
savedPath: item.path,
|
||||
editing: false,
|
||||
busy: false,
|
||||
}));
|
||||
if (!rows.value.length) add();
|
||||
loaded.value = true;
|
||||
updateCount();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
const save = async (row: DirectoryRow) => {
|
||||
if (row.busy || !row.path.trim()) return;
|
||||
row.busy = true;
|
||||
try {
|
||||
const payload = { path: row.path, alias: (row.alias ?? '').trim() };
|
||||
const { data } = row.id
|
||||
? await updateQuickDirectory({ id: row.id, legacy: row.legacy ?? false, ...payload })
|
||||
: await createQuickDirectory(payload);
|
||||
Object.assign(row, data, { savedPath: data.path, editing: false });
|
||||
updateCount();
|
||||
} finally {
|
||||
row.busy = false;
|
||||
}
|
||||
};
|
||||
const remove = async (row: DirectoryRow) => {
|
||||
if (row.busy) return;
|
||||
if (row.id) {
|
||||
row.busy = true;
|
||||
try {
|
||||
await ElMessageBox.confirm('', i18n.global.t('home.quickDirDelete', [row.savedPath]), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
customClass: 'quick-directory-delete',
|
||||
type: 'warning',
|
||||
center: true,
|
||||
});
|
||||
} catch {
|
||||
row.busy = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteQuickDirectory(row.id, row.legacy ?? false);
|
||||
} catch (error) {
|
||||
row.busy = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
rows.value = rows.value.filter((item) => item.key !== row.key);
|
||||
if (!rows.value.length) add();
|
||||
updateCount();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.quick-directories {
|
||||
padding: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.directory-search {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
.directory-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.directory-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 96px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.directory-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
.directory-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.directory-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.directory-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.directory-add {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.quick-directory-delete {
|
||||
.el-message-box__title {
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #59472a;
|
||||
color: #f5cf87;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.el-message-box__status {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
color: #f2b329;
|
||||
}
|
||||
.el-message-box__content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,25 +10,6 @@
|
||||
<el-table-column prop="isShow" :label="$t('setting.ifShow')">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isShow" />
|
||||
<div v-if="row.name === 'File' && row.isShow">
|
||||
<el-input v-model="row.detail" class="w-full">
|
||||
<template #prepend>
|
||||
<el-button
|
||||
class="w-16"
|
||||
v-if="row.name === 'File' && row.isShow"
|
||||
icon="Folder"
|
||||
@click="fileRef.acceptParams({ path: row.detail, isAll: true })"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="row.alias" class="mt-1">
|
||||
<template #prepend>
|
||||
<el-button class="w-16">
|
||||
{{ $t('home.alias') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ComplexTable>
|
||||
@@ -39,14 +20,12 @@
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<FileList ref="fileRef" @choose="loadDir" />
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { changeQuick, loadQuickOption } from '@/api/modules/dashboard';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
@@ -57,7 +36,6 @@ const acceptParams = (): void => {
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const quickOptions = ref([]);
|
||||
const fileRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
@@ -86,26 +64,17 @@ const onChangeShow = async () => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
await changeQuick(quickOptions.value)
|
||||
await changeQuick(quickOptions.value.map(({ id, isShow }) => ({ id, isShow })))
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
drawerVisible.value = false;
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
for (const item of quickOptions.value) {
|
||||
if (item.name === 'File') {
|
||||
item.detail = path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
|
||||
Reference in New Issue
Block a user