1<template> 2 <div> 3 <header id="page-header"> 4 <a 5 class="link-skip-nav btn btn-light" 6 href="#main-content" 7 @click="setFocus" 8 > 9 {{ $t('appHeader.skipToContent') }} 10 </a> 11 12 <b-navbar type="dark" :aria-label="$t('appHeader.applicationHeader')"> 13 <!-- Left aligned nav items --> 14 <b-button 15 id="app-header-trigger" 16 class="nav-trigger" 17 aria-hidden="true" 18 type="button" 19 variant="link" 20 :class="{ open: isNavigationOpen }" 21 @click="toggleNavigation" 22 > 23 <icon-close 24 v-if="isNavigationOpen" 25 :title="$t('appHeader.titleHideNavigation')" 26 /> 27 <icon-menu 28 v-if="!isNavigationOpen" 29 :title="$t('appHeader.titleShowNavigation')" 30 /> 31 </b-button> 32 <b-navbar-nav> 33 <b-navbar-brand 34 class="mr-0" 35 to="/" 36 data-test-id="appHeader-container-overview" 37 > 38 <img 39 class="header-logo" 40 src="@/assets/images/logo-header.svg" 41 :alt="altLogo" 42 /> 43 </b-navbar-brand> 44 <div v-if="isNavTagPresent" :key="routerKey" class="pl-2 nav-tags"> 45 <span>|</span> 46 <span class="pl-3 asset-tag">{{ assetTag }}</span> 47 <span class="pl-3">{{ modelType }}</span> 48 <span class="pl-3">{{ serialNumber }}</span> 49 </div> 50 </b-navbar-nav> 51 <!-- Right aligned nav items --> 52 <b-navbar-nav class="ml-auto helper-menu"> 53 <b-nav-item 54 to="/logs/event-logs" 55 data-test-id="appHeader-container-health" 56 > 57 <status-icon :status="healthStatusIcon" /> 58 {{ $t('appHeader.health') }} 59 </b-nav-item> 60 <b-nav-item 61 to="/operations/server-power-operations" 62 data-test-id="appHeader-container-power" 63 > 64 <status-icon :status="serverStatusIcon" /> 65 {{ $t('appHeader.power') }} 66 </b-nav-item> 67 <!-- Using LI elements instead of b-nav-item to support semantic button elements --> 68 <li class="nav-item"> 69 <b-button 70 id="app-header-refresh" 71 variant="link" 72 data-test-id="appHeader-button-refresh" 73 @click="refresh" 74 > 75 <icon-renew :title="$t('appHeader.titleRefresh')" /> 76 <span class="responsive-text">{{ $t('appHeader.refresh') }}</span> 77 </b-button> 78 </li> 79 <li class="nav-item"> 80 <b-dropdown 81 id="app-header-user" 82 variant="link" 83 right 84 data-test-id="appHeader-container-user" 85 > 86 <template #button-content> 87 <icon-avatar :title="$t('appHeader.titleProfile')" /> 88 <span class="responsive-text">{{ username }}</span> 89 </template> 90 <b-dropdown-item 91 to="/profile-settings" 92 data-test-id="appHeader-link-profile" 93 >{{ $t('appHeader.profileSettings') }} 94 </b-dropdown-item> 95 <b-dropdown-item 96 data-test-id="appHeader-link-logout" 97 @click="logout" 98 > 99 {{ $t('appHeader.logOut') }} 100 </b-dropdown-item> 101 </b-dropdown> 102 </li> 103 </b-navbar-nav> 104 </b-navbar> 105 </header> 106 <loading-bar /> 107 </div> 108</template> 109 110<script> 111import BVToastMixin from '@/components/Mixins/BVToastMixin'; 112import IconAvatar from '@carbon/icons-vue/es/user--avatar/20'; 113import IconClose from '@carbon/icons-vue/es/close/20'; 114import IconMenu from '@carbon/icons-vue/es/menu/20'; 115import IconRenew from '@carbon/icons-vue/es/renew/20'; 116import StatusIcon from '@/components/Global/StatusIcon'; 117import LoadingBar from '@/components/Global/LoadingBar'; 118 119export default { 120 name: 'AppHeader', 121 components: { 122 IconAvatar, 123 IconClose, 124 IconMenu, 125 IconRenew, 126 StatusIcon, 127 LoadingBar, 128 }, 129 mixins: [BVToastMixin], 130 props: { 131 routerKey: { 132 type: Number, 133 default: 0, 134 }, 135 }, 136 data() { 137 return { 138 isNavigationOpen: false, 139 altLogo: process.env.VUE_APP_COMPANY_NAME || 'Built on OpenBMC', 140 }; 141 }, 142 computed: { 143 isNavTagPresent() { 144 return this.assetTag || this.modelType || this.serialNumber; 145 }, 146 assetTag() { 147 return this.$store.getters['global/assetTag']; 148 }, 149 modelType() { 150 return this.$store.getters['global/modelType']; 151 }, 152 serialNumber() { 153 return this.$store.getters['global/serialNumber']; 154 }, 155 isAuthorized() { 156 return this.$store.getters['global/isAuthorized']; 157 }, 158 serverStatus() { 159 return this.$store.getters['global/serverStatus']; 160 }, 161 healthStatus() { 162 return this.$store.getters['eventLog/healthStatus']; 163 }, 164 serverStatusIcon() { 165 switch (this.serverStatus) { 166 case 'on': 167 return 'success'; 168 case 'error': 169 return 'danger'; 170 case 'diagnosticMode': 171 return 'warning'; 172 case 'off': 173 default: 174 return 'secondary'; 175 } 176 }, 177 healthStatusIcon() { 178 switch (this.healthStatus) { 179 case 'OK': 180 return 'success'; 181 case 'Warning': 182 return 'warning'; 183 case 'Critical': 184 return 'danger'; 185 default: 186 return 'secondary'; 187 } 188 }, 189 username() { 190 return this.$store.getters['global/username']; 191 }, 192 }, 193 watch: { 194 isAuthorized(value) { 195 if (value === false) { 196 this.errorToast(this.$t('global.toast.unAuthDescription'), { 197 title: this.$t('global.toast.unAuthTitle'), 198 }); 199 } 200 }, 201 }, 202 created() { 203 // Reset auth state to check if user is authenticated based 204 // on available browser cookies 205 this.$store.dispatch('authentication/resetStoreState'); 206 this.getSystemInfo(); 207 this.getEvents(); 208 }, 209 mounted() { 210 this.$root.$on( 211 'change-is-navigation-open', 212 (isNavigationOpen) => (this.isNavigationOpen = isNavigationOpen) 213 ); 214 }, 215 methods: { 216 getSystemInfo() { 217 this.$store.dispatch('global/getSystemInfo'); 218 }, 219 getEvents() { 220 this.$store.dispatch('eventLog/getEventLogData'); 221 }, 222 refresh() { 223 this.$emit('refresh'); 224 }, 225 logout() { 226 this.$store.dispatch('authentication/logout'); 227 }, 228 toggleNavigation() { 229 this.$root.$emit('toggle-navigation'); 230 }, 231 setFocus(event) { 232 event.preventDefault(); 233 this.$root.$emit('skip-navigation'); 234 }, 235 }, 236}; 237</script> 238 239<style lang="scss"> 240@mixin focus-box-shadow($padding-color: $navbar-color, $outline-color: $white) { 241 box-shadow: inset 0 0 0 3px $padding-color, inset 0 0 0 5px $outline-color; 242} 243.app-header { 244 .link-skip-nav { 245 position: absolute; 246 top: -60px; 247 left: 0.5rem; 248 z-index: $zindex-popover; 249 transition: $duration--moderate-01 $exit-easing--expressive; 250 &:focus { 251 top: 0.5rem; 252 transition-timing-function: $entrance-easing--expressive; 253 } 254 } 255 .navbar-text, 256 .nav-link, 257 .btn-link { 258 color: color('white') !important; 259 fill: currentColor; 260 padding: 0.68rem 1rem !important; 261 262 &:hover { 263 background-color: theme-color-level(light, 10); 264 } 265 &:active { 266 background-color: theme-color-level(light, 9); 267 } 268 &:focus { 269 @include focus-box-shadow; 270 outline: 0; 271 } 272 } 273 274 .nav-item { 275 fill: theme-color('light'); 276 } 277 278 .navbar { 279 padding: 0; 280 background-color: $navbar-color; 281 @include media-breakpoint-up($responsive-layout-bp) { 282 height: $header-height; 283 } 284 285 .helper-menu { 286 @include media-breakpoint-down(sm) { 287 background-color: gray('800'); 288 width: 100%; 289 justify-content: flex-end; 290 291 .nav-link, 292 .btn { 293 padding: $spacer / 1.125 $spacer / 2; 294 } 295 296 .nav-link:focus, 297 .btn:focus { 298 @include focus-box-shadow($gray-800); 299 } 300 } 301 302 .responsive-text { 303 @include media-breakpoint-down(xs) { 304 @include sr-only; 305 } 306 } 307 } 308 } 309 310 .navbar-nav { 311 @include media-breakpoint-up($responsive-layout-bp) { 312 padding: 0 $spacer; 313 } 314 align-items: center; 315 316 .navbar-brand, 317 .nav-link { 318 transition: $focus-transition; 319 } 320 .nav-tags { 321 color: theme-color-level(light, 3); 322 @include media-breakpoint-down(xs) { 323 @include sr-only; 324 } 325 .asset-tag { 326 @include media-breakpoint-down($responsive-layout-bp) { 327 @include sr-only; 328 } 329 } 330 } 331 } 332 333 .nav-trigger { 334 fill: theme-color('light'); 335 width: $header-height; 336 height: $header-height; 337 transition: none; 338 display: inline-flex; 339 flex: 0 0 20px; 340 align-items: center; 341 342 svg { 343 margin: 0; 344 } 345 346 &:hover { 347 fill: theme-color('light'); 348 background-color: theme-color-level(light, 10); 349 } 350 351 &.open { 352 background-color: gray('800'); 353 } 354 355 @include media-breakpoint-up($responsive-layout-bp) { 356 display: none; 357 } 358 } 359 360 .dropdown-menu { 361 margin-top: 0; 362 363 @include media-breakpoint-only(md) { 364 margin-top: 4px; 365 } 366 } 367 368 .navbar-expand { 369 @include media-breakpoint-down(sm) { 370 flex-flow: wrap; 371 } 372 } 373} 374 375.navbar-brand { 376 padding: $spacer/2; 377 height: $header-height; 378 line-height: 1; 379 &:focus { 380 box-shadow: inset 0 0 0 3px $navbar-color, inset 0 0 0 5px color('white'); 381 outline: 0; 382 } 383} 384</style> 385