diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go index 4a140473b..fd82393a0 100644 --- a/backend/app/dto/setting.go +++ b/backend/app/dto/setting.go @@ -54,6 +54,7 @@ type SettingInfo struct { FileRecycleBin string `json:"fileRecycleBin"` SnapshotIgnore string `json:"snapshotIgnore"` + XpackHideMenu string `json:"xpackHideMenu"` } type SettingUpdate struct { diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index daa868482..45d86a373 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -75,6 +75,7 @@ func Init() { migrations.AddSnapshotIgnore, migrations.AddDatabaseIsDelete, + migrations.AddXpackHideMenu, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_10.go b/backend/init/migration/migrations/v_1_10.go index fd07918b7..8ab8041f0 100644 --- a/backend/init/migration/migrations/v_1_10.go +++ b/backend/init/migration/migrations/v_1_10.go @@ -25,3 +25,13 @@ var AddDatabaseIsDelete = &gormigrate.Migration{ return nil }, } + +var AddXpackHideMenu = &gormigrate.Migration{ + ID: "20240328-add-xpack-hide-menu", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "XpackHideMenu", Value: "{\"id\":\"1\",\"label\":\"/xpack\",\"isCheck\":false,\"title\":\"xpack.menu\",\"children\":[{\"id\":\"2\",\"title\":\"xpack.waf.name\",\"path\":\"/xpack/waf/dashboard\",\"label\":\"Dashboard\",\"isCheck\":false},{\"id\":\"3\",\"title\":\"xpack.tamper.tamper\",\"path\":\"/xpack/tamper\",\"label\":\"Tamper\",\"isCheck\":true},{\"id\":\"4\",\"title\":\"xpack.setting.setting\",\"path\":\"/xpack/setting\",\"label\":\"XSetting\",\"isCheck\":true}]}"}).Error; err != nil { + return err + } + return nil + }, +} diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index 4ef59512c..e31434d29 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -46,6 +46,7 @@ export namespace Setting { weChatVars: string; dingVars: string; snapshotIgnore: string; + xpackHideMenu: string; } export interface SettingUpdate { key: string; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index a4f4c5563..9e4c2f824 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1457,6 +1457,13 @@ const message = { currentVersion: 'Version', license: 'License', + advancedMenuShow: 'Advanced Menu Display', + showMainAdvancedMenu: + 'If only one menu is retained, only the main advanced menu will be displayed in the sidebar', + showAll: 'Show All', + ifShow: 'Whether to Show', + menu: 'Menu', + confirmMessage: 'The page will be refreshed to update the advanced menu list. Continue?', }, license: { community: 'Community Edition', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 0e19ca448..166019e2e 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1357,6 +1357,12 @@ const message = { currentVersion: '當前運行版本:', license: '許可證', + advancedMenuShow: '高級功能選單顯示', + showMainAdvancedMenu: '如果只保留 1 個選單,則側邊欄只會顯示高級功能主選單', + showAll: '全部顯示', + ifShow: '是否顯示', + menu: '選單', + confirmMessage: '即將刷新頁面更新高級功能菜單列表,是否繼續?', }, license: { community: '社區版', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index b0c8ed1a8..3440552fb 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1359,6 +1359,12 @@ const message = { currentVersion: '当前运行版本:', license: '许可证', + advancedMenuShow: '高级功能菜单显示', + showMainAdvancedMenu: '如果只保留 1 个菜单,则侧边栏只会显示高级功能主菜单', + showAll: '全部显示', + ifShow: '是否显示', + menu: '菜单', + confirmMessage: '即将刷新页面更新高级功能菜单列表,是否继续?', }, license: { community: '社区版', diff --git a/frontend/src/layout/components/Sidebar/index.vue b/frontend/src/layout/components/Sidebar/index.vue index 463bbee93..919fa641f 100644 --- a/frontend/src/layout/components/Sidebar/index.vue +++ b/frontend/src/layout/components/Sidebar/index.vue @@ -44,6 +44,8 @@ import { ElMessageBox } from 'element-plus'; import { GlobalStore, MenuStore } from '@/store'; import { MsgSuccess } from '@/utils/message'; import { isString } from '@vueuse/core'; +import { getSettingInfo } from '@/api/modules/setting'; + const route = useRoute(); const menuStore = MenuStore(); const globalStore = GlobalStore(); @@ -53,9 +55,20 @@ const activeMenu = computed(() => { }); const isCollapse = computed((): boolean => menuStore.isCollapse); -const routerMenus = computed((): RouteRecordRaw[] => menuStore.menuList); +let routerMenus = computed((): RouteRecordRaw[] => { + return menuStore.menuList.filter((route) => route.meta && !route.meta.hideInSidebar); +}); const screenWidth = ref(0); + +interface Node { + id: string; + title: string; + path?: string; + label: string; + isCheck: boolean; + children?: Node[]; +} const listeningWindow = () => { window.onresize = () => { return (() => { @@ -85,8 +98,65 @@ const logout = () => { const systemLogOut = async () => { await logOutApi(); }; + +function extractLabels(node: Node, result: string[]): void { + // 未勾选的才隐藏 + if (node.isCheck) { + result.push(node.label); + } + if (node.children) { + for (const childNode of node.children) { + extractLabels(childNode, result); + } + } +} + +function getCheckedLabels(json: Node): string[] { + let result: string[] = []; + extractLabels(json, result); + return result; +} + +const search = async () => { + const res = await getSettingInfo(); + const json: Node = JSON.parse(res.data.xpackHideMenu); + const checkedLabels = getCheckedLabels(json); + let rstMenuList: RouteRecordRaw[] = []; + menuStore.menuList.forEach((item) => { + let menuItem = JSON.parse(JSON.stringify(item)); + let menuChildren: RouteRecordRaw[] = []; + if (menuItem.path === '/xpack') { + if (checkedLabels.length) { + menuItem.children.forEach((child: any) => { + for (const str of checkedLabels) { + if (child.name === str) { + child.hidden = false; + } + } + if (child.hidden === false) { + menuChildren.push(child); + } + }); + menuItem.meta.hideInSidebar = false; + } + menuItem.children = menuChildren as RouteRecordRaw[]; + rstMenuList.push(menuItem); + } else { + menuItem.children.forEach((child: any) => { + if (child.hidden == undefined || child.hidden == false) { + menuChildren.push(child); + } + }); + menuItem.children = menuChildren as RouteRecordRaw[]; + rstMenuList.push(menuItem); + } + }); + menuStore.menuList = rstMenuList; +}; + onMounted(() => { menuStore.setMenuList(menuList); + search(); }); diff --git a/frontend/src/routers/router.ts b/frontend/src/routers/router.ts index a65341ba4..62f37706c 100644 --- a/frontend/src/routers/router.ts +++ b/frontend/src/routers/router.ts @@ -48,7 +48,7 @@ rolesRoutes.forEach((item) => { let menuItem = JSON.parse(JSON.stringify(item)); let menuChildren: RouteRecordRaw[] = []; menuItem.children.forEach((child: any) => { - if (child.hidden == null || child.hidden == false) { + if (child.hidden == undefined || child.hidden == false) { menuChildren.push(child); } }); diff --git a/frontend/src/views/setting/panel/hidemenu/index.vue b/frontend/src/views/setting/panel/hidemenu/index.vue new file mode 100644 index 000000000..d0d41a114 --- /dev/null +++ b/frontend/src/views/setting/panel/hidemenu/index.vue @@ -0,0 +1,151 @@ + + + diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index f082fed32..b16804f56 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -101,6 +101,16 @@ + + + + + + @@ -113,6 +123,7 @@ + @@ -130,6 +141,7 @@ import Timeout from '@/views/setting/panel/timeout/index.vue'; import PanelName from '@/views/setting/panel/name/index.vue'; import SystemIP from '@/views/setting/panel/systemip/index.vue'; import Network from '@/views/setting/panel/default-network/index.vue'; +import HideMenu from '@/views/setting/panel/hidemenu/index.vue'; const loading = ref(false); const i18n = useI18n(); @@ -152,6 +164,9 @@ const form = reactive({ complexityVerification: '', defaultNetwork: '', defaultNetworkVal: '', + + proHideMenus: ref(i18n.t('setting.unSetting')), + hideMenuList: '', }); const show = ref(); @@ -162,8 +177,18 @@ const panelNameRef = ref(); const systemIPRef = ref(); const timeoutRef = ref(); const networkRef = ref(); +const hideMenuRef = ref(); const unset = ref(i18n.t('setting.unSetting')); +interface Node { + id: string; + title: string; + path?: string; + label: string; + isCheck: boolean; + children?: Node[]; +} + const search = async () => { const res = await getSettingInfo(); form.userName = res.data.userName; @@ -179,8 +204,39 @@ const search = async () => { form.complexityVerification = res.data.complexityVerification; form.defaultNetwork = res.data.defaultNetwork; form.defaultNetworkVal = res.data.defaultNetwork === 'all' ? i18n.t('commons.table.all') : res.data.defaultNetwork; + form.proHideMenus = res.data.xpackHideMenu; + form.hideMenuList = res.data.xpackHideMenu; + + // 提取隐藏节点的 title 并显示 + const json: Node = JSON.parse(res.data.xpackHideMenu); + const checkedTitles = getCheckedTitles(json); + form.proHideMenus = checkedTitles.toString(); }; +function extractTitles(node: Node, result: string[]): void { + if (node.isCheck && !node.children) { + result.push(i18n.t(node.title)); + } + if (node.children) { + for (const childNode of node.children) { + extractTitles(childNode, result); + } + } +} + +function getCheckedTitles(json: Node): string[] { + let result: string[] = []; + extractTitles(json, result); + if (result.length === 0) { + result.push(i18n.t('setting.unSetting')); + } + if (result.length === json.children.length) { + result = []; + result.push(i18n.t('setting.showAll')); + } + return result; +} + const onChangePassword = () => { passwordRef.value.acceptParams({ complexityVerification: form.complexityVerification }); }; @@ -200,6 +256,10 @@ const onChangeNetwork = () => { networkRef.value.acceptParams({ defaultNetwork: form.defaultNetwork }); }; +const onChangeHideMenus = () => { + hideMenuRef.value.acceptParams({ menuList: form.hideMenuList }); +}; + const onSave = async (key: string, val: any) => { loading.value = true; if (key === 'Language') {