mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
feat: add xpackee permission-aware settings flow
This commit is contained in:
committed by
zhengkunwang223
parent
d9cedeca09
commit
69906046e8
@@ -0,0 +1,19 @@
|
||||
//go:build xpackee
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
xpackRouter "github.com/1Panel-dev/1Panel/agent/xpack/router"
|
||||
)
|
||||
|
||||
func RouterGroups() []CommonRouter {
|
||||
baseRouter := commonGroups()
|
||||
for _, ro := range xpackRouter.XpackGroups() {
|
||||
if val, ok := ro.(CommonRouter); ok {
|
||||
baseRouter = append(baseRouter, val)
|
||||
}
|
||||
}
|
||||
return baseRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = RouterGroups()
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build xpackee
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
xpack "github.com/1Panel-dev/1Panel/agent/xpack"
|
||||
)
|
||||
|
||||
func InitOthers() {
|
||||
xpack.Init()
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//go:build xpackee
|
||||
|
||||
package xpack
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
edition "github.com/1Panel-dev/1Panel/agent/xpack/edition"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RemoveTamper(website string) {
|
||||
edition.RemoveTamper(website)
|
||||
}
|
||||
|
||||
func StartClam(startClam *model.Clam, isUpdate bool) (int, error) {
|
||||
return edition.StartClam(startClam, isUpdate)
|
||||
}
|
||||
|
||||
func LoadNodeInfo(isBase bool) (model.NodeInfo, error) {
|
||||
return edition.LoadNodeInfo(isBase)
|
||||
}
|
||||
|
||||
func GetImagePrefix() string {
|
||||
return edition.GetImagePrefix()
|
||||
}
|
||||
|
||||
func IsUseCustomApp() bool {
|
||||
return edition.IsUseCustomApp()
|
||||
}
|
||||
|
||||
func IsXpack() bool {
|
||||
return edition.IsXpack()
|
||||
}
|
||||
|
||||
func CreateTaskScanSMSAlertLog(info dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
|
||||
return edition.CreateTaskScanSMSAlertLog(info, alertType, create, pushAlert, method)
|
||||
}
|
||||
|
||||
func CreateSMSAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
|
||||
return edition.CreateSMSAlertLog(alertType, info, create, project, params, method)
|
||||
}
|
||||
|
||||
func CreateTaskScanWebhookAlertLog(alert dto.AlertDTO, alertType string, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
return edition.CreateTaskScanWebhookAlertLog(alert, alertType, create, pushAlert, method, transport, agentInfo)
|
||||
}
|
||||
|
||||
func CreateWebhookAlertLog(alertType string, info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
|
||||
return edition.CreateWebhookAlertLog(alertType, info, create, project, params, method, transport, agentInfo)
|
||||
}
|
||||
|
||||
func GetLicenseErrorAlert() (uint, error) {
|
||||
return edition.GetLicenseErrorAlert()
|
||||
}
|
||||
|
||||
func GetNodeErrorAlert() (uint, error) {
|
||||
return edition.GetNodeErrorAlert()
|
||||
}
|
||||
|
||||
func LoadRequestTransport() *http.Transport { return edition.LoadRequestTransport() }
|
||||
|
||||
func ValidateCertificate(c *gin.Context) bool {
|
||||
return edition.ValidateCertificate(c)
|
||||
}
|
||||
|
||||
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
|
||||
return edition.PushSSLToNode(websiteSSL)
|
||||
}
|
||||
|
||||
func GetAgentInfo() (*dto.AgentInfo, error) {
|
||||
return edition.GetAgentInfo()
|
||||
}
|
||||
@@ -22,6 +22,15 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
|
||||
res.Code = 401
|
||||
res.Message = msgKey
|
||||
}
|
||||
if msgKey == "ErrRBAC" {
|
||||
res.Code = 412
|
||||
if err != nil {
|
||||
res.Message = err.Error()
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err})
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
ctx.Abort()
|
||||
|
||||
@@ -109,7 +109,7 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
req.Value = value
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
if err := settingService.Update(c, req.Key, req.Value); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func (b *BaseApi) UpdateMenu(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update(req.Key, req.Value); err != nil {
|
||||
if err := settingService.Update(c, req.Key, req.Value); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
@@ -411,17 +411,17 @@ func (b *BaseApi) MFABind(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAInterval", req.Interval); err != nil {
|
||||
if err := settingService.Update(c, "MFAInterval", req.Interval); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFAStatus", constant.StatusEnable); err != nil {
|
||||
if err := settingService.Update(c, "MFAStatus", constant.StatusEnable); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.Update("MFASecret", req.Secret); err != nil {
|
||||
if err := settingService.Update(c, "MFASecret", req.Secret); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (u *AuthService) generateSession(c *gin.Context, name string) (*dto.UserLog
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionUser := psession.SessionUser{Name: name, Role: "ADMIN"}
|
||||
sessionUser := psession.SessionUser{ID: psession.SuperAdminSessionUserID, Name: name, Role: "ADMIN"}
|
||||
lifeTime = xpack.LoadSessionTimeout(sessionUser, lifeTime)
|
||||
if err := global.SESSION.SetFresh(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -44,7 +44,7 @@ type SettingService struct{}
|
||||
type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
LoadInterfaceAddr() ([]string, error)
|
||||
Update(key, value string) error
|
||||
Update(c *gin.Context, key, value string) error
|
||||
UpdatePassword(c *gin.Context, old, new string) error
|
||||
UpdatePort(port uint) error
|
||||
UpdateBindInfo(req dto.BindInfo) error
|
||||
@@ -127,7 +127,7 @@ func sortShowMenus(menus []dto.ShowMenu) {
|
||||
})
|
||||
}
|
||||
|
||||
func (u *SettingService) Update(key, value string) error {
|
||||
func (u *SettingService) Update(c *gin.Context, key, value string) error {
|
||||
oldVal, err := settingRepo.Get(repo.WithByKey(key))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -180,7 +180,7 @@ func (u *SettingService) Update(key, value string) error {
|
||||
return err
|
||||
}
|
||||
case "UserName", "Password":
|
||||
_ = global.SESSION.DeleteByID("")
|
||||
u.deleteCurrentSession(c)
|
||||
case "Language":
|
||||
i18n.SetCachedDBLanguage(value)
|
||||
if err := xpack.Sync(constant.SyncLanguage); err != nil {
|
||||
@@ -566,10 +566,21 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
|
||||
if err := u.HandlePasswordExpired(c, old, new); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = global.SESSION.DeleteByID("")
|
||||
u.deleteCurrentSession(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) deleteCurrentSession(c *gin.Context) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
sessionUser, err := global.SESSION.Get(c)
|
||||
if err != nil || sessionUser.ID == "" {
|
||||
return
|
||||
}
|
||||
_ = global.SESSION.DeleteByID(sessionUser.ID)
|
||||
}
|
||||
|
||||
func (u *SettingService) clearPasskeySettings() error {
|
||||
if err := settingRepo.Update(passkey.PasskeyUserIDSettingKey, ""); err != nil {
|
||||
return err
|
||||
|
||||
Binary file not shown.
@@ -55,6 +55,8 @@ AppInstallCheck: 'Check application installation environment'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted"
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: "Backup account connection test failed {{ .err }}"
|
||||
ErrBackupLocal: "Local server backup account does not support this operation!"
|
||||
ErrBackupPublic: "Detected that this backup account is not public, check and try again!"
|
||||
|
||||
@@ -54,6 +54,8 @@ AppInstallCheck: 'Verificar entorno de instalación de aplicación'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'Cuenta de respaldo en uso por tarea programada'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: 'Conexión de respaldo falló: {{ .err }}'
|
||||
ErrBackupLocal: "La cuenta de respaldo del servidor local no admite esta operación"
|
||||
ErrBackupPublic: "Se detectó que esta cuenta de respaldo no es pública, verifique e intente de nuevo"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: 'アプリケーションインストール環境を確認'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'バックアップアカウントがスケジュールで使用中'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: '接続テストに失敗しました: {{ .err }}'
|
||||
ErrBackupLocal: "ローカルサーバーバックアップアカウントはこの操作をサポートしていません!"
|
||||
ErrBackupPublic: "このバックアップアカウントが公開されていないと検出されました。再確認してください!"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: '애플리케이션 설치 환경 확인'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: '백업 계정이 예약에 사용 중'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: '연결 테스트 실패: {{ .err }}'
|
||||
ErrBackupLocal: "로컬 서버 백업 계정은 이 작업을 지원하지 않습니다"
|
||||
ErrBackupPublic: "이 백업 계정이 공개된 것으로 감지되지 않았습니다. 다시 확인하십시오"
|
||||
|
||||
@@ -44,6 +44,8 @@ ErrFileNotFound: "Fail {{ .name }} tidak wujud"
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'Akaun sandaran sedang digunakan oleh tugas'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: 'Ujian sambungan gagal: {{ .err }}'
|
||||
ErrBackupLocal: "Akaun sandaran pelayan tempatan tidak menyokong operasi ini"
|
||||
ErrBackupPublic: "Akaun sandaran ini dikesan tidak awam, sila semak semula dan cuba lagi"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: 'Verificar ambiente de instalação da aplicação'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'Conta de backup em uso por tarefa'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: 'Teste de conexão falhou: {{ .err }}'
|
||||
ErrBackupLocal: "A conta de backup do servidor local não suporta essa operação"
|
||||
ErrBackupPublic: "A conta de backup detectada não é pública, por favor verifique e tente novamente"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: 'Проверить среду установки прилож
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'Аккаунт бэкапа занят задачей'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: 'Проверка подключения не удалась: {{ .err }}'
|
||||
ErrBackupLocal: "Локальная учетная запись резервного копирования не поддерживает эту операцию"
|
||||
ErrBackupPublic: "Обнаружено, что эта учетная запись резервного копирования не является публичной, проверьте и повторите попытку"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: 'Uygulama kurulum ortamını kontrol et'
|
||||
|
||||
# backup
|
||||
ErrBackupInUsed: 'Yedek hesabı görevde kullanılıyor'
|
||||
ErrRolePresetCannotDelete: "System preset roles cannot be deleted"
|
||||
ErrRoleBoundToUser: "This role is already bound to users and cannot be deleted"
|
||||
ErrBackupCheck: 'Bağlantı testi başarısız: {{ .err }}'
|
||||
ErrBackupLocal: "Yerel sunucu yedekleme hesabı bu işlemi desteklemiyor"
|
||||
ErrBackupPublic: "Bu yedekleme hesabının herkese açık olmadığı tespit edildi, lütfen kontrol edip tekrar deneyin"
|
||||
|
||||
@@ -49,6 +49,8 @@ AppInstallCheck: '檢查應用安裝環境'
|
||||
|
||||
#backup
|
||||
ErrBackupInUsed: "該備份帳號已在排程任務中使用,無法刪除"
|
||||
ErrRolePresetCannotDelete: "系統預設角色,無法刪除"
|
||||
ErrRoleBoundToUser: "角色已被使用者綁定,無法刪除"
|
||||
ErrBackupCheck: "備份帳號測試連線失敗 {{ .err }}"
|
||||
ErrBackupLocal: "本機伺服器備份帳號暫不支援該操作!"
|
||||
ErrBackupPublic: "偵測到該備份帳號為非公用,請檢查後再試。"
|
||||
|
||||
@@ -55,6 +55,8 @@ AppInstallCheck: "检查应用安装环境"
|
||||
|
||||
#backup
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
ErrRolePresetCannotDelete: "系统预设角色,无法删除"
|
||||
ErrRoleBoundToUser: "角色已被用户绑定,无法删除"
|
||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
|
||||
ErrBackupLocal: "本地服务器备份账号暂不支持该操作!"
|
||||
ErrBackupPublic: "检测到该备份账号为非公用,请检查后重试!"
|
||||
|
||||
@@ -6,13 +6,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/init/proxy"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/proxy"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/xpack"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||
rou "github.com/1Panel-dev/1Panel/core/router"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/security"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/xpack"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -87,6 +88,7 @@ func Routers() *gin.Engine {
|
||||
Router.Use(middleware.PasswordExpired())
|
||||
Router.Use(middleware.ApiAuth())
|
||||
Router.Use(middleware.CSRFTokenGuard())
|
||||
Router.Use(xpack.CoreRBACMiddlewares()...)
|
||||
Router.Use(Proxy())
|
||||
|
||||
PrivateGroup := Router.Group("/api/v2/core")
|
||||
|
||||
@@ -19,6 +19,8 @@ type SessionUser struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
const SuperAdminSessionUserID = "__super_admin__"
|
||||
|
||||
type sessionItem struct {
|
||||
CreatedAt time.Time
|
||||
CSRFToken string
|
||||
|
||||
@@ -10,13 +10,29 @@ import (
|
||||
"time"
|
||||
|
||||
baseDto "github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/init/proxy"
|
||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/core/xpack/app/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Proxy(c *gin.Context, currentNode string) {}
|
||||
func Proxy(c *gin.Context, currentNode string) {
|
||||
if currentNode != "local" && currentNode != "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil && err != http.ErrAbortHandler {
|
||||
global.LOG.Debug(err)
|
||||
}
|
||||
}()
|
||||
proxy.LocalAgentProxy.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
func CoreRBACMiddlewares() []gin.HandlerFunc { return nil }
|
||||
|
||||
func ProxyDocker(proxyURL string) error { return nil }
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ func Proxy(c *gin.Context, currentNode string) {
|
||||
edition.Proxy(c, currentNode)
|
||||
}
|
||||
|
||||
func CoreRBACMiddlewares() []gin.HandlerFunc { return nil }
|
||||
|
||||
func ProxyDocker(proxyURL string) error { return edition.ProxyDocker(proxyURL) }
|
||||
|
||||
func UpdateGroup(name string, group, newGroup uint) error {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/core/init/session/psession"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/ssh"
|
||||
edition "github.com/1Panel-dev/1Panel/core/xpack-ee/edition"
|
||||
xeemiddleware "github.com/1Panel-dev/1Panel/core/xpack-ee/router/middleware"
|
||||
"github.com/1Panel-dev/1Panel/core/xpack/app/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -17,6 +18,10 @@ func Proxy(c *gin.Context, currentNode string) {
|
||||
edition.Proxy(c, currentNode)
|
||||
}
|
||||
|
||||
func CoreRBACMiddlewares() []gin.HandlerFunc {
|
||||
return []gin.HandlerFunc{xeemiddleware.RequireRBAC()}
|
||||
}
|
||||
|
||||
func ProxyDocker(proxyURL string) error { return edition.ProxyDocker(proxyURL) }
|
||||
|
||||
func UpdateGroup(name string, group, newGroup uint) error {
|
||||
|
||||
@@ -2,9 +2,9 @@ import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
export const checkStatus = (status: number, msg: string): void => {
|
||||
const globalStore = GlobalStore();
|
||||
switch (status) {
|
||||
case 400:
|
||||
MsgError(msg ? msg : i18n.global.t('commons.res.paramError'));
|
||||
|
||||
@@ -9,7 +9,8 @@ import { encodeBase64 } from '@/utils/base64';
|
||||
import i18n from '@/lang';
|
||||
import { changeToLocal } from '@/utils/node';
|
||||
import { getCookie } from '@/utils/auth';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
const config = {
|
||||
baseURL: import.meta.env.VITE_API_URL as string,
|
||||
@@ -28,6 +29,7 @@ class RequestHttp {
|
||||
this.service = axios.create(config);
|
||||
this.service.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const globalStore = getGlobalStore();
|
||||
let language = globalStore.language;
|
||||
config.headers = {
|
||||
'Accept-Language': language,
|
||||
@@ -67,6 +69,7 @@ class RequestHttp {
|
||||
|
||||
this.service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { data } = response;
|
||||
if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) {
|
||||
globalStore.isLogin = false;
|
||||
@@ -76,6 +79,10 @@ class RequestHttp {
|
||||
});
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code == ResultEnum.ERRRBAC) {
|
||||
MsgError(data.message || i18n.global.t('commons.res.forbidden'));
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code == ResultEnum.EXPIRED) {
|
||||
router.push({ name: 'Expired' });
|
||||
return;
|
||||
|
||||
@@ -49,6 +49,9 @@ export namespace Login {
|
||||
id: number;
|
||||
name: string;
|
||||
role: string;
|
||||
permissions: string[];
|
||||
nodeScopes: number[];
|
||||
nodeRoles: Array<{ nodeId: number; nodeName: string; roleId: number; roleName: string }>;
|
||||
}
|
||||
export interface AuthInfoUpdate {
|
||||
id: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Login } from '@/api/interface/auth';
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export const loginApi = (params: Login.ReqLoginForm) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ResPage } from '../interface';
|
||||
import { Backup } from '../interface/backup';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
// backup-agent
|
||||
export const getLocalBackupDir = (node?: string) => {
|
||||
@@ -16,6 +16,7 @@ export const searchBackup = (params: Backup.SearchWithType) => {
|
||||
return http.post<ResPage<Backup.BackupInfo>>(`/backups/search`, params);
|
||||
};
|
||||
export const checkBackup = (params: Backup.BackupOperate) => {
|
||||
const globalStore = getGlobalStore();
|
||||
let request = deepCopy(params) as Backup.BackupOperate;
|
||||
encodeBase64Fields(request, ['accessKey', 'credential']);
|
||||
if (!params.isPublic || !globalStore.isProductPro) {
|
||||
@@ -24,6 +25,7 @@ export const checkBackup = (params: Backup.BackupOperate) => {
|
||||
return http.post<Backup.CheckResult>(`/backups/conn/check`, request);
|
||||
};
|
||||
export const listBucket = (params: Backup.ForBucket) => {
|
||||
const globalStore = getGlobalStore();
|
||||
let request = deepCopy(params) as Backup.BackupOperate;
|
||||
encodeBase64Fields(request, ['accessKey', 'credential']);
|
||||
if (!params.isPublic || !globalStore.isProductPro) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { listNodes } from '@/utils/node';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { globalStore } = useGlobalStore();
|
||||
|
||||
@@ -556,9 +556,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
.ai-notice-fade-enter-active,
|
||||
.ai-notice-fade-leave-active {
|
||||
transition:
|
||||
opacity 180ms ease,
|
||||
transform 180ms ease;
|
||||
transition: opacity 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
.ai-mask-fade-enter-active,
|
||||
|
||||
@@ -13,6 +13,7 @@ export enum ResultEnum {
|
||||
ERRGLOBALLOADING = 407,
|
||||
ERRXPACK = 410,
|
||||
NodeUnBind = 411,
|
||||
ERRRBAC = 412,
|
||||
TIMEOUT = 20000,
|
||||
TYPE = 'success',
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ const loadNodes = async () => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const changeNode = (command: string) => {
|
||||
const changeNode = async (command: string) => {
|
||||
if (globalStore.currentNode === command) {
|
||||
return;
|
||||
}
|
||||
@@ -233,6 +233,9 @@ const changeNode = (command: string) => {
|
||||
if (command == 'local') {
|
||||
globalStore.currentNode = 'local';
|
||||
globalStore.currentNodeAddr = item.addr;
|
||||
if (globalStore.isXpackEE) {
|
||||
await loadCurrentUser();
|
||||
}
|
||||
loadGlobalSetting();
|
||||
localStorage.removeItem('dashboardCache');
|
||||
localStorage.removeItem('upgradeChecked');
|
||||
@@ -257,6 +260,9 @@ const changeNode = (command: string) => {
|
||||
localStorage.removeItem('upgradeChecked');
|
||||
globalStore.currentNode = command || 'local';
|
||||
globalStore.currentNodeAddr = item.addr;
|
||||
if (globalStore.isXpackEE) {
|
||||
await loadCurrentUser();
|
||||
}
|
||||
loadProductProFromDB();
|
||||
routerToNameWithQuery('home', { t: Date.now() });
|
||||
}
|
||||
@@ -298,7 +304,8 @@ const logout = () => {
|
||||
.then(async () => {
|
||||
await logOutApi();
|
||||
router.push({ name: 'entrance', params: { code: globalStore.entrance } });
|
||||
globalStore.isLogin = false;
|
||||
globalStore.setLogStatus(false);
|
||||
globalStore.clearAuthInfo();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -307,6 +314,12 @@ const logout = () => {
|
||||
const loadCurrentUser = async () => {
|
||||
await getAuthInfo().then((res) => {
|
||||
currentUser.value = res.data;
|
||||
globalStore.setAuthInfo({
|
||||
isAdmin: res.data.role === 'ADMIN',
|
||||
permissions: res.data.permissions || [],
|
||||
nodeScopes: res.data.nodeScopes || [],
|
||||
nodeRoles: res.data.nodeRoles || [],
|
||||
});
|
||||
});
|
||||
};
|
||||
const changeUserInfo = () => {
|
||||
@@ -334,6 +347,7 @@ const onSubmit = async (formEl: any) => {
|
||||
await logOutApi();
|
||||
router.push({ name: 'entrance', params: { code: globalStore.entrance } });
|
||||
globalStore.setLogStatus(false);
|
||||
globalStore.clearAuthInfo();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { RouteRecordRaw, useRoute } from 'vue-router';
|
||||
import { loadingSvg } from '@/utils/svg';
|
||||
import Logo from './components/Logo.vue';
|
||||
@@ -38,6 +38,7 @@ import { menuList } from '@/routers/router';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
import PrimaryMenu from '@/assets/images/menu-bg.svg?component';
|
||||
import { hasPermission } from '@/utils/rbac';
|
||||
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
@@ -95,24 +96,10 @@ const search = async () => {
|
||||
const rstMenuList: RouteRecordRaw[] = [];
|
||||
const resMenuList = adjustAndCleanMenu(hideMenu, menuList);
|
||||
for (const menu of resMenuList) {
|
||||
let menuItem = JSON.parse(JSON.stringify(menu));
|
||||
if (!showSet.has(menuItem.name as string)) {
|
||||
continue;
|
||||
} else if (menuItem.name === 'Xpack-Menu') {
|
||||
menuItem.meta.hideInSidebar = false;
|
||||
const menuItem = buildVisibleMenu(menu, showSet);
|
||||
if (menuItem) {
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
const itemChildren =
|
||||
(menuItem.children ?? []).filter(
|
||||
(item) =>
|
||||
item.name && showSet.has(item.name as string) && !(item.name === 'Upage' && globalStore.isIntl),
|
||||
) || [];
|
||||
|
||||
if (itemChildren.length === 1) {
|
||||
menuItem.meta.icon = itemChildren[0].meta.icon;
|
||||
menuItem.meta.title = itemChildren[0].meta.title;
|
||||
}
|
||||
menuItem.children = itemChildren;
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
if (!isSameMenuList(menuStore.menuList as RouteRecordRaw[], rstMenuList)) {
|
||||
menuStore.setMenuList(rstMenuList);
|
||||
@@ -128,6 +115,53 @@ function isSameMenuList(source: RouteRecordRaw[], target: RouteRecordRaw[]) {
|
||||
return JSON.stringify(source) === JSON.stringify(target);
|
||||
}
|
||||
|
||||
function allowMenuItem(item: RouteRecordRaw) {
|
||||
const permission = item.meta?.permission as string | undefined;
|
||||
if (!permission) {
|
||||
return true;
|
||||
}
|
||||
const allowed = hasPermission(permission);
|
||||
return allowed;
|
||||
}
|
||||
|
||||
function buildVisibleMenu(menu: RouteRecordRaw, showSet: Set<string>): RouteRecordRaw | null {
|
||||
const menuItem = JSON.parse(JSON.stringify(menu));
|
||||
if (!menuItem?.name || !showSet.has(menuItem.name as string)) {
|
||||
return null;
|
||||
}
|
||||
if (!allowMenuItem(menuItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = Array.isArray(menuItem.children) ? menuItem.children : [];
|
||||
if (children.length === 0) {
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
const visibleChildren = children
|
||||
.map((item) => {
|
||||
if (item.name === 'Upage' && globalStore.isIntl) {
|
||||
return null;
|
||||
}
|
||||
return buildVisibleMenu(item, showSet);
|
||||
})
|
||||
.filter(Boolean) as RouteRecordRaw[];
|
||||
|
||||
menuItem.children = visibleChildren;
|
||||
if (menuItem.children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (menuItem.children.length === 1) {
|
||||
menuItem.meta.icon = menuItem.children[0].meta.icon;
|
||||
menuItem.meta.title = menuItem.children[0].meta.title;
|
||||
}
|
||||
if (menuItem.name === 'Xpack-Menu') {
|
||||
menuItem.meta.hideInSidebar = false;
|
||||
}
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
function adjustAndCleanMenu(menuItem, list) {
|
||||
const menuList = JSON.parse(JSON.stringify(list));
|
||||
const itemMap = new Map();
|
||||
@@ -178,6 +212,13 @@ onMounted(() => {
|
||||
}
|
||||
search();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [globalStore.currentNode, globalStore.permissions.join('|')],
|
||||
() => {
|
||||
search();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,6 +2,9 @@ import router from '@/routers/router';
|
||||
import NProgress from '@/config/nprogress';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { AxiosCanceler } from '@/api/helper/axios-cancel';
|
||||
import { hasPermission } from '@/utils/rbac';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError } from '@/utils/message';
|
||||
|
||||
const axiosCanceler = new AxiosCanceler();
|
||||
|
||||
@@ -62,6 +65,16 @@ router.beforeEach((to, from, next) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const requiredPermission = [...to.matched].reverse().find((record) => record.meta?.permission)?.meta?.permission as
|
||||
| string
|
||||
| undefined;
|
||||
if (requiredPermission && !hasPermission(requiredPermission)) {
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!to.matched.some((record) => record.meta.requiresAuth)) return next();
|
||||
|
||||
return next();
|
||||
@@ -96,19 +109,46 @@ const xpackEEJumper = (to: any, next: any) => {
|
||||
switch (to.name) {
|
||||
case 'Panel':
|
||||
case 'Safe':
|
||||
case 'Alert':
|
||||
if (hasPermission('setting_view')) {
|
||||
return false;
|
||||
}
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return true;
|
||||
case 'License':
|
||||
next({
|
||||
name: 'Alert',
|
||||
});
|
||||
if (hasPermission('setting_view')) {
|
||||
return false;
|
||||
}
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return true;
|
||||
case 'Node':
|
||||
case 'SimpleNode':
|
||||
case 'NodeAppUpgrade':
|
||||
if (hasPermission('node_view')) {
|
||||
return false;
|
||||
}
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return true;
|
||||
case 'UserXpackEEUser':
|
||||
next({
|
||||
name: 'NodeDashboard',
|
||||
});
|
||||
if (hasPermission('setting_view')) {
|
||||
return false;
|
||||
}
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return true;
|
||||
case 'XpackEERole':
|
||||
if (hasPermission('setting_view')) {
|
||||
return false;
|
||||
}
|
||||
MsgError(i18n.global.t('commons.res.forbidden'));
|
||||
next(false);
|
||||
NProgress.done();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const aiRouter = {
|
||||
meta: {
|
||||
icon: 'p-jiqiren2',
|
||||
title: 'aiTools.agents.agent',
|
||||
permission: 'ai_agent_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
@@ -28,6 +29,7 @@ const aiRouter = {
|
||||
meta: {
|
||||
icon: 'p-moxing-menu',
|
||||
title: 'aiTools.model.model',
|
||||
permission: 'ai_model_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
@@ -39,6 +41,7 @@ const aiRouter = {
|
||||
meta: {
|
||||
title: 'aiTools.model.localModel',
|
||||
activeMenu: '/ai/model/account',
|
||||
permission: 'ai_model_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
@@ -49,6 +52,7 @@ const aiRouter = {
|
||||
meta: {
|
||||
icon: 'p-mcp-menu',
|
||||
title: 'menu.mcp',
|
||||
permission: 'ai_mcp_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
@@ -60,6 +64,7 @@ const aiRouter = {
|
||||
icon: 'p-gpu-menu',
|
||||
title: 'aiTools.gpu.gpu',
|
||||
activeMenu: '/ai/gpu/current',
|
||||
permission: 'ai_gpu_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
@@ -70,6 +75,7 @@ const aiRouter = {
|
||||
meta: {
|
||||
title: 'aiTools.gpu.history',
|
||||
activeMenu: '/ai/gpu/current',
|
||||
permission: 'ai_gpu_view',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ const appStoreRouter = {
|
||||
meta: {
|
||||
icon: 'p-appstore',
|
||||
title: 'menu.apps',
|
||||
permission: 'app_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -16,7 +17,9 @@ const appStoreRouter = {
|
||||
name: 'App',
|
||||
redirect: '/apps/all',
|
||||
component: () => import('@/views/app-store/index.vue'),
|
||||
meta: {},
|
||||
meta: {
|
||||
permission: 'app_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
@@ -29,6 +32,7 @@ const appStoreRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.app',
|
||||
title: 'app.all',
|
||||
permission: 'app_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -42,6 +46,7 @@ const appStoreRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.app',
|
||||
title: 'app.installed',
|
||||
permission: 'app_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -55,6 +60,7 @@ const appStoreRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.app',
|
||||
title: 'app.canUpgrade',
|
||||
permission: 'app_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,6 +74,7 @@ const appStoreRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.app',
|
||||
title: 'commons.button.set',
|
||||
permission: 'app_view',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -42,6 +42,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'menu.container',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -66,6 +67,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.image',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,6 +80,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.network',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -90,6 +93,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.volume',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,6 +106,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.repo',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -114,6 +119,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.compose',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -126,6 +132,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.composeTemplate',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -138,6 +145,7 @@ const containerRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.container',
|
||||
title: 'container.setting',
|
||||
permission: 'container_view',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -27,6 +27,7 @@ const cronRouter = {
|
||||
activeMenu: '/cronjobs',
|
||||
requiresAuth: false,
|
||||
title: 'menu.cronjob',
|
||||
permission: 'cronjob_task_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -49,6 +50,7 @@ const cronRouter = {
|
||||
activeMenu: '/cronjobs',
|
||||
requiresAuth: false,
|
||||
title: 'cronjob.library.library',
|
||||
permission: 'cronjob_script_view',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -28,6 +28,7 @@ const databaseRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.database',
|
||||
title: 'MySQL',
|
||||
permission: 'database_mysql_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -53,6 +54,7 @@ const databaseRouter = {
|
||||
parent: 'menu.database',
|
||||
title: 'MySQL',
|
||||
detail: 'database.remote',
|
||||
permission: 'database_mysql_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -65,6 +67,7 @@ const databaseRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.database',
|
||||
title: 'PostgreSQL',
|
||||
permission: 'database_postgresql_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,6 +81,7 @@ const databaseRouter = {
|
||||
parent: 'menu.database',
|
||||
title: 'PostgreSQL',
|
||||
detail: 'database.remote',
|
||||
permission: 'database_postgresql_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,6 +106,7 @@ const databaseRouter = {
|
||||
requiresAuth: false,
|
||||
parent: 'menu.database',
|
||||
title: 'Redis',
|
||||
permission: 'database_redis_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -115,6 +120,7 @@ const databaseRouter = {
|
||||
parent: 'menu.database',
|
||||
title: 'Redis',
|
||||
detail: 'database.remote',
|
||||
permission: 'database_redis_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ const hostRouter = {
|
||||
icon: 'p-file-menu',
|
||||
title: 'menu.files',
|
||||
requiresAuth: false,
|
||||
permission: 'host_file_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -30,6 +31,7 @@ const hostRouter = {
|
||||
icon: 'p-system-monitor-menu',
|
||||
title: 'menu.monitor',
|
||||
requiresAuth: false,
|
||||
permission: 'host_monitor_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -42,6 +44,7 @@ const hostRouter = {
|
||||
title: 'menu.monitor',
|
||||
detail: 'commons.button.set',
|
||||
requiresAuth: false,
|
||||
permission: 'host_monitor_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -54,6 +57,7 @@ const hostRouter = {
|
||||
title: 'menu.firewall',
|
||||
detail: 'firewall.portRule',
|
||||
requiresAuth: false,
|
||||
permission: 'host_firewall_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -101,6 +105,7 @@ const hostRouter = {
|
||||
icon: 'p-disk-menu',
|
||||
title: 'menu.disk',
|
||||
requiresAuth: false,
|
||||
permission: 'host_disk_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -114,6 +119,7 @@ const hostRouter = {
|
||||
activeMenu: '/hosts/process/process',
|
||||
keepAlive: true,
|
||||
requiresAuth: false,
|
||||
permission: 'host_process_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -139,6 +145,7 @@ const hostRouter = {
|
||||
activeMenu: '/hosts/ssh/ssh',
|
||||
keepAlive: true,
|
||||
requiresAuth: false,
|
||||
permission: 'host_ssh_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const logsRouter = {
|
||||
meta: {
|
||||
title: 'menu.logs',
|
||||
icon: 'p-log',
|
||||
permission: 'log_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -28,6 +29,7 @@ const logsRouter = {
|
||||
title: 'logs.operation',
|
||||
activeMenu: '/logs',
|
||||
requiresAuth: false,
|
||||
permission: 'log_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,6 +42,7 @@ const logsRouter = {
|
||||
title: 'logs.login',
|
||||
activeMenu: '/logs',
|
||||
requiresAuth: false,
|
||||
permission: 'log_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -64,6 +67,7 @@ const logsRouter = {
|
||||
title: 'logs.system',
|
||||
activeMenu: '/logs',
|
||||
requiresAuth: false,
|
||||
permission: 'log_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ const settingRouter = {
|
||||
title: 'setting.panel',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,6 +41,7 @@ const settingRouter = {
|
||||
title: 'xpack.alert.alertNotice',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -52,6 +54,7 @@ const settingRouter = {
|
||||
title: 'setting.backupAccount',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -64,6 +67,7 @@ const settingRouter = {
|
||||
title: 'setting.license',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -76,6 +80,7 @@ const settingRouter = {
|
||||
title: 'setting.about',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -88,6 +93,7 @@ const settingRouter = {
|
||||
title: 'setting.safe',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -100,6 +106,7 @@ const settingRouter = {
|
||||
title: 'setting.snapshot',
|
||||
requiresAuth: true,
|
||||
activeMenu: '/settings',
|
||||
permission: 'setting_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const toolboxRouter = {
|
||||
meta: {
|
||||
title: 'menu.toolbox',
|
||||
icon: 'p-toolbox',
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -28,6 +29,7 @@ const toolboxRouter = {
|
||||
title: 'toolbox.device.toolbox',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,6 +42,7 @@ const toolboxRouter = {
|
||||
title: 'menu.supervisor',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -52,6 +55,7 @@ const toolboxRouter = {
|
||||
title: 'toolbox.clam.clam',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -75,6 +79,7 @@ const toolboxRouter = {
|
||||
title: 'FTP',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,6 +92,7 @@ const toolboxRouter = {
|
||||
title: 'Fail2Ban',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -99,6 +105,7 @@ const toolboxRouter = {
|
||||
title: 'setting.diskClean',
|
||||
activeMenu: '/toolbox',
|
||||
requiresAuth: false,
|
||||
permission: 'toolbox_view',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@ const webSiteRouter = {
|
||||
icon: 'p-website',
|
||||
title: 'menu.website',
|
||||
requiresAuth: false,
|
||||
permission: 'website_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -41,6 +42,7 @@ const webSiteRouter = {
|
||||
icon: 'p-ssl-menu',
|
||||
title: 'menu.ssl',
|
||||
requiresAuth: false,
|
||||
permission: 'website_cert_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -53,6 +55,7 @@ const webSiteRouter = {
|
||||
detail: 'PHP',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -65,6 +68,7 @@ const webSiteRouter = {
|
||||
detail: 'Node',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -77,6 +81,7 @@ const webSiteRouter = {
|
||||
detail: 'Java',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -89,6 +94,7 @@ const webSiteRouter = {
|
||||
detail: 'Go',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -101,6 +107,7 @@ const webSiteRouter = {
|
||||
detail: 'Python',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -113,6 +120,7 @@ const webSiteRouter = {
|
||||
detail: '.Net',
|
||||
activeMenu: '/websites/runtimes/php',
|
||||
requiresAuth: false,
|
||||
permission: 'website_runtime_view',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -56,6 +56,9 @@ export interface GlobalState {
|
||||
isOnRestart: boolean;
|
||||
// tags
|
||||
isAdmin: boolean;
|
||||
permissions: string[];
|
||||
nodeScopes: number[];
|
||||
nodeRoles: Array<{ nodeId: number; nodeName: string; roleId: number; roleName: string }>;
|
||||
isXpackEE: boolean;
|
||||
isIntl: boolean;
|
||||
docWithRegion: boolean;
|
||||
|
||||
@@ -53,6 +53,9 @@ const GlobalStore = defineStore({
|
||||
isOnRestart: false,
|
||||
// tags
|
||||
isAdmin: false,
|
||||
permissions: [],
|
||||
nodeScopes: [],
|
||||
nodeRoles: [],
|
||||
isXpackEE: false,
|
||||
isIntl: false,
|
||||
docWithRegion: true,
|
||||
@@ -87,6 +90,38 @@ const GlobalStore = defineStore({
|
||||
setScreenFull() {
|
||||
this.isFullScreen = !this.isFullScreen;
|
||||
},
|
||||
setLogStatus(login: boolean) {
|
||||
this.isLogin = login;
|
||||
},
|
||||
setAuthInfo(payload: {
|
||||
isAdmin: boolean;
|
||||
permissions: string[];
|
||||
nodeScopes: number[];
|
||||
nodeRoles?: Array<{ nodeId: number; nodeName: string; roleId: number; roleName: string }>;
|
||||
}) {
|
||||
this.isAdmin = !!payload.isAdmin;
|
||||
this.permissions = payload.permissions || [];
|
||||
this.nodeScopes = payload.nodeScopes || [];
|
||||
this.nodeRoles = payload.nodeRoles || [];
|
||||
},
|
||||
clearAuthInfo() {
|
||||
this.permissions = [];
|
||||
this.nodeScopes = [];
|
||||
this.nodeRoles = [];
|
||||
this.isAdmin = false;
|
||||
},
|
||||
hasPermission(permission: string) {
|
||||
return this.isAdmin || this.permissions.includes(permission);
|
||||
},
|
||||
setGlobalLoading(loading: boolean) {
|
||||
this.isLoading = loading;
|
||||
},
|
||||
setLoadingText(text: string) {
|
||||
this.loadingText = text;
|
||||
},
|
||||
setCsrfToken(token: string) {
|
||||
this.csrfToken = token;
|
||||
},
|
||||
async updateLanguage(language: string) {
|
||||
const activeLocale = await setActiveLocale(language);
|
||||
this.language = activeLocale;
|
||||
|
||||
@@ -2,9 +2,10 @@ import { jumpToPath } from './router';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
export const jumpToInstall = (type: string, key: string) => {
|
||||
const globalStore = getGlobalStore();
|
||||
switch (type) {
|
||||
case 'php':
|
||||
case 'node':
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Setting } from '@/api/interface/setting';
|
||||
import { listNodeOptions, loadNodeByUser } from '@/api/modules/setting';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
export const changeToLocal = async () => {
|
||||
const globalStore = getGlobalStore();
|
||||
let nodes = await listNodes('all');
|
||||
if (nodes.length === 0) {
|
||||
setDefaultNodeInfo();
|
||||
@@ -24,6 +25,7 @@ export const changeToLocal = async () => {
|
||||
};
|
||||
|
||||
export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> {
|
||||
const globalStore = getGlobalStore();
|
||||
try {
|
||||
if (globalStore.isAdmin) {
|
||||
const res = await listNodeOptions(type);
|
||||
@@ -38,6 +40,7 @@ export async function listNodes(type: string): Promise<Array<Setting.NodeItem>>
|
||||
}
|
||||
|
||||
export const setDefaultNodeInfo = () => {
|
||||
const globalStore = getGlobalStore();
|
||||
globalStore.currentNode = 'local';
|
||||
globalStore.currentNodeAddr = '127.0.0.1';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getAuthInfo } from '@/api/modules/auth';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
export const syncAuthInfo = async () => {
|
||||
const globalStore = GlobalStore();
|
||||
if (!globalStore.isXpackEE) {
|
||||
return;
|
||||
}
|
||||
const res = await getAuthInfo();
|
||||
globalStore.setAuthInfo({
|
||||
isAdmin: res.data.role === 'ADMIN',
|
||||
permissions: res.data.permissions || [],
|
||||
nodeScopes: res.data.nodeScopes || [],
|
||||
nodeRoles: res.data.nodeRoles || [],
|
||||
});
|
||||
};
|
||||
|
||||
export const hasPermission = (permission: string) => {
|
||||
return GlobalStore().hasPermission(permission);
|
||||
};
|
||||
@@ -10,11 +10,13 @@ import {
|
||||
updateXpackSettingByKey as updateXpackSettingByKeyFromExtension,
|
||||
} from '@/extensions/xpack';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
const { switchTheme } = useTheme();
|
||||
import faviconUrl from '@/assets/images/favicon.svg';
|
||||
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
export function resetXSetting() {
|
||||
const globalStore = getGlobalStore();
|
||||
globalStore.themeConfig.title = '';
|
||||
globalStore.themeConfig.logo = '';
|
||||
globalStore.themeConfig.logoWithText = '';
|
||||
@@ -32,6 +34,7 @@ async function getColoredFavicon(url: string, color: string) {
|
||||
}
|
||||
|
||||
export async function initFavicon() {
|
||||
const globalStore = getGlobalStore();
|
||||
document.title = globalStore.themeConfig.panelName;
|
||||
const favicon = globalStore.themeConfig.favicon;
|
||||
const isPro = globalStore.isXpackOrEE();
|
||||
@@ -71,6 +74,7 @@ export async function getXpackSetting() {
|
||||
}
|
||||
|
||||
const loadDataFromDB = async () => {
|
||||
const globalStore = getGlobalStore();
|
||||
const res = await getSettingInfo();
|
||||
document.title = res.data.panelName;
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
@@ -78,6 +82,7 @@ const loadDataFromDB = async () => {
|
||||
};
|
||||
|
||||
export async function loadProductProFromDB() {
|
||||
const globalStore = getGlobalStore();
|
||||
if (!globalStore.isXpackEE) {
|
||||
const res = await getLicenseStatus();
|
||||
if (!res || !res.data) {
|
||||
@@ -99,6 +104,7 @@ export async function loadProductProFromDB() {
|
||||
}
|
||||
|
||||
export async function loadMasterProductProFromDB() {
|
||||
const globalStore = getGlobalStore();
|
||||
if (!globalStore.isXpackEE) {
|
||||
const res = await getMasterLicenseStatus();
|
||||
if (!res || !res.data) {
|
||||
@@ -120,6 +126,7 @@ export async function loadMasterProductProFromDB() {
|
||||
}
|
||||
|
||||
export async function getXpackSettingForTheme() {
|
||||
const globalStore = getGlobalStore();
|
||||
const res2 = await searchXpackSetting();
|
||||
if (res2) {
|
||||
globalStore.themeConfig.title = res2.data?.title;
|
||||
|
||||
@@ -244,8 +244,8 @@
|
||||
baseInfo.prettyDistro
|
||||
? baseInfo.prettyDistro
|
||||
: baseInfo.platformVersion
|
||||
? baseInfo.platform + '-' + baseInfo.platformVersion
|
||||
: baseInfo.platform
|
||||
? baseInfo.platform + '-' + baseInfo.platformVersion
|
||||
: baseInfo.platform
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item
|
||||
@@ -865,8 +865,8 @@ const handleCopy = () => {
|
||||
(baseInfo.value.prettyDistro
|
||||
? baseInfo.value.prettyDistro
|
||||
: baseInfo.value.platformVersion
|
||||
? baseInfo.value.platform + '-' + baseInfo.value.platformVersion
|
||||
: baseInfo.value.platform) +
|
||||
? baseInfo.value.platform + '-' + baseInfo.value.platformVersion
|
||||
: baseInfo.value.platform) +
|
||||
'\n' +
|
||||
i18n.global.t('home.kernelVersion') +
|
||||
': ' +
|
||||
|
||||
@@ -256,6 +256,7 @@ import { getXpackSettingForTheme } from '@/utils/xpack';
|
||||
import { routerToName } from '@/utils/router';
|
||||
import { Key } from '@element-plus/icons-vue';
|
||||
import { changeToLocal } from '@/utils/node';
|
||||
import { syncAuthInfo } from '@/utils/rbac';
|
||||
|
||||
const i18n = useI18n();
|
||||
const themeConfig = computed(() => globalStore.themeConfig);
|
||||
@@ -448,6 +449,7 @@ const login = (formEl: FormInstance | undefined) => {
|
||||
menuStore.setMenuList([]);
|
||||
tabsStore.removeAllTabs();
|
||||
globalStore.isAdmin = res.data.role === 'ADMIN';
|
||||
await syncAuthInfo();
|
||||
await changeToLocal();
|
||||
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
|
||||
localStorage.removeItem('dashboardCache');
|
||||
@@ -493,6 +495,7 @@ const mfaLogin = async (auto: boolean) => {
|
||||
tabsStore.removeAllTabs();
|
||||
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
|
||||
globalStore.isAdmin = res.data.role === 'ADMIN';
|
||||
await syncAuthInfo();
|
||||
await changeToLocal();
|
||||
localStorage.removeItem('dashboardCache');
|
||||
localStorage.removeItem('upgradeChecked');
|
||||
@@ -551,13 +554,15 @@ const passkeyLogin = async () => {
|
||||
return;
|
||||
}
|
||||
const payload = buildPasskeyAssertion(credential);
|
||||
await passkeyFinishApi(payload, res.data.sessionId);
|
||||
const loginRes = await passkeyFinishApi(payload, res.data.sessionId);
|
||||
enableAutoPasskey();
|
||||
globalStore.ignoreCaptcha = true;
|
||||
globalStore.isLogin = true;
|
||||
globalStore.agreeLicense = true;
|
||||
menuStore.setMenuList([]);
|
||||
tabsStore.removeAllTabs();
|
||||
globalStore.isAdmin = loginRes.data.role === 'ADMIN';
|
||||
await syncAuthInfo();
|
||||
await changeToLocal();
|
||||
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
|
||||
localStorage.removeItem('dashboardCache');
|
||||
|
||||
@@ -8,52 +8,55 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { hasPermission } from '@/utils/rbac';
|
||||
const { isOffLine, isFxplay, isAdmin, isXpackEE } = useGlobalStore();
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('setting.panel'),
|
||||
path: '/settings/panel',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.safe'),
|
||||
path: '/settings/safe',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('xpack.alert.alertNotice'),
|
||||
path: '/settings/alert',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.backupAccount', 2),
|
||||
path: '/settings/backupaccount',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.snapshot', 2),
|
||||
path: '/settings/snapshot',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.license'),
|
||||
path: isXpackEE.value ? '/xpack-ee/license' : '/settings/license',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.about'),
|
||||
path: '/settings/about',
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
if (isOffLine.value) {
|
||||
buttons.splice(5, 1);
|
||||
}
|
||||
if (isFxplay.value) {
|
||||
buttons.splice(6, 1);
|
||||
}
|
||||
if (isXpackEE.value && !isAdmin.value) {
|
||||
buttons.splice(0, 1);
|
||||
buttons.splice(0, 1);
|
||||
buttons.splice(3, 1);
|
||||
}
|
||||
const buttons = computed(() => {
|
||||
const items = [
|
||||
...(isAdmin.value || hasPermission('setting_view')
|
||||
? [
|
||||
{
|
||||
label: i18n.global.t('setting.panel'),
|
||||
path: '/settings/panel',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.safe'),
|
||||
path: '/settings/safe',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('xpack.alert.alertNotice'),
|
||||
path: '/settings/alert',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.backupAccount', 2),
|
||||
path: '/settings/backupaccount',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('setting.snapshot', 2),
|
||||
path: '/settings/snapshot',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(isOffLine.value || !(isAdmin.value || hasPermission('setting_view'))
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: i18n.global.t('setting.license'),
|
||||
path: isXpackEE.value ? '/xpack-ee/license' : '/settings/license',
|
||||
},
|
||||
]),
|
||||
...(isFxplay.value
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: i18n.global.t('setting.about'),
|
||||
path: '/settings/about',
|
||||
},
|
||||
]),
|
||||
];
|
||||
return items;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -252,6 +252,7 @@ import ThemeColor from '@/views/setting/panel/theme-color/index.vue';
|
||||
import ApiInterface from '@/views/setting/panel/api-interface/index.vue';
|
||||
import Password from '@/views/setting/panel/password/index.vue';
|
||||
import Watermark from '@/views/setting/panel/watermark/index.vue';
|
||||
import Edition from '@/views/setting/panel/edition/index.vue';
|
||||
import UserName from '@/views/setting/panel/username/index.vue';
|
||||
import Timeout from '@/views/setting/panel/timeout/index.vue';
|
||||
import PanelName from '@/views/setting/panel/name/index.vue';
|
||||
|
||||
Reference in New Issue
Block a user