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