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 v-slot: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:isNavigationOpen', 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.app-header { 206 .link-skip-nav { 207 position: absolute; 208 top: -60px; 209 left: 0.5rem; 210 z-index: $zindex-popover; 211 transition: $duration--moderate-01 $exit-easing--expressive; 212 &:focus { 213 top: 0.5rem; 214 transition-timing-function: $entrance-easing--expressive; 215 } 216 } 217 .navbar-text, 218 .nav-link, 219 .btn-link { 220 color: color('white') !important; 221 fill: currentColor; 222 padding: 0.68rem 1rem !important; 223 224 &:hover { 225 background-color: theme-color-level(light, 10); 226 } 227 &:active { 228 background-color: theme-color-level(light, 9); 229 } 230 &:focus { 231 box-shadow: inset 0 0 0 3px $navbar-color, inset 0 0 0 5px color('white'); 232 } 233 } 234 235 .nav-item { 236 fill: theme-color('light'); 237 } 238 239 .navbar { 240 padding: 0; 241 background-color: $navbar-color; 242 @include media-breakpoint-up($responsive-layout-bp) { 243 height: $header-height; 244 } 245 246 &:focus { 247 outline: 0; 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 262 .responsive-text { 263 @include media-breakpoint-down(xs) { 264 display: none; 265 } 266 } 267 } 268 } 269 270 .navbar-nav { 271 padding: 0 $spacer; 272 align-items: center; 273 274 .navbar-brand, 275 .nav-link { 276 transition: $focus-transition; 277 } 278 &:focus { 279 outline: 0; 280 } 281 } 282 283 .nav-trigger { 284 fill: theme-color('light'); 285 width: $header-height; 286 height: $header-height; 287 transition: none; 288 display: inline-flex; 289 flex: 0 0 20px; 290 align-items: center; 291 292 svg { 293 margin: 0; 294 } 295 296 &:hover { 297 fill: theme-color('light'); 298 background-color: theme-color-level(light, 10); 299 } 300 301 &.open { 302 background-color: gray('800'); 303 } 304 305 @include media-breakpoint-up($responsive-layout-bp) { 306 display: none; 307 } 308 } 309 310 .dropdown-menu { 311 margin-top: 0; 312 313 @include media-breakpoint-only(md) { 314 margin-top: 4px; 315 } 316 } 317 318 .navbar-expand { 319 @include media-breakpoint-down(sm) { 320 flex-flow: wrap; 321 } 322 } 323} 324 325.navbar-brand { 326 padding: $spacer/2; 327 height: $header-height; 328 line-height: 1; 329 &:focus { 330 box-shadow: inset 0 0 0 3px $navbar-color, inset 0 0 0 5px color('white'); 331 outline: 0; 332 } 333} 334</style> 335