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