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