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'; 118import { mapState } from 'vuex'; 119 120export default { 121 name: 'AppHeader', 122 components: { 123 IconAvatar, 124 IconClose, 125 IconMenu, 126 IconRenew, 127 StatusIcon, 128 LoadingBar, 129 }, 130 mixins: [BVToastMixin], 131 props: { 132 routerKey: { 133 type: Number, 134 default: 0, 135 }, 136 }, 137 data() { 138 return { 139 isNavigationOpen: false, 140 altLogo: process.env.VUE_APP_COMPANY_NAME || 'Built on OpenBMC', 141 }; 142 }, 143 computed: { 144 ...mapState('authentication', ['consoleWindow']), 145 isNavTagPresent() { 146 return this.assetTag || this.modelType || this.serialNumber; 147 }, 148 assetTag() { 149 return this.$store.getters['global/assetTag']; 150 }, 151 modelType() { 152 return this.$store.getters['global/modelType']; 153 }, 154 serialNumber() { 155 return this.$store.getters['global/serialNumber']; 156 }, 157 isAuthorized() { 158 return this.$store.getters['global/isAuthorized']; 159 }, 160 userPrivilege() { 161 return this.$store.getters['global/userPrivilege']; 162 }, 163 serverStatus() { 164 return this.$store.getters['global/serverStatus']; 165 }, 166 healthStatus() { 167 return this.$store.getters['eventLog/healthStatus']; 168 }, 169 serverStatusIcon() { 170 switch (this.serverStatus) { 171 case 'on': 172 return 'success'; 173 case 'error': 174 return 'danger'; 175 case 'diagnosticMode': 176 return 'warning'; 177 case 'off': 178 default: 179 return 'secondary'; 180 } 181 }, 182 healthStatusIcon() { 183 switch (this.healthStatus) { 184 case 'OK': 185 return 'success'; 186 case 'Warning': 187 return 'warning'; 188 case 'Critical': 189 return 'danger'; 190 default: 191 return 'secondary'; 192 } 193 }, 194 username() { 195 return this.$store.getters['global/username']; 196 }, 197 }, 198 watch: { 199 consoleWindow() { 200 if (this.consoleWindow === false) this.$eventBus.$consoleWindow.close(); 201 }, 202 isAuthorized(value) { 203 if (value === false) { 204 this.errorToast(this.$t('global.toast.unAuthDescription'), { 205 title: this.$t('global.toast.unAuthTitle'), 206 }); 207 } 208 }, 209 }, 210 created() { 211 // Reset auth state to check if user is authenticated based 212 // on available browser cookies 213 this.$store.dispatch('authentication/resetStoreState'); 214 this.getSystemInfo(); 215 this.getEvents(); 216 }, 217 mounted() { 218 this.$root.$on( 219 'change-is-navigation-open', 220 (isNavigationOpen) => (this.isNavigationOpen = isNavigationOpen), 221 ); 222 }, 223 methods: { 224 getSystemInfo() { 225 this.$store.dispatch('global/getSystemInfo'); 226 }, 227 getEvents() { 228 this.$store.dispatch('eventLog/getEventLogData'); 229 }, 230 refresh() { 231 this.$emit('refresh'); 232 }, 233 logout() { 234 this.$store.dispatch('authentication/logout'); 235 }, 236 toggleNavigation() { 237 this.$root.$emit('toggle-navigation'); 238 }, 239 setFocus(event) { 240 event.preventDefault(); 241 this.$root.$emit('skip-navigation'); 242 }, 243 }, 244}; 245</script> 246 247<style lang="scss"> 248@mixin focus-box-shadow($padding-color: $navbar-color, $outline-color: $white) { 249 box-shadow: 250 inset 0 0 0 3px $padding-color, 251 inset 0 0 0 5px $outline-color; 252} 253.app-header { 254 .link-skip-nav { 255 position: absolute; 256 top: -60px; 257 left: 0.5rem; 258 z-index: $zindex-popover; 259 transition: $duration--moderate-01 $exit-easing--expressive; 260 &:focus { 261 top: 0.5rem; 262 transition-timing-function: $entrance-easing--expressive; 263 } 264 } 265 .navbar-text, 266 .nav-link, 267 .btn-link { 268 color: color('white') !important; 269 fill: currentColor; 270 padding: 0.68rem 1rem !important; 271 272 &:hover { 273 background-color: theme-color-level(light, 10); 274 } 275 &:active { 276 background-color: theme-color-level(light, 9); 277 } 278 &:focus { 279 @include focus-box-shadow; 280 outline: 0; 281 } 282 } 283 284 .nav-item { 285 fill: theme-color('light'); 286 } 287 288 .navbar { 289 padding: 0; 290 background-color: $navbar-color; 291 @include media-breakpoint-up($responsive-layout-bp) { 292 height: $header-height; 293 } 294 295 .helper-menu { 296 @include media-breakpoint-down(sm) { 297 background-color: gray('800'); 298 width: 100%; 299 justify-content: flex-end; 300 301 .nav-link, 302 .btn { 303 padding: $spacer / 1.125 $spacer / 2; 304 } 305 306 .nav-link:focus, 307 .btn:focus { 308 @include focus-box-shadow($gray-800); 309 } 310 } 311 312 .responsive-text { 313 @include media-breakpoint-down(xs) { 314 @include sr-only; 315 } 316 } 317 } 318 } 319 320 .navbar-nav { 321 @include media-breakpoint-up($responsive-layout-bp) { 322 padding: 0 $spacer; 323 } 324 align-items: center; 325 326 .navbar-brand, 327 .nav-link { 328 transition: $focus-transition; 329 } 330 .nav-tags { 331 color: theme-color-level(light, 3); 332 @include media-breakpoint-down(xs) { 333 @include sr-only; 334 } 335 .asset-tag { 336 @include media-breakpoint-down($responsive-layout-bp) { 337 @include sr-only; 338 } 339 } 340 } 341 } 342 343 .nav-trigger { 344 fill: theme-color('light'); 345 width: $header-height; 346 height: $header-height; 347 transition: none; 348 display: inline-flex; 349 flex: 0 0 20px; 350 align-items: center; 351 352 svg { 353 margin: 0; 354 } 355 356 &:hover { 357 fill: theme-color('light'); 358 background-color: theme-color-level(light, 10); 359 } 360 361 &.open { 362 background-color: gray('800'); 363 } 364 365 @include media-breakpoint-up($responsive-layout-bp) { 366 display: none; 367 } 368 } 369 370 .dropdown-menu { 371 margin-top: 0; 372 373 @include media-breakpoint-only(md) { 374 margin-top: 4px; 375 } 376 } 377 378 .navbar-expand { 379 @include media-breakpoint-down(sm) { 380 flex-flow: wrap; 381 } 382 } 383} 384 385.navbar-brand { 386 padding: $spacer/2; 387 height: $header-height; 388 line-height: 1; 389 &:focus { 390 box-shadow: 391 inset 0 0 0 3px $navbar-color, 392 inset 0 0 0 5px color('white'); 393 outline: 0; 394 } 395} 396</style> 397