feat: add xpackee permission-aware settings flow

This commit is contained in:
ssongliu
2026-05-21 11:34:08 +08:00
committed by zhengkunwang223
parent d9cedeca09
commit 69906046e8
55 changed files with 514 additions and 99 deletions
+19
View File
@@ -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()
+11
View File
@@ -0,0 +1,11 @@
//go:build xpackee
package server
import (
xpack "github.com/1Panel-dev/1Panel/agent/xpack"
)
func InitOthers() {
xpack.Init()
}
+74
View File
@@ -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()
}
+9
View File
@@ -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()
+5 -5
View File
@@ -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
}
+1 -1
View File
@@ -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
+15 -4
View File
@@ -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.
+2
View File
@@ -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!"
+2
View File
@@ -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"
+2
View File
@@ -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: "このバックアップアカウントが公開されていないと検出されました。再確認してください!"
+2
View File
@@ -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: "이 백업 계정이 공개된 것으로 감지되지 않았습니다. 다시 확인하십시오"
+2
View File
@@ -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"
+2
View File
@@ -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"
+2
View File
@@ -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: "Обнаружено, что эта учетная запись резервного копирования не является публичной, проверьте и повторите попытку"
+2
View File
@@ -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"
+2
View File
@@ -49,6 +49,8 @@ AppInstallCheck: '檢查應用安裝環境'
#backup
ErrBackupInUsed: "該備份帳號已在排程任務中使用,無法刪除"
ErrRolePresetCannotDelete: "系統預設角色,無法刪除"
ErrRoleBoundToUser: "角色已被使用者綁定,無法刪除"
ErrBackupCheck: "備份帳號測試連線失敗 {{ .err }}"
ErrBackupLocal: "本機伺服器備份帳號暫不支援該操作!"
ErrBackupPublic: "偵測到該備份帳號為非公用,請檢查後再試。"
+2
View File
@@ -55,6 +55,8 @@ AppInstallCheck: "检查应用安装环境"
#backup
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
ErrRolePresetCannotDelete: "系统预设角色,无法删除"
ErrRoleBoundToUser: "角色已被用户绑定,无法删除"
ErrBackupCheck: "备份账号测试连接失败 {{ .err }}"
ErrBackupLocal: "本地服务器备份账号暂不支持该操作!"
ErrBackupPublic: "检测到该备份账号为非公用,请检查后重试!"
+1 -2
View File
@@ -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"
)
+2
View File
@@ -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")
+2
View File
@@ -19,6 +19,8 @@ type SessionUser struct {
Name string `json:"name"`
}
const SuperAdminSessionUserID = "__super_admin__"
type sessionItem struct {
CreatedAt time.Time
CSRFToken string
+17 -1
View File
@@ -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 }
+2
View File
@@ -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 {
+5
View File
@@ -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 {
+1 -1
View File
@@ -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'));
+8 -1
View File
@@ -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;
+3
View File
@@ -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 -1
View File
@@ -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) => {
+3 -1
View File
@@ -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();
+1 -3
View File
@@ -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,
+1
View File
@@ -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>
+46 -6
View File
@@ -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;
}
+6
View File
@@ -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,
},
},
+8 -1
View File
@@ -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',
},
},
],
+2
View File
@@ -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',
},
},
],
+6
View File
@@ -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',
},
},
{
+7
View File
@@ -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',
},
},
{
+4
View File
@@ -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',
},
},
{
+7
View File
@@ -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',
},
},
{
+7
View File
@@ -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',
},
},
],
+8
View File
@@ -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',
},
},
],
+3
View File
@@ -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;
+35
View File
@@ -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 -1
View File
@@ -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':
+4 -1
View File
@@ -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';
};
+20
View File
@@ -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);
};
+8 -1
View File
@@ -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;
+4 -4
View File
@@ -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');
+46 -43
View File
@@ -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';