1+ <script setup lang="ts">
2+ import { getRouteName , useDashboard } from ' @/composables/dashboard' ;
3+ import { useMetrics } from ' @/composables/metrics' ;
4+ import { useRoute } from ' vue-router' ;
5+ import type { AppInfo } from ' @/types/dashboard' ;
6+ import { computed , onBeforeUnmount , onMounted , nextTick , useTemplateRef , type PropType } from ' vue' ;
7+ import { useRouter } from ' @/router' ;
8+
9+ const props = defineProps ({
10+ appInfo: { type: Object as PropType <AppInfo >, required: true },
11+ });
12+
13+ const { dashboard, getMode } = useDashboard ();
14+ const { sum } = useMetrics ();
15+ const tabsContainer = useTemplateRef <HTMLElement >(' tabsContainer' );
16+ const dropdownMenu = useTemplateRef <HTMLElement >(' dropdownMenu' );
17+ const moreTabs = useTemplateRef <HTMLElement >(' moreTabs' );
18+ const tabItems = computed (() => [
19+ { text: ' Overview' , isVisible: true , to: { name: getRouteName (' dashboard' ).value }, cssClass: ' overview' },
20+ { text: ' HTTP' , isVisible: isServiceAvailable (' http' ), to: { name: getRouteName (' http' ).value } },
21+ { text: ' Kafka' , isVisible: isServiceAvailable (' kafka' ), to: { name: getRouteName (' kafka' ).value } },
22+ { text: ' Mail' , isVisible: isServiceAvailable (' mail' ), to: { name: getRouteName (' mail' ).value } },
23+ { text: ' LDAP' , isVisible: isServiceAvailable (' ldap' ), to: { name: getRouteName (' ldap' ).value } },
24+ { text: ' Jobs' , isVisible: hasJobs .value , to: { name: getRouteName (' jobs' ).value } },
25+ { text: ' Configs' , isVisible: true , to: { name: getRouteName (' configs' ).value } },
26+ { text: ' Faker' , isVisible: getMode () === ' live' , to: { name: getRouteName (' tree' ).value } },
27+ { text: ' Search' , isVisible: props .appInfo .search .enabled , to: { name: getRouteName (' search' ).value } },
28+ ]);
29+ const route = useRoute ();
30+ const router = useRouter ();
31+ const tabActive = computed (() => tabItems .value .find (x => x .text !== ' Overview' && route .matched .some (r => r .name === x .to .name )) ?? tabItems .value [0 ])
32+
33+ const response = dashboard .value .getMetrics (' app' )
34+ const hasJobs = computed (() => {
35+ return sum (response .data , ' app_job_run_total' ) > 0
36+ })
37+
38+ const handleResize = () => updateTabsResponsiveness ();
39+
40+ onMounted (async () => {
41+ // Wait for Vue to finish rendering the DOM
42+ await nextTick ();
43+ window .addEventListener (" resize" , handleResize );
44+ updateTabsResponsiveness ();
45+ });
46+
47+ onBeforeUnmount (() => {
48+ window .removeEventListener (" resize" , handleResize );
49+ });
50+
51+ function isServiceAvailable(service : string ): boolean {
52+ if (! props .appInfo .activeServices ){
53+ return false
54+ }
55+ return props .appInfo .activeServices .includes (service )
56+ }
57+
58+ function updateTabsResponsiveness() {
59+ const container = tabsContainer .value ;
60+ const dropdown = dropdownMenu .value ;
61+ const more = moreTabs .value
62+
63+ if (! container || ! dropdown || ! more ) {
64+ return
65+ }
66+
67+ // Reset everything
68+ const allTabs = [... container .querySelectorAll (" li.nav-item:not(#moreTabs)" )];
69+ for (const tab of allTabs ) {
70+ tab .classList .remove (' d-none' );
71+ }
72+ for (const tab of [... dropdown .querySelectorAll (" li" )]) {
73+ tab .classList .add (' d-none' );
74+ }
75+ // Make sure it's measurable
76+ more .classList .remove (" d-none" );
77+ const moreWidth = more .offsetWidth ;
78+ more .classList .add (" d-none" );
79+
80+ const documentWidth = document .body .clientWidth * 0.9 ;
81+ let usedWidth = moreWidth ;
82+ let hidden = 0 ;
83+
84+ for (const [index, item] of allTabs .entries ()) {
85+ const tab = item as HTMLElement
86+
87+ // do not show the more dropdown only for one item
88+ if (index === (allTabs .length - 1 ) && hidden === 0 ) {
89+ break ;
90+ }
91+
92+ if (usedWidth + tab .offsetWidth > documentWidth ) {
93+ tab .classList .add (" d-none" );
94+ const dropItem = Array .from (dropdown .querySelectorAll (" li" ))
95+ .find (el => el .textContent .trim () === tab .innerText );
96+ if (dropItem ) {
97+ dropItem .classList .remove (" d-none" );
98+ }
99+ more .classList .remove (" d-none" );
100+ hidden ++ ;
101+ } else {
102+ usedWidth += tab .offsetWidth ;
103+ }
104+ }
105+
106+ const activeTab = allTabs .find (tab =>
107+ tab .querySelector (' .nav-link.router-link-exact-active' )
108+ );
109+ const moreLink = more .querySelector (' .nav-link' )! ;
110+ // If the active tab is hidden => it's in the dropdown
111+ if (moreLink && activeTab && activeTab .classList .contains (' d-none' )) {
112+ // Get the label text of the active tab
113+ const activeLabel = activeTab .querySelector (' .nav-link' )! .textContent .trim ();
114+ // Replace "More" text with active tab label
115+ moreLink .textContent = activeLabel ;
116+ moreLink .classList .add (' router-link-exact-active' )
117+
118+ } else {
119+ // Restore default
120+ moreLink .textContent = ' More' ;
121+ moreLink .classList .remove (' router-link-exact-active' )
122+ }
123+ };
124+ </script >
125+
126+ <template >
127+ <nav class =" navbar navbar-expand pb-1" aria-label =" Services" >
128+ <div >
129+ <ul class =" navbar-nav me-auto mb-0" ref =" tabsContainer" >
130+ <template v-for =" tabItem in tabItems " :key =" tabItem .text " >
131+ <li class =" nav-item" :class =" tabItem.cssClass ? tabItem.cssClass : ''" v-if =" tabItem.isVisible" >
132+ <router-link class =" nav-link" :to =" tabItem.to" >{{ tabItem.text }}</router-link >
133+ </li >
134+ </template >
135+ <li id =" moreTabs" class =" nav-item dropdown d-none" ref =" moreTabs" >
136+ <a class =" nav-link dropdown-toggle" data-bs-toggle =" dropdown" href =" #" role =" button" aria-expanded =" false" >{{ tabActive?.text }}</a >
137+ <ul class =" dropdown-menu" ref =" dropdownMenu" >
138+ <template v-for =" tabItem in tabItems " :key =" tabItem .text " >
139+ <li >
140+ <a :href =" router.resolve(tabItem.to).href" class =" dropdown-item" >
141+ {{ tabItem.text }}
142+ </a >
143+ </li >
144+ </template >
145+ </ul >
146+ </li >
147+ </ul >
148+ </div >
149+ </nav >
150+ </template >
0 commit comments