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