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(this.$t('global.toast.unAuthDescription'), { 176 title: this.$t('global.toast.unAuthTitle'), 177 }); 178 } 179 }, 180 }, 181 created() { 182 // Reset auth state to check if user is authenticated based 183 // on available browser cookies 184 this.$store.dispatch('authentication/resetStoreState'); 185 this.getHostInfo(); 186 this.getEvents(); 187 }, 188 mounted() { 189 this.$root.$on( 190 'change-is-navigation-open', 191 (isNavigationOpen) => (this.isNavigationOpen = isNavigationOpen) 192 ); 193 }, 194 methods: { 195 getHostInfo() { 196 this.$store.dispatch('global/getHostStatus'); 197 }, 198 getEvents() { 199 this.$store.dispatch('eventLog/getEventLogData'); 200 }, 201 refresh() { 202 this.$emit('refresh'); 203 }, 204 logout() { 205 this.$store.dispatch('authentication/logout'); 206 }, 207 toggleNavigation() { 208 this.$root.$emit('toggle-navigation'); 209 }, 210 }, 211}; 212</script> 213 214<style lang="scss"> 215@mixin focus-box-shadow($padding-color: $navbar-color, $outline-color: $white) { 216 box-shadow: inset 0 0 0 3px $padding-color, inset 0 0 0 5px $outline-color; 217} 218.app-header { 219 .link-skip-nav { 220 position: absolute; 221 top: -60px; 222 left: 0.5rem; 223 z-index: $zindex-popover; 224 transition: $duration--moderate-01 $exit-easing--expressive; 225 &:focus { 226 top: 0.5rem; 227 transition-timing-function: $entrance-easing--expressive; 228 } 229 } 230 .navbar-text, 231 .nav-link, 232 .btn-link { 233 color: color('white') !important; 234 fill: currentColor; 235 padding: 0.68rem 1rem !important; 236 237 &:hover { 238 background-color: theme-color-level(light, 10); 239 } 240 &:active { 241 background-color: theme-color-level(light, 9); 242 } 243 &:focus { 244 @include focus-box-shadow; 245 outline: 0; 246 } 247 } 248 249 .nav-item { 250 fill: theme-color('light'); 251 } 252 253 .navbar { 254 padding: 0; 255 background-color: $navbar-color; 256 @include media-breakpoint-up($responsive-layout-bp) { 257 height: $header-height; 258 } 259 260 .helper-menu { 261 @include media-breakpoint-down(sm) { 262 background-color: gray('800'); 263 width: 100%; 264 justify-content: flex-end; 265 266 .nav-link, 267 .btn { 268 padding: $spacer / 1.125 $spacer / 2; 269 } 270 271 .nav-link:focus, 272 .btn:focus { 273 @include focus-box-shadow($gray-800); 274 } 275 } 276 277 .responsive-text { 278 @include media-breakpoint-down(xs) { 279 display: none; 280 } 281 } 282 } 283 } 284 285 .navbar-nav { 286 padding: 0 $spacer; 287 align-items: center; 288 289 .navbar-brand, 290 .nav-link { 291 transition: $focus-transition; 292 } 293 .asset-tag { 294 color: theme-color-level(light, 3); 295 } 296 } 297 298 .nav-trigger { 299 fill: theme-color('light'); 300 width: $header-height; 301 height: $header-height; 302 transition: none; 303 display: inline-flex; 304 flex: 0 0 20px; 305 align-items: center; 306 307 svg { 308 margin: 0; 309 } 310 311 &:hover { 312 fill: theme-color('light'); 313 background-color: theme-color-level(light, 10); 314 } 315 316 &.open { 317 background-color: gray('800'); 318 } 319 320 @include media-breakpoint-up($responsive-layout-bp) { 321 display: none; 322 } 323 } 324 325 .dropdown-menu { 326 margin-top: 0; 327 328 @include media-breakpoint-only(md) { 329 margin-top: 4px; 330 } 331 } 332 333 .navbar-expand { 334 @include media-breakpoint-down(sm) { 335 flex-flow: wrap; 336 } 337 } 338} 339 340.navbar-brand { 341 padding: $spacer/2; 342 height: $header-height; 343 line-height: 1; 344 &:focus { 345 box-shadow: inset 0 0 0 3px $navbar-color, inset 0 0 0 5px color('white'); 346 outline: 0; 347 } 348} 349</style> 350