fix: serve frontend routes with fallback (#12752)

* feat: change isProductPro logic (#12712)

* fix: tighten frontend RBAC permission guards (#12717)

* fix: tighten frontend RBAC permission guards

* feat: add permission directive coverage

* refactor frontend global store usage

* serve frontend routes with fallback

---------

Co-authored-by: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
This commit is contained in:
ssongliu
2026-05-21 12:39:49 +08:00
committed by zhengkunwang223
co-authored by CityFun
parent 3a5d5bc6d7
commit d57c998047
33 changed files with 362 additions and 46 deletions
+45
View File
@@ -805,6 +805,51 @@
"formatZH": "更新快捷命令 [name]",
"formatEN": "update quick command [name]"
},
"/core/enterprise/user": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "添加用户 [name]",
"formatEN": "add user [name]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
"ids"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "ids",
"isList": true,
"db": "users",
"output_column": "name",
"output_value": "names"
}
],
"formatZH": "删除用户 [names]",
"formatEN": "delete user [names]"
},
"/core/enterprise/users/info/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 [name] 认证信息",
"formatEN": "update user [name] auth info"
},
"/core/enterprise/users/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
+2 -2
View File
@@ -55,10 +55,10 @@ func RepoURL() string {
}
func ResourceURL() string {
if CONF.Base.IsEnterprise {
return "https://resource.fit2cloud.com/1panel/resource/enterprise"
return "https://resource.fit2cloud.com/1panel/resource/v2"
}
if CONF.Base.IsFxplay {
return "https://resource.fit2cloud.com/1panel/resource/fusionxplay"
return "https://resource.fit2cloud.com/1panel/resource/v2"
}
if CONF.Base.Edition != "intl" {
return "https://resource.fit2cloud.com/1panel/resource/v2"
+2 -2
View File
@@ -109,7 +109,7 @@ func loadRestorePath(upgradeDir string) (string, error) {
}
func downloadLangFromRemote(fileOp files.FileOp) {
path := fmt.Sprintf("%s/language/lang.tar.gz", global.RepoURL())
path := fmt.Sprintf("%s/language/lang.tar.gz", global.ResourceURL())
if err := fileOp.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil {
global.LOG.Errorf("download lang.tar.gz failed, err: %v", err)
return
@@ -127,7 +127,7 @@ func downloadLangFromRemote(fileOp files.FileOp) {
}
func downloadGeoFromRemote(fileOp files.FileOp, targetPath string) {
_ = os.MkdirAll(path.Dir(targetPath), os.ModePerm)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.RepoURL())
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.ResourceURL())
if err := fileOp.DownloadFile(pathItem, targetPath); err != nil {
global.LOG.Errorf("download geo ip failed, err: %v", err)
return
+78 -3
View File
@@ -14155,6 +14155,41 @@ const docTemplate = `{
}
}
},
"/files/decompress/stop": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileDeCompressStopReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Stop decompress task",
"tags": [
"File"
]
}
},
"/files/del": {
"post": {
"consumes": [
@@ -20162,6 +20197,30 @@ const docTemplate = `{
}
}
},
"/settings/website/dir": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Load website dir",
"tags": [
"System Setting"
]
}
},
"/toolbox/clam": {
"post": {
"consumes": [
@@ -34334,6 +34393,9 @@ const docTemplate = `{
"proxyType": {
"type": "string"
},
"scriptSync": {
"type": "string"
},
"securityEntrance": {
"type": "string"
},
@@ -36577,6 +36639,9 @@ const docTemplate = `{
"secret": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
}
@@ -36588,6 +36653,17 @@ const docTemplate = `{
],
"type": "object"
},
"request.FileDeCompressStopReq": {
"properties": {
"taskID": {
"type": "string"
}
},
"required": [
"taskID"
],
"type": "object"
},
"request.FileDelete": {
"properties": {
"forceDelete": {
@@ -36877,8 +36953,7 @@ const docTemplate = `{
},
"required": [
"page",
"pageSize",
"type"
"pageSize"
],
"type": "object"
},
@@ -41850,4 +41925,4 @@ var SwaggerInfo = &swag.Spec{
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
}
+78 -3
View File
@@ -14151,6 +14151,41 @@
}
}
},
"/files/decompress/stop": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/request.FileDeCompressStopReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Stop decompress task",
"tags": [
"File"
]
}
},
"/files/del": {
"post": {
"consumes": [
@@ -20158,6 +20193,30 @@
}
}
},
"/settings/website/dir": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Load website dir",
"tags": [
"System Setting"
]
}
},
"/toolbox/clam": {
"post": {
"consumes": [
@@ -34330,6 +34389,9 @@
"proxyType": {
"type": "string"
},
"scriptSync": {
"type": "string"
},
"securityEntrance": {
"type": "string"
},
@@ -36573,6 +36635,9 @@
"secret": {
"type": "string"
},
"taskID": {
"type": "string"
},
"type": {
"type": "string"
}
@@ -36584,6 +36649,17 @@
],
"type": "object"
},
"request.FileDeCompressStopReq": {
"properties": {
"taskID": {
"type": "string"
}
},
"required": [
"taskID"
],
"type": "object"
},
"request.FileDelete": {
"properties": {
"forceDelete": {
@@ -36873,8 +36949,7 @@
},
"required": [
"page",
"pageSize",
"type"
"pageSize"
],
"type": "object"
},
@@ -41829,4 +41904,4 @@
"type": "object"
}
}
}
}
+3 -1
View File
@@ -22,6 +22,8 @@ func TestGenerateXlog(t *testing.T) {
filepath.Join(workDir, "core/app/api/v2"),
filepath.Join(workDir, "agent/xpack/app/api/v2"),
filepath.Join(workDir, "core/xpack/app/api/v2"),
filepath.Join(workDir, "agent/enterprise/app/api/v2"),
filepath.Join(workDir, "core/enterprise/app/api/v2"),
}
xlogMap := make(map[string]operationJson)
@@ -89,7 +91,7 @@ func TestGenerateSwaggerDoc(t *testing.T) {
cmd2.Dir = workDir
std2, err := cmd2.CombinedOutput()
if err != nil {
fmt.Printf("generate swagger doc of core failed, std1: %v, err: %v", string(std2), err)
fmt.Printf("generate swagger doc of core failed, std2: %v, err: %v", string(std2), err)
return
}
+45
View File
@@ -805,6 +805,51 @@
"formatZH": "更新快捷命令 [name]",
"formatEN": "update quick command [name]"
},
"/core/enterprise/user": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "添加用户 [name]",
"formatEN": "add user [name]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
"ids"
],
"paramKeys": [],
"beforeFunctions": [
{
"input_column": "id",
"input_value": "ids",
"isList": true,
"db": "users",
"output_column": "name",
"output_value": "names"
}
],
"formatZH": "删除用户 [names]",
"formatEN": "delete user [names]"
},
"/core/enterprise/users/info/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 [name] 认证信息",
"formatEN": "update user [name] auth info"
},
"/core/enterprise/users/update": {
"bodyKeys": [
"name"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 [name]",
"formatEN": "update user [name]"
},
"/core/groups": {
"bodyKeys": [
"name",
+2 -2
View File
@@ -108,7 +108,7 @@ func loadRestorePath(upgradeDir string) (string, error) {
}
func downloadLangFromRemote() {
path := fmt.Sprintf("%s/language/lang.tar.gz", global.RepoURL())
path := fmt.Sprintf("%s/language/lang.tar.gz", global.ResourceURL())
if err := fileUtils.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil {
global.LOG.Errorf("download lang.tar.gz failed, err: %v", err)
return
@@ -126,7 +126,7 @@ func downloadLangFromRemote() {
}
func downloadGeoFromRemote(targetPath string) {
_ = os.MkdirAll(path.Dir(targetPath), os.ModePerm)
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.RepoURL())
pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.ResourceURL())
if err := fileUtils.DownloadFile(pathItem, targetPath); err != nil {
global.LOG.Errorf("download geo ip failed, err: %v", err)
return
+1
View File
@@ -84,6 +84,7 @@ func Routers() *gin.Engine {
Router.Use(middleware.DemoHandle())
}
Router.Use(middleware.FrontendFallback())
Router.Use(middleware.OperationLog())
Router.Use(middleware.GlobalLoading())
Router.Use(xpack.AuthProvider.CoreAPIAuthMiddleware())
+44
View File
@@ -0,0 +1,44 @@
package middleware
import (
"net/http"
"strings"
"github.com/1Panel-dev/1Panel/core/utils/security"
"github.com/gin-gonic/gin"
)
func FrontendFallback() gin.HandlerFunc {
return func(c *gin.Context) {
if !isFrontendFallbackRequest(c) {
c.Next()
return
}
if security.CanServeFrontendPath(c) {
security.ToIndexHtml(c)
c.Abort()
return
}
security.HandleNotSecurity(c, "")
c.Abort()
}
}
func isFrontendFallbackRequest(c *gin.Context) bool {
if c.Request.Method != http.MethodGet && c.Request.Method != http.MethodHead {
return false
}
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api/") ||
strings.HasPrefix(path, "/assets/") ||
strings.HasPrefix(path, "/public") ||
strings.HasPrefix(path, "/1panel/swagger/") ||
path == "/favicon.ico" {
return false
}
if !security.IsFrontendPath(path) {
return false
}
accept := c.GetHeader("Accept")
return accept == "" || strings.Contains(accept, "text/html")
}
+6 -6
View File
@@ -29,7 +29,7 @@ func HandleNotRoute(c *gin.Context) bool {
HandleNotSecurity(c, "err_ip_limit")
return false
}
if checkFrontendPath(c) {
if CanServeFrontendPath(c) {
ToIndexHtml(c)
return false
}
@@ -122,8 +122,8 @@ func HandleNotSecurity(c *gin.Context, resType string) {
c.Data(statusCode, "text/html; charset=utf-8", data)
}
func isFrontendPath(c *gin.Context) bool {
reqUri := strings.TrimSuffix(c.Request.URL.Path, "/")
func IsFrontendPath(path string) bool {
reqUri := strings.TrimSuffix(path, "/")
if _, ok := constant.WebUrlMap[reqUri]; ok {
return true
}
@@ -135,8 +135,8 @@ func isFrontendPath(c *gin.Context) bool {
return false
}
func checkFrontendPath(c *gin.Context) bool {
if !isFrontendPath(c) {
func CanServeFrontendPath(c *gin.Context) bool {
if !IsFrontendPath(c.Request.URL.Path) {
return false
}
if isPublicFileSharePagePath(c.Request.URL.Path) {
@@ -144,7 +144,7 @@ func checkFrontendPath(c *gin.Context) bool {
}
authService := service.NewIAuthService()
if authService.GetSecurityEntrance() != "" {
return authService.IsLogin(c)
return checkEntrance(c) || authService.IsLogin(c)
}
return true
}
+4 -4
View File
@@ -1,10 +1,10 @@
import i18n from '@/lang';
import router from '@/routers';
import { MsgError } from '@/utils/message';
import { GlobalStore } from '@/store';
import { useGlobalStore } from '@/composables/useGlobalStore';
export const checkStatus = (status: number, msg: string): void => {
const globalStore = GlobalStore();
const { entrance, isLogin } = useGlobalStore();
switch (status) {
case 400:
MsgError(msg ? msg : i18n.global.t('commons.res.paramError'));
@@ -13,8 +13,8 @@ export const checkStatus = (status: number, msg: string): void => {
MsgError(msg ? msg : i18n.global.t('commons.res.notFound'));
break;
case 403:
globalStore.isLogin = false;
router.replace({ name: 'entrance', params: { code: globalStore.entrance } });
isLogin.value = false;
router.replace({ name: 'entrance', params: { code: entrance.value } });
MsgError(msg ? msg : i18n.global.t('commons.res.forbidden'));
break;
case 500:
+1 -1
View File
@@ -3,13 +3,13 @@ import { ResultData } from '@/api/interface';
import { ResultEnum } from '@/enums/http-enum';
import { checkStatus } from './helper/check-status';
import router from '@/routers';
import { GlobalStore } from '@/store';
import { MsgError } from '@/utils/message';
import { encodeBase64 } from '@/utils/base64';
import i18n from '@/lang';
import { changeToLocal } from '@/utils/node';
import { getCookie } from '@/utils/auth';
import { handleAuthResponseCode } from '@/utils/auth-response';
import { GlobalStore } from '@/store';
const config = {
baseURL: import.meta.env.VITE_API_URL as string,
@@ -107,7 +107,6 @@ const props = defineProps({
},
});
const emit = defineEmits(['search', 'update:selects', 'update:paginationConfig']);
const tableRef = ref();
const tableHeight = ref<number | string>('');
const menuRef = ref<HTMLElement | null>(null);
+2
View File
@@ -4145,6 +4145,8 @@ const message = {
nodeAdminMasterForbidden: 'Node Admin permissions cannot be added to the master node',
permissionLinkageTip:
'Related permissions are selected automatically when dependencies exist; after manual removal, some features may show "Current user has no permission".',
allViewPermissionHelper: 'Grants all view permissions for AI Gateway.',
apiKeyViewPermissionHelper: 'Only allows viewing AI Gateway API Keys.',
view: 'View',
manage: 'Manage',
},
+2
View File
@@ -4199,6 +4199,8 @@ const message = {
nodeAdminMasterForbidden: 'No se pueden agregar permisos de administrador de nodo al nodo principal',
permissionLinkageTip:
'Si hay dependencias, los permisos relacionados se seleccionarán automáticamente; tras quitarlos manualmente, algunas funciones pueden mostrar "El usuario actual no tiene permiso".',
allViewPermissionHelper: 'Concede todos los permisos de visualización para AI Gateway.',
apiKeyViewPermissionHelper: 'Solo permite ver las API Keys de AI Gateway.',
view: 'Ver',
manage: 'Gestionar',
},
+2
View File
@@ -4181,6 +4181,8 @@ const message = {
nodeAdminMasterForbidden: 'マスターノードにノード管理者権限を追加することはできません',
permissionLinkageTip:
'依存関係がある場合、関連権限は自動選択されます。手動で解除すると、一部機能で「現在のユーザーには権限がありません」と表示される場合があります。',
allViewPermissionHelper: 'AI ゲートウェイのすべての表示権限を付与します。',
apiKeyViewPermissionHelper: 'AI ゲートウェイの API Key のみ表示できます。',
view: '表示',
manage: '管理',
},
+2
View File
@@ -4097,6 +4097,8 @@ const message = {
nodeAdminMasterForbidden: '마스터 노드에는 노드 관리자 권한을 추가할 수 없습니다',
permissionLinkageTip:
'의존 관계가 있으면 관련 권한이 자동 선택되며, 수동으로 해제하면 일부 기능에서 "현재 사용자에게 권한이 없습니다"가 표시될 수 있습니다.',
allViewPermissionHelper: 'AI 게이트웨이의 모든 보기 권한을 부여합니다.',
apiKeyViewPermissionHelper: 'AI 게이트웨이 API Key만 볼 수 있습니다.',
view: '보기',
manage: '관리',
},
+2
View File
@@ -4238,6 +4238,8 @@ const message = {
nodeAdminMasterForbidden: 'Kebenaran Pentadbir Nod tidak boleh ditambah pada nod utama',
permissionLinkageTip:
'Kebenaran berkaitan akan dipilih automatik jika ada kebergantungan; selepas dialih keluar manual, sesetengah ciri mungkin memaparkan "Pengguna semasa tiada kebenaran".',
allViewPermissionHelper: 'Memberikan semua kebenaran paparan untuk AI Gateway.',
apiKeyViewPermissionHelper: 'Hanya membenarkan paparan API Key AI Gateway.',
view: 'Lihat',
manage: 'Urus',
},
+2
View File
@@ -4377,6 +4377,8 @@ const message = {
nodeAdminMasterForbidden: 'Permissões de administrador de nó não podem ser adicionadas ao nó principal',
permissionLinkageTip:
'Permissões relacionadas serão selecionadas automaticamente quando houver dependências; após removê-las manualmente, alguns recursos podem mostrar "O usuário atual não tem permissão".',
allViewPermissionHelper: 'Concede todas as permissões de visualização do AI Gateway.',
apiKeyViewPermissionHelper: 'Permite visualizar apenas as API Keys do AI Gateway.',
view: 'Visualizar',
manage: 'Gerenciar',
},
+2
View File
@@ -4232,6 +4232,8 @@ const message = {
nodeAdminMasterForbidden: 'Права администратора узла нельзя добавить к главному узлу',
permissionLinkageTip:
'При наличии зависимостей связанные права выбираются автоматически; после ручного снятия некоторые функции могут показать "У текущего пользователя нет разрешения".',
allViewPermissionHelper: 'Предоставляет все права просмотра для AI Gateway.',
apiKeyViewPermissionHelper: 'Разрешает просматривать только API Key AI Gateway.',
view: 'Просмотр',
manage: 'Управление',
},
+2
View File
@@ -4231,6 +4231,8 @@ const message = {
nodeAdminMasterForbidden: 'Ana düğüme düğüm yöneticisi izni eklenemez',
permissionLinkageTip:
'Bağımlılık varsa ilişkili izinler otomatik seçilir; manuel kaldırıldıktan sonra bazı özelliklerde "Geçerli kullanıcının izni yok" gösterilebilir.',
allViewPermissionHelper: 'AI Gateway için tüm görüntüleme izinlerini verir.',
apiKeyViewPermissionHelper: 'Yalnızca AI Gateway API Key görüntülemeye izin verir.',
view: 'Görüntüle',
manage: 'Yönet',
},
+2
View File
@@ -3875,6 +3875,8 @@ const message = {
permissionDuplicate: '每個節點只能新增一種角色',
nodeAdminMasterForbidden: '主節點不允許新增節點管理員權限',
permissionLinkageTip: '存在依賴時將自動勾選關聯權限,手動取消後部分功能可能提示「目前使用者無權限」。',
allViewPermissionHelper: '擁有 AI 閘道的所有檢視權限。',
apiKeyViewPermissionHelper: '僅能檢視 AI 閘道 API Key。',
view: '檢視',
manage: '管理',
},
+2
View File
@@ -4463,6 +4463,8 @@ const message = {
permissionDuplicate: '每个节点只能添加一种角色',
nodeAdminMasterForbidden: '主节点不允许添加节点管理员权限',
permissionLinkageTip: '存在依赖时将自动勾选关联权限,手动取消后部分功能可能提示“当前用户无权限”。',
allViewPermissionHelper: '拥有 AI 网关的所有查看权限。',
apiKeyViewPermissionHelper: '仅能查看 AI 网关 API Key。',
view: '查看',
manage: '管理',
},
-1
View File
@@ -9,7 +9,6 @@ const webSiteRouter = {
meta: {
icon: 'p-website',
title: 'menu.website',
permission: 'website_view',
},
children: [
{
+4 -4
View File
@@ -1,7 +1,7 @@
import { ResultEnum } from '@/enums/http-enum';
import i18n from '@/lang';
import router from '@/routers';
import { GlobalStore } from '@/store';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { MsgError } from '@/utils/message';
export type AuthResponseAction = 'reject' | 'return';
@@ -19,11 +19,11 @@ interface AuthResponseOptions {
const forbiddenMessage = () => i18n.global.t('commons.res.forbidden');
export const redirectToEntrance = () => {
const globalStore = GlobalStore();
globalStore.isLogin = false;
const { entrance, isLogin } = useGlobalStore();
isLogin.value = false;
router.push({
name: 'entrance',
params: { code: globalStore.entrance },
params: { code: entrance.value },
});
};
+5 -5
View File
@@ -1,6 +1,6 @@
import { getUserInfo } from '@/api/modules/auth';
import { getEnterpriseUserInfo } from '@/extensions/xpack';
import { GlobalStore } from '@/store';
import { useGlobalStore } from '@/composables/useGlobalStore';
import type { RouteMeta } from 'vue-router';
type RouteAccessMeta = {
@@ -17,12 +17,12 @@ type RouteAccessTarget = {
};
export const syncAuthInfo = async (currentNode?: string) => {
const globalStore = GlobalStore();
if (!globalStore.isEnterprise) {
const { globalStore, currentNode: storeCurrentNode, isEnterprise } = useGlobalStore();
if (!isEnterprise.value) {
const res = await getUserInfo();
return res.data;
}
const res = await getEnterpriseUserInfo(currentNode ?? globalStore.currentNode);
const res = await getEnterpriseUserInfo(currentNode ?? storeCurrentNode.value);
globalStore.setAuthInfo({
isAdmin: res.data.role === 'ADMIN',
permissions: res.data.permissions || [],
@@ -32,7 +32,7 @@ export const syncAuthInfo = async (currentNode?: string) => {
};
export const hasPermission = (permission: string) => {
return GlobalStore().hasPermission(permission);
return useGlobalStore().globalStore.hasPermission(permission);
};
export const hasRouteRoleAccess = (meta?: RouteMeta & RouteAccessMeta) => {
+4 -4
View File
@@ -1,16 +1,16 @@
import { GlobalStore } from '@/store';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { handleAuthResponseCode, handleAuthResponseStatus } from '@/utils/auth-response';
export const checkStreamAuth = async (url: string, currentNode?: string) => {
const globalStore = GlobalStore();
const { currentNode: storeCurrentNode, language } = useGlobalStore();
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url.replace(/^ws/, 'http'), {
credentials: 'include',
headers: {
'Accept-Language': globalStore.language,
CurrentNode: encodeURIComponent(currentNode || globalStore.currentNode),
'Accept-Language': language.value,
CurrentNode: encodeURIComponent(currentNode || storeCurrentNode.value),
},
signal: controller.signal,
});
+2 -2
View File
@@ -136,7 +136,7 @@ export async function loadMasterProductProFromDB() {
globalStore.isProductPro = res.data.status === 'Bound';
}
}
switchTheme();
useTheme().switchTheme();
initFavicon();
loadDataFromDB();
}
@@ -167,7 +167,7 @@ export async function getXpackSettingForTheme() {
} else {
resetXSetting();
}
switchTheme();
useTheme().switchTheme();
initFavicon();
}
@@ -44,6 +44,7 @@
import { reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { useMenuManagePermission } from '@/composables/useMenuManagePermission';
import { useI18n } from 'vue-i18n';
import { AI } from '@/api/interface/ai';
import { approveAgentChannelPairing, getAgentDiscordConfig, updateAgentDiscordConfig } from '@/api/modules/ai';
@@ -78,6 +78,7 @@
import { reactive, ref } from 'vue';
import type { FormInstance } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { useMenuManagePermission } from '@/composables/useMenuManagePermission';
import { useI18n } from 'vue-i18n';
import { AI } from '@/api/interface/ai';
import { approveAgentChannelPairing, getAgentTelegramConfig, updateAgentTelegramConfig } from '@/api/modules/ai';
@@ -999,10 +999,6 @@ const paginationConfig = reactive({
total: 0,
});
const mobile = computed(() => {
return globalStore.isMobile();
});
const btnWrapperRefs = ref<Record<string, any>>({});
const setBtnWrapperRef = (key: string, el: any) => {
@@ -1169,7 +1165,7 @@ const loadInitialExistingPath = async (url: string) => {
sortOrder: oldSortOrder,
showHidden,
});
globalStore.lastFilePath = req.path;
lastFilePath.value = req.path;
getPaths(req.path);
updateTab(req.path);
paths.value = buildPaths(req.path);
@@ -215,6 +215,19 @@ const {
watermarkShow,
} = useGlobalStore();
const {
globalStore,
isEnterprise,
isIntl,
isMobile,
isOffline,
isXpackOrEE,
openMenuTabs,
themeConfig,
watermark,
watermarkShow,
} = useGlobalStore();
const loading = ref(false);
const { switchTheme } = useTheme();