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:
ssongliu 2022-09-19 19:42:06 +08:00 committed by ssongliu
parent f99a2ae656
commit 99edb9c7ad
32 changed files with 513 additions and 110 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
/pkg/ /pkg/
backend/__debug_bin

Binary file not shown.

View File

@ -98,7 +98,7 @@ func (b *BaseApi) SafeEntrance(c *gin.Context) {
} }
codeWithMD5 := encrypt.Md5(code) codeWithMD5 := encrypt.Md5(code)
cookieValue, _ := encrypt.StringEncrypt(codeWithMD5) cookieValue, _ := encrypt.StringEncrypt(codeWithMD5)
c.SetCookie(codeWithMD5, cookieValue, 86400, "", "", false, false) c.SetCookie(codeWithMD5, cookieValue, 604800, "", "", false, false)
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }

View File

@ -88,25 +88,12 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
func (b *BaseApi) PageBackup(c *gin.Context) { func (b *BaseApi) ListBackup(c *gin.Context) {
var req dto.PageInfo data, err := backupService.List()
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := backupService.Page(req)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
helper.SuccessWithData(c, dto.PageResult{ helper.SuccessWithData(c, data)
Items: list,
Total: total,
})
} }

View File

@ -3,7 +3,6 @@ package dto
import "time" import "time"
type BackupOperate struct { type BackupOperate struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`
Bucket string `json:"bucket"` Bucket string `json:"bucket"`
Credential string `json:"credential"` Credential string `json:"credential"`
@ -13,7 +12,6 @@ type BackupOperate struct {
type BackupInfo struct { type BackupInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Bucket string `json:"bucket"` Bucket string `json:"bucket"`
Vars string `json:"vars"` Vars string `json:"vars"`

View File

@ -2,10 +2,8 @@ package model
type BackupAccount struct { type BackupAccount struct {
BaseModel BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"` Type string `gorm:"type:varchar(64);unique;not null" json:"type"`
Type string `gorm:"type:varchar(64)" json:"type"`
Bucket string `gorm:"type:varchar(256)" json:"bucket"` Bucket string `gorm:"type:varchar(256)" json:"bucket"`
Credential string `gorm:"type:varchar(256)" json:"credential"` Credential string `gorm:"type:varchar(256)" json:"credential"`
Vars string `gorm:"type:longText" json:"vars"` Vars string `gorm:"type:longText" json:"vars"`
Status string `gorm:"type:varchar(64)" json:"status"`
} }

View File

@ -8,7 +8,8 @@ import (
type BackupRepo struct{} type BackupRepo struct{}
type IBackupRepo interface { type IBackupRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.BackupAccount, error) Get(opts ...DBOption) (model.BackupAccount, error)
List(opts ...DBOption) ([]model.BackupAccount, error)
Create(backup *model.BackupAccount) error Create(backup *model.BackupAccount) error
Update(id uint, vars map[string]interface{}) error Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error Delete(opts ...DBOption) error
@ -28,7 +29,7 @@ func (u *BackupRepo) Get(opts ...DBOption) (model.BackupAccount, error) {
return backup, err return backup, err
} }
func (u *BackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.BackupAccount, error) { func (u *BackupRepo) List(opts ...DBOption) ([]model.BackupAccount, error) {
var ops []model.BackupAccount var ops []model.BackupAccount
db := global.DB.Model(&model.BackupAccount{}) db := global.DB.Model(&model.BackupAccount{})
for _, opt := range opts { for _, opt := range opts {
@ -36,8 +37,8 @@ func (u *BackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.Back
} }
count := int64(0) count := int64(0)
db = db.Count(&count) db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error err := db.Find(&ops).Error
return count, ops, err return ops, err
} }
func (u *BackupRepo) Create(backup *model.BackupAccount) error { func (u *BackupRepo) Create(backup *model.BackupAccount) error {

View File

@ -26,6 +26,12 @@ func (c *CommonRepo) WithByName(name string) DBOption {
} }
} }
func (c *CommonRepo) WithByType(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("type = ?", name)
}
}
func (c *CommonRepo) WithLikeName(name string) DBOption { func (c *CommonRepo) WithLikeName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
return g.Where("name like ?", "%"+name+"%") return g.Where("name like ?", "%"+name+"%")

View File

@ -102,7 +102,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
sessionUser, err := global.SESSION.Get(sID) sessionUser, err := global.SESSION.Get(sID)
if err != nil { if err != nil {
sID = uuid.NewV4().String() sID = uuid.NewV4().String()
c.SetCookie(constant.SessionName, sID, lifeTime, "", "", false, false) c.SetCookie(constant.SessionName, sID, 604800, "", "", false, false)
err := global.SESSION.Set(sID, sessionUser, lifeTime) err := global.SESSION.Set(sID, sessionUser, lifeTime)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -13,7 +13,7 @@ import (
type BackupService struct{} type BackupService struct{}
type IBackupService interface { type IBackupService interface {
Page(search dto.PageInfo) (int64, interface{}, error) List() ([]dto.BackupInfo, error)
Create(backupDto dto.BackupOperate) error Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(id uint, upMap map[string]interface{}) error Update(id uint, upMap map[string]interface{}) error
@ -24,21 +24,21 @@ func NewIBackupService() IBackupService {
return &BackupService{} return &BackupService{}
} }
func (u *BackupService) Page(search dto.PageInfo) (int64, interface{}, error) { func (u *BackupService) List() ([]dto.BackupInfo, error) {
total, ops, err := backupRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc")) ops, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc"))
var dtobas []dto.BackupInfo var dtobas []dto.BackupInfo
for _, group := range ops { for _, group := range ops {
var item dto.BackupInfo var item dto.BackupInfo
if err := copier.Copy(&item, &group); err != nil { if err := copier.Copy(&item, &group); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
} }
dtobas = append(dtobas, item) dtobas = append(dtobas, item)
} }
return total, dtobas, err return dtobas, err
} }
func (u *BackupService) Create(backupDto dto.BackupOperate) error { func (u *BackupService) Create(backupDto dto.BackupOperate) error {
backup, _ := backupRepo.Get(commonRepo.WithByName(backupDto.Name)) backup, _ := backupRepo.Get(commonRepo.WithByType(backupDto.Type))
if backup.ID != 0 { if backup.ID != 0 {
return constant.ErrRecordExist return constant.ErrRecordExist
} }

View File

@ -50,17 +50,6 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
if err := settingRepo.Update(key, value); err != nil { if err := settingRepo.Update(key, value); err != nil {
return err return err
} }
switch key {
case "UserName":
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
err := global.SESSION.Delete(sID)
if err != nil {
return err
}
}
}
return settingRepo.Update(key, value) return settingRepo.Update(key, value)
} }

View File

@ -81,7 +81,7 @@ var AddTableSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "ServerPort", Value: "4004"}).Error; err != nil { if err := tx.Create(&model.Setting{Key: "ServerPort", Value: "4004"}).Error; err != nil {
return err return err
} }
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "89dc6ae8"}).Error; err != nil { if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: "onepanel"}).Error; err != nil {
return err return err
} }
if err := tx.Create(&model.Setting{Key: "PasswordTimeOut", Value: time.Now().AddDate(0, 0, 10).Format("2016.01.02 15:04:05")}).Error; err != nil { if err := tx.Create(&model.Setting{Key: "PasswordTimeOut", Value: time.Now().AddDate(0, 0, 10).Format("2016.01.02 15:04:05")}).Error; err != nil {
@ -127,10 +127,8 @@ var AddTableBackupAccount = &gormigrate.Migration{
return err return err
} }
item := &model.BackupAccount{ item := &model.BackupAccount{
Name: "Default Local", Type: "LOCAL",
Type: "LOCAL", Vars: "{\"dir\":\"/opt/1Panel/backup\"}",
Status: "VALID",
Vars: "{\"dir\":\"/opt/1Panel/backup\"}",
} }
if err := tx.Create(item).Error; err != nil { if err := tx.Create(item).Error; err != nil {
return err return err

View File

@ -1,7 +1,10 @@
package middleware package middleware
import ( import (
"strconv"
"github.com/1Panel-dev/1Panel/app/api/v1/helper" "github.com/1Panel-dev/1Panel/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/app/repo"
"github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -17,10 +20,18 @@ func SessionAuth() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil) helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil)
return return
} }
if _, err := global.SESSION.Get(sId); err != nil { psession, err := global.SESSION.Get(sId)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil) helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeNotLogin, nil)
return return
} }
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(settingRepo.WithByKey("SessionTimeout"))
if err != nil {
global.LOG.Errorf("create operation record failed, err: %v", err)
}
lifeTime, _ := strconv.Atoi(setting.Value)
_ = global.SESSION.Set(sId, psession, lifeTime)
c.Next() c.Next()
} }
} }

View File

@ -14,7 +14,7 @@ func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) {
withRecordRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord()) withRecordRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord())
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
baRouter.POST("/search", baseApi.PageBackup) baRouter.GET("/search", baseApi.ListBackup)
baRouter.POST("/buckets", baseApi.ListBuckets) baRouter.POST("/buckets", baseApi.ListBuckets)
withRecordRouter.POST("", baseApi.CreateBackup) withRecordRouter.POST("", baseApi.CreateBackup)
withRecordRouter.POST("/del", baseApi.DeleteBackup) withRecordRouter.POST("/del", baseApi.DeleteBackup)

View File

@ -1,7 +1,6 @@
export namespace Backup { export namespace Backup {
export interface BackupInfo { export interface BackupInfo {
id: number; id: number;
name: string;
type: string; type: string;
bucket: string; bucket: string;
vars: string; vars: string;
@ -9,7 +8,6 @@ export namespace Backup {
} }
export interface BackupOperate { export interface BackupOperate {
id: number; id: number;
name: string;
type: string; type: string;
bucket: string; bucket: string;
credential: string; credential: string;

View File

@ -1,9 +1,8 @@
import http from '@/api'; import http from '@/api';
import { Backup } from '../interface/backup'; import { Backup } from '../interface/backup';
import { ResPage, ReqPage } from '../interface';
export const getBackupList = (params: ReqPage) => { export const getBackupList = () => {
return http.post<ResPage<Backup.BackupInfo>>(`/backups/search`, params); return http.get<Array<Backup.BackupInfo>>(`/backups/search`);
}; };
export const addBackup = (params: Backup.BackupOperate) => { export const addBackup = (params: Backup.BackupOperate) => {

View File

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: "panel"; /* Project id 3575356 */ font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1662692062751') format('woff2'), src: url('iconfont.woff2?t=1663584463212') format('woff2'),
url('iconfont.woff?t=1662692062751') format('woff'), url('iconfont.woff?t=1663584463212') format('woff'),
url('iconfont.ttf?t=1662692062751') format('truetype'), url('iconfont.ttf?t=1663584463212') format('truetype'),
url('iconfont.svg?t=1662692062751#panel') format('svg'); url('iconfont.svg?t=1663584463212#panel') format('svg');
} }
.panel { .panel {
@ -14,6 +14,38 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.p-taolun:before {
content: "\e602";
}
.p-StarStar:before {
content: "\e635";
}
.p-bug:before {
content: "\e616";
}
.p-SFTP:before {
content: "\e647";
}
.p-huaban88:before {
content: "\e67c";
}
.p-oss:before {
content: "\e607";
}
.p-s3:before {
content: "\e8e4";
}
.p-minio:before {
content: "\e63c";
}
.p-logout:before { .p-logout:before {
content: "\e8fe"; content: "\e8fe";
} }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,247 @@
{
"id": "3575356",
"name": "panel",
"font_family": "panel",
"css_prefix_text": "p-",
"description": "",
"glyphs": [
{
"icon_id": "1760690",
"name": "讨论",
"font_class": "taolun",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "5192988",
"name": "Star Star",
"font_class": "StarStar",
"unicode": "e635",
"unicode_decimal": 58933
},
{
"icon_id": "6642940",
"name": "bug",
"font_class": "bug",
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "13532955",
"name": "SFTP",
"font_class": "SFTP",
"unicode": "e647",
"unicode_decimal": 58951
},
{
"icon_id": "15337722",
"name": "Logo GitHub",
"font_class": "huaban88",
"unicode": "e67c",
"unicode_decimal": 59004
},
{
"icon_id": "16268521",
"name": "oss",
"font_class": "oss",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "17895439",
"name": "Amazon S3上传",
"font_class": "s3",
"unicode": "e8e4",
"unicode_decimal": 59620
},
{
"icon_id": "20290513",
"name": "minio",
"font_class": "minio",
"unicode": "e63c",
"unicode_decimal": 58940
},
{
"icon_id": "924436",
"name": "logout",
"font_class": "logout",
"unicode": "e8fe",
"unicode_decimal": 59646
},
{
"icon_id": "837256",
"name": "terminal",
"font_class": "terminal2",
"unicode": "e82a",
"unicode_decimal": 59434
},
{
"icon_id": "8358944",
"name": "英文5",
"font_class": "yingwen",
"unicode": "e6c3",
"unicode_decimal": 59075
},
{
"icon_id": "8358949",
"name": "中文5",
"font_class": "zhongwen",
"unicode": "e6c8",
"unicode_decimal": 59080
},
{
"icon_id": "11487994",
"name": "calendar",
"font_class": "plan",
"unicode": "e746",
"unicode_decimal": 59206
},
{
"icon_id": "11488064",
"name": "integral",
"font_class": "database",
"unicode": "e754",
"unicode_decimal": 59220
},
{
"icon_id": "11488108",
"name": "rejected-order",
"font_class": "rejected-order",
"unicode": "e75e",
"unicode_decimal": 59230
},
{
"icon_id": "11488148",
"name": "tool",
"font_class": "toolbox",
"unicode": "e769",
"unicode_decimal": 59241
},
{
"icon_id": "4765743",
"name": "earth",
"font_class": "website",
"unicode": "e781",
"unicode_decimal": 59265
},
{
"icon_id": "4765891",
"name": "setting",
"font_class": "config",
"unicode": "e78e",
"unicode_decimal": 59278
},
{
"icon_id": "4765962",
"name": "app store",
"font_class": "appstore1",
"unicode": "e792",
"unicode_decimal": 59282
},
{
"icon_id": "4765971",
"name": "detail",
"font_class": "log",
"unicode": "e793",
"unicode_decimal": 59283
},
{
"icon_id": "4766440",
"name": "sever",
"font_class": "host",
"unicode": "e7b1",
"unicode_decimal": 59313
},
{
"icon_id": "4766685",
"name": "home",
"font_class": "home",
"unicode": "e7c6",
"unicode_decimal": 59334
},
{
"icon_id": "19688849",
"name": "应用商店",
"font_class": "appstore",
"unicode": "eb65",
"unicode_decimal": 60261
},
{
"icon_id": "3876424",
"name": "docker",
"font_class": "docker",
"unicode": "e659",
"unicode_decimal": 58969
},
{
"icon_id": "15838431",
"name": "arrow-right",
"font_class": "arrow-right",
"unicode": "e665",
"unicode_decimal": 58981
},
{
"icon_id": "6172786",
"name": "terminal",
"font_class": "terminal",
"unicode": "e864",
"unicode_decimal": 59492
},
{
"icon_id": "14772948",
"name": "terminal",
"font_class": "terminal1",
"unicode": "e663",
"unicode_decimal": 58979
},
{
"icon_id": "7533292",
"name": "中英文",
"font_class": "language",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "22551111",
"name": "主题",
"font_class": "theme",
"unicode": "e638",
"unicode_decimal": 58936
},
{
"icon_id": "22735864",
"name": "文件类型-文件夹",
"font_class": "file-folder",
"unicode": "66",
"unicode_decimal": 102
},
{
"icon_id": "22761833",
"name": "文件类型-未知文件",
"font_class": "file-unknown",
"unicode": "233",
"unicode_decimal": 563
},
{
"icon_id": "22761837",
"name": "文件类型-Txt",
"font_class": "file-txt",
"unicode": "74",
"unicode_decimal": 116
},
{
"icon_id": "19671156",
"name": "txt-1",
"font_class": "file-normal",
"unicode": "e7ac",
"unicode_decimal": 59308
},
{
"icon_id": "22761832",
"name": "文件类型-压缩包",
"font_class": "file-zip",
"unicode": "e606",
"unicode_decimal": 58886
}
]
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -262,6 +262,7 @@ export default {
downloading: 'Downloading...', downloading: 'Downloading...',
}, },
setting: { setting: {
all: 'All',
panel: 'Panel', panel: 'Panel',
emailHelper: 'For password retrieval', emailHelper: 'For password retrieval',
title: 'Panel alias', title: 'Panel alias',
@ -282,6 +283,8 @@ export default {
backup: 'Backup', backup: 'Backup',
serverDisk: 'Server disks', serverDisk: 'Server disks',
OSS: 'Ali OSS',
S3: 'Amazon S3',
backupAccount: 'Backup account', backupAccount: 'Backup account',
loadBucket: 'Get bucket', loadBucket: 'Get bucket',
accountName: 'Account name', accountName: 'Account name',
@ -298,11 +301,11 @@ export default {
'The recommended port range is 8888 to 65535. Note: If the server has a security group, permit the new port from the security group in advance', 'The recommended port range is 8888 to 65535. Note: If the server has a security group, permit the new port from the security group in advance',
safeEntrance: 'Security entrance', safeEntrance: 'Security entrance',
safeEntranceHelper: safeEntranceHelper:
'Panel management portal. You can log in to the panel only through a specified security portal, for example, / 89DC6AE8', 'Panel management portal. You can log in to the panel only through a specified security portal, for example: onepanel',
passwordTimeout: 'Password expiration Time', passwordTimeout: 'Expiration Time',
timeoutHelper: timeoutHelper:
'[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password', '[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password',
complexity: 'Password complexity verification', complexity: 'Complexity verification',
complexityHelper: complexityHelper:
'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters', 'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters',
mfa: 'MFA', mfa: 'MFA',
@ -324,5 +327,12 @@ export default {
emailAddr: 'Service address', emailAddr: 'Service address',
emailSMTP: 'SMTP code', emailSMTP: 'SMTP code',
secret: 'Secret', secret: 'Secret',
about: 'About',
project: 'Project Address',
issue: 'Feedback',
chat: 'Community Discussion',
star: 'Star',
description: 'A modern Linux panel tool',
}, },
}; };

View File

@ -259,6 +259,7 @@ export default {
downloading: '正在下载...', downloading: '正在下载...',
}, },
setting: { setting: {
all: '全部',
panel: '面板', panel: '面板',
emailHelper: '用于密码找回', emailHelper: '用于密码找回',
title: '面板别名', title: '面板别名',
@ -278,6 +279,8 @@ export default {
backup: '备份', backup: '备份',
serverDisk: '服务器磁盘', serverDisk: '服务器磁盘',
OSS: '阿里云 OSS',
S3: '亚马逊 S3 云存储',
backupAccount: '备份账号', backupAccount: '备份账号',
loadBucket: '获取桶', loadBucket: '获取桶',
accountName: '账户名称', accountName: '账户名称',
@ -292,7 +295,7 @@ export default {
panelPort: '面板端口', panelPort: '面板端口',
portHelper: '建议端口范围8888 - 65535注意有安全组的服务器请提前在安全组放行新端口', portHelper: '建议端口范围8888 - 65535注意有安全组的服务器请提前在安全组放行新端口',
safeEntrance: '安全入口', safeEntrance: '安全入口',
safeEntranceHelper: '面板管理入口设置后只能通过指定安全入口登录面板,: 89dc6ae8', safeEntranceHelper: '面板管理入口设置后只能通过指定安全入口登录面板: onepanel',
passwordTimeout: '密码过期时间', passwordTimeout: '密码过期时间',
timeoutHelper: ' {0} 天后 面板密码即将过期过期后需要重新设置密码', timeoutHelper: ' {0} 天后 面板密码即将过期过期后需要重新设置密码',
complexity: '密码复杂度验证', complexity: '密码复杂度验证',
@ -318,5 +321,10 @@ export default {
secret: '密钥', secret: '密钥',
about: '关于', about: '关于',
project: '项目地址',
issue: '问题反馈',
chat: '参与讨论',
star: '点亮 Star',
description: '一个现代化的 Linux 面板工具',
}, },
}; };

View File

@ -410,23 +410,23 @@ onBeforeMount(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.terminal-tabs { .terminal-tabs {
:deep(.el-tabs__header) { :deep .el-tabs__header {
padding: 0; padding: 0;
position: relative; position: relative;
margin: 0 0 3px 0; margin: 0 0 3px 0;
} }
:deep(.el-tabs__nav) { ::deep .el-tabs__nav {
white-space: nowrap; white-space: nowrap;
position: relative; position: relative;
transition: transform var(--el-transition-duration); transition: transform var(--el-transition-duration);
float: left; float: left;
z-index: calc(var(--el-index-normal) + 1); z-index: calc(var(--el-index-normal) + 1);
} }
:deep(.el-tabs__item) { :deep .el-tabs__item {
color: #575758; color: #575758;
padding: 0 0px; padding: 0 0px;
} }
:deep(.el-tabs__item.is-active) { :deep .el-tabs__item.is-active {
color: #ebeef5; color: #ebeef5;
background-color: #575758; background-color: #575758;
} }
@ -441,7 +441,7 @@ onBeforeMount(() => {
.fullScreen { .fullScreen {
position: absolute; position: absolute;
right: 50px; right: 50px;
top: 6px; top: 86px;
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }

View File

@ -2,18 +2,27 @@
<div> <div>
<el-card class="topCard"> <el-card class="topCard">
<el-radio-group v-model="activeNames"> <el-radio-group v-model="activeNames">
<el-radio-button class="topButton" size="large" label="all">全部</el-radio-button> <el-radio-button class="topButton" size="large" label="all">{{ $t('setting.all') }}</el-radio-button>
<el-radio-button class="topButton" size="large" label="panel">面板</el-radio-button> <el-radio-button class="topButton" size="large" label="panel">
<el-radio-button class="topButton" size="large" label="safe">安全</el-radio-button> {{ $t('setting.panel') }}
<el-radio-button class="topButton" size="large" label="backup">备份</el-radio-button> </el-radio-button>
<el-radio-button class="topButton" size="large" label="monitor">监控</el-radio-button> <el-radio-button class="topButton" size="large" label="safe">{{ $t('setting.safe') }}</el-radio-button>
<el-radio-button class="topButton" size="large" label="about">关于</el-radio-button> <el-radio-button class="topButton" size="large" label="backup">
{{ $t('setting.backup') }}
</el-radio-button>
<el-radio-button class="topButton" size="large" label="monitor">
{{ $t('menu.monitor') }}
</el-radio-button>
<el-radio-button class="topButton" size="large" label="about">
{{ $t('setting.about') }}
</el-radio-button>
</el-radio-group> </el-radio-group>
</el-card> </el-card>
<Panel v-if="activeNames === 'all' || activeNames === 'panel'" :settingInfo="form" @on-save="SaveSetting" /> <Panel v-if="activeNames === 'all' || activeNames === 'panel'" :settingInfo="form" @on-save="SaveSetting" />
<Safe v-if="activeNames === 'all' || activeNames === 'safe'" :settingInfo="form" @on-save="SaveSetting" /> <Safe v-if="activeNames === 'all' || activeNames === 'safe'" :settingInfo="form" @on-save="SaveSetting" />
<Backup v-if="activeNames === 'all' || activeNames === 'backup'" :settingInfo="form" @on-save="SaveSetting" /> <Backup v-if="activeNames === 'all' || activeNames === 'backup'" :settingInfo="form" @on-save="SaveSetting" />
<Monitor v-if="activeNames === 'all' || activeNames === 'monitor'" :settingInfo="form" @on-save="SaveSetting" /> <Monitor v-if="activeNames === 'all' || activeNames === 'monitor'" :settingInfo="form" @on-save="SaveSetting" />
<About v-if="activeNames === 'all' || activeNames === 'about'" />
</div> </div>
</template> </template>
@ -25,6 +34,7 @@ import Panel from '@/views/setting/tabs/panel.vue';
import Safe from '@/views/setting/tabs/safe.vue'; import Safe from '@/views/setting/tabs/safe.vue';
import Backup from '@/views/setting/tabs/backup.vue'; import Backup from '@/views/setting/tabs/backup.vue';
import Monitor from '@/views/setting/tabs/monitor.vue'; import Monitor from '@/views/setting/tabs/monitor.vue';
import About from '@/views/setting/tabs/about.vue';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { useTheme } from '@/hooks/use-theme'; import { useTheme } from '@/hooks/use-theme';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';

View File

@ -0,0 +1,50 @@
<template>
<el-card style="margin-top: 20px">
<template #header>
<div class="card-header">
<span>{{ $t('setting.about') }}</span>
</div>
</template>
<div style="text-align: center">
<div style="justify-self: center">
<img style="width: 80px" src="@/assets/images/ko_image.png" />
</div>
<h1>1Panel</h1>
<h3>{{ $t('setting.description') }}</h3>
<h3>v0.0.1</h3>
<div style="margin-top: 10px">
<svg-icon style="font-size: 9px" iconName="p-huaban88"></svg-icon>
<el-link @click="toGithub">
<span>{{ $t('setting.project') }}</span>
</el-link>
<svg-icon style="font-size: 9px; margin-left: 15px" iconName="p-bug"></svg-icon>
<el-link @click="toIssue">
<span>{{ $t('setting.issue') }}</span>
</el-link>
<svg-icon style="font-size: 9px; margin-left: 15px" iconName="p-taolun"></svg-icon>
<el-link @click="toTalk">
<span>{{ $t('setting.chat') }}</span>
</el-link>
<svg-icon style="font-size: 9px; margin-left: 15px" iconName="p-StarStar"></svg-icon>
<el-link @click="toGithubStar">
<span>{{ $t('setting.star') }}</span>
</el-link>
</div>
</div>
</el-card>
</template>
<script lang="ts" setup>
const toGithub = () => {
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
};
const toIssue = () => {
window.open('https://github.com/1Panel-dev/1Panel/issues', '_blank');
};
const toTalk = () => {
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
};
const toGithubStar = () => {
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
};
</script>

View File

@ -13,8 +13,9 @@
<el-card class="el-card"> <el-card class="el-card">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<svg-icon style="font-size: 7px" :iconName="loadIconName(item.type)"></svg-icon>
<span style="font-size: 16px; font-weight: 500"> <span style="font-size: 16px; font-weight: 500">
[{{ item.type === 'LOCAL' ? $t('setting.serverDisk') : item.type }}] {{ item.name }} {{ loadBackupName(item.type) }}
</span> </span>
<div style="float: right"> <div style="float: right">
<el-button @click="onEdit(item)">{{ $t('commons.button.edit') }}</el-button> <el-button @click="onEdit(item)">{{ $t('commons.button.edit') }}</el-button>
@ -25,7 +26,7 @@
</div> </div>
</template> </template>
<el-form label-position="left" label-width="130px"> <el-form label-position="left" label-width="130px">
<el-form-item v-if="item.type === 'LOCAL'" label="Dir"> <el-form-item v-if="item.type === 'LOCAL'" label="Directory">
{{ item.varsJson['dir'] }} {{ item.varsJson['dir'] }}
</el-form-item> </el-form-item>
<el-form-item v-if="hasBucket(item.type)" label="Access Key ID"> <el-form-item v-if="hasBucket(item.type)" label="Access Key ID">
@ -59,9 +60,6 @@
<el-dialog @close="search" v-model="backupVisiable" :title="$t('setting.backupAccount')" width="30%"> <el-dialog @close="search" v-model="backupVisiable" :title="$t('setting.backupAccount')" width="30%">
<el-form ref="formRef" label-position="left" :model="form" label-width="160px"> <el-form ref="formRef" label-position="left" :model="form" label-width="160px">
<el-form-item :label="$t('commons.table.name')" prop="name" :rules="Rules.name">
<el-input v-model="form.name" :disabled="operation === 'edit'" />
</el-form-item>
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect"> <el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-select style="width: 100%" v-model="form.type" :disabled="operation === 'edit'"> <el-select style="width: 100%" v-model="form.type" :disabled="operation === 'edit'">
<el-option <el-option
@ -74,7 +72,7 @@
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="form.type === 'LOCAL'" v-if="form.type === 'LOCAL'"
label="Dir" label="Directory"
prop="varsJson['dir']" prop="varsJson['dir']"
:rules="Rules.requiredInput" :rules="Rules.requiredInput"
> >
@ -94,7 +92,7 @@
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if="hasBucket(form.type)" v-if="hasBucket(form.type)"
label="Secret Access Key" label="Access Key Secret"
prop="credential" prop="credential"
:rules="Rules.requiredInput" :rules="Rules.requiredInput"
> >
@ -182,19 +180,8 @@ const selects = ref<any>([]);
const backupVisiable = ref<boolean>(false); const backupVisiable = ref<boolean>(false);
const operation = ref<string>('create'); const operation = ref<string>('create');
const paginationConfig = reactive({
currentPage: 1,
pageSize: 5,
total: 0,
});
const backSearch = reactive({
page: 1,
pageSize: 5,
});
const form = reactive({ const form = reactive({
id: 0, id: 0,
name: '',
type: 'LOCAL', type: 'LOCAL',
bucket: '', bucket: '',
credential: '', credential: '',
@ -203,31 +190,22 @@ const form = reactive({
}); });
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const typeOptions = ref([ const typeOptions = ref();
{ label: i18n.global.t('setting.serverDisk'), value: 'LOCAL' },
{ label: 'OSS', value: 'OSS' },
{ label: 'S3', value: 'S3' },
{ label: 'SFTP', value: 'SFTP' },
{ label: 'MINIO', value: 'MINIO' },
]);
const buckets = ref(); const buckets = ref();
const search = async () => { const search = async () => {
backSearch.page = paginationConfig.currentPage; const res = await getBackupList();
backSearch.pageSize = paginationConfig.pageSize; data.value = res.data;
const res = await getBackupList(backSearch);
data.value = res.data.items;
for (const bac of data.value) { for (const bac of data.value) {
bac.varsJson = JSON.parse(bac.vars); bac.varsJson = JSON.parse(bac.vars);
} }
paginationConfig.total = res.data.total;
}; };
const onCreate = () => { const onCreate = () => {
loadOption();
operation.value = 'create'; operation.value = 'create';
form.id = 0; form.id = 0;
form.name = ''; form.type = typeOptions.value[0].value;
form.type = 'LOCAL';
form.bucket = ''; form.bucket = '';
form.credential = ''; form.credential = '';
form.vars = ''; form.vars = '';
@ -250,9 +228,15 @@ const onBatchDelete = async (row: Backup.BackupInfo | null) => {
}; };
const onEdit = (row: Backup.BackupInfo) => { const onEdit = (row: Backup.BackupInfo) => {
typeOptions.value = [
{ label: i18n.global.t('setting.serverDisk'), value: 'LOCAL' },
{ label: i18n.global.t('setting.OSS'), value: 'OSS' },
{ label: i18n.global.t('setting.S3'), value: 'S3' },
{ label: 'SFTP', value: 'SFTP' },
{ label: 'MinIO', value: 'MINIO' },
];
restForm(); restForm();
form.id = row.id; form.id = row.id;
form.name = row.name;
form.type = row.type; form.type = row.type;
form.bucket = row.bucket; form.bucket = row.bucket;
form.varsJson = JSON.parse(row.vars); form.varsJson = JSON.parse(row.vars);
@ -296,6 +280,57 @@ const loadDir = async (path: string) => {
console.log(path); console.log(path);
form.varsJson['dir'] = path; form.varsJson['dir'] = path;
}; };
const loadOption = () => {
let options = [
{ label: i18n.global.t('setting.serverDisk'), value: 'LOCAL' },
{ label: i18n.global.t('setting.OSS'), value: 'OSS' },
{ label: i18n.global.t('setting.S3'), value: 'S3' },
{ label: 'SFTP', value: 'SFTP' },
{ label: 'MinIO', value: 'MINIO' },
];
for (const item of data.value) {
for (let i = 0; i < options.length; i++) {
if (item.type === options[i].value) {
options.splice(i, 1);
}
}
}
typeOptions.value = options;
};
const loadIconName = (type: string) => {
switch (type) {
case 'OSS':
return 'p-oss';
break;
case 'S3':
return 'p-s3';
break;
case 'SFTP':
return 'p-SFTP';
break;
case 'MINIO':
return 'p-minio';
break;
case 'LOCAL':
return 'p-file-folder';
break;
}
};
const loadBackupName = (type: string) => {
switch (type) {
case 'OSS':
return i18n.global.t('setting.OSS');
break;
case 'S3':
return i18n.global.t('setting.S3');
break;
case 'LOCAL':
return i18n.global.t('setting.serverDisk');
break;
default:
return type;
}
};
onMounted(() => { onMounted(() => {
search(); search();

View File

@ -21,6 +21,7 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('auth.password')" :rules="Rules.requiredInput" prop="settingInfo.password"> <el-form-item :label="$t('auth.password')" :rules="Rules.requiredInput" prop="settingInfo.password">
<el-input type="password" clearable disabled v-model="form.settingInfo.password"> <el-input type="password" clearable disabled v-model="form.settingInfo.password">
<template #append> <template #append>
@ -30,6 +31,7 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('auth.email')" :rules="Rules.email" prop="settingInfo.email"> <el-form-item :label="$t('auth.email')" :rules="Rules.email" prop="settingInfo.email">
<el-input clearable v-model="form.settingInfo.email"> <el-input clearable v-model="form.settingInfo.email">
<template #append> <template #append>
@ -45,6 +47,7 @@
<span class="input-help">{{ $t('setting.emailHelper') }}</span> <span class="input-help">{{ $t('setting.emailHelper') }}</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('setting.title')" :label="$t('setting.title')"
:rules="Rules.requiredInput" :rules="Rules.requiredInput"
@ -61,6 +64,7 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('setting.theme')" :rules="Rules.requiredSelect" prop="settingInfo.theme"> <el-form-item :label="$t('setting.theme')" :rules="Rules.requiredSelect" prop="settingInfo.theme">
<el-radio-group <el-radio-group
@change="onSave(panelFormRef, 'Theme', form.settingInfo.theme)" @change="onSave(panelFormRef, 'Theme', form.settingInfo.theme)"
@ -76,12 +80,14 @@
</el-radio-button> </el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('setting.language')" :label="$t('setting.language')"
:rules="Rules.requiredSelect" :rules="Rules.requiredSelect"
prop="settingInfo.language" prop="settingInfo.language"
> >
<el-radio-group <el-radio-group
style="width: 100%"
@change="onSave(panelFormRef, 'Language', form.settingInfo.language)" @change="onSave(panelFormRef, 'Language', form.settingInfo.language)"
v-model="form.settingInfo.language" v-model="form.settingInfo.language"
> >
@ -94,6 +100,7 @@
</span> </span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('setting.sessionTimeout')" :label="$t('setting.sessionTimeout')"
:rules="Rules.number" :rules="Rules.number"
@ -115,6 +122,7 @@
</span> </span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('setting.syncTime')"> <el-form-item :label="$t('setting.syncTime')">
<el-input disabled v-model="form.settingInfo.localTime"> <el-input disabled v-model="form.settingInfo.localTime">
<template #append> <template #append>

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<el-form :model="form" ref="panelFormRef" label-position="left" label-width="160px"> <el-form :model="form" ref="panelFormRef" label-position="left" label-width="160px">
<el-card style="margin-top: 10px"> <el-card style="margin-top: 20px">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>{{ $t('setting.safe') }}</span> <span>{{ $t('setting.safe') }}</span>
@ -86,6 +86,7 @@
:rules="Rules.requiredSelect" :rules="Rules.requiredSelect"
> >
<el-radio-group <el-radio-group
style="width: 100%"
@change=" @change="
onSave( onSave(
panelFormRef, panelFormRef,