xref: /openbmc/webui-vue/src/components/AppHeader/AppHeader.vue (revision d624dae9d6727a09f6eb33b95c19986826359d6c)
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-nav-item 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-nav-item>
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-dark {
218    .navbar-text,
219    .nav-link,
220    .btn-link {
221      color: theme-color('light') !important;
222      fill: currentColor;
223    }
224  }
225
226  .nav-item {
227    fill: theme-color('light');
228  }
229
230  .navbar {
231    padding: 0;
232    background-color: $navbar-color;
233    @include media-breakpoint-up($responsive-layout-bp) {
234      height: $header-height;
235    }
236
237    .btn-link {
238      padding: $spacer / 2;
239    }
240
241    .header-logo {
242      width: auto;
243      height: $header-height;
244      padding: $spacer/2 0;
245    }
246
247    .helper-menu {
248      @include media-breakpoint-down(sm) {
249        background-color: gray('800');
250        width: 100%;
251        justify-content: flex-end;
252
253        .nav-link,
254        .btn {
255          padding: $spacer / 1.125 $spacer / 2;
256        }
257      }
258
259      .responsive-text {
260        @include media-breakpoint-down(xs) {
261          display: none;
262        }
263      }
264    }
265  }
266
267  .navbar-nav {
268    padding: 0 $spacer;
269  }
270
271  .nav-trigger {
272    fill: theme-color('light');
273    width: $header-height;
274    height: $header-height;
275    transition: none;
276
277    svg {
278      margin: 0;
279    }
280
281    &:hover {
282      fill: theme-color('light');
283      background-color: theme-color('dark');
284    }
285
286    &.open {
287      background-color: gray('800');
288    }
289
290    @include media-breakpoint-up($responsive-layout-bp) {
291      display: none;
292    }
293  }
294
295  .dropdown {
296    .dropdown-menu {
297      margin-top: 0;
298      @include media-breakpoint-up(md) {
299        margin-top: 7px;
300      }
301    }
302  }
303
304  .navbar-expand {
305    @include media-breakpoint-down(sm) {
306      flex-flow: wrap;
307    }
308  }
309}
310</style>
311