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