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