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