summaryrefslogtreecommitdiff
path: root/src/views/_sila/Overview
diff options
context:
space:
mode:
authorAnna Tsyganova <ATSyganova@IBS.RU>2022-07-08 11:32:33 +0300
committerAnna Tsyganova <ATSyganova@IBS.RU>2022-07-08 11:32:33 +0300
commitf12e602ab84f2a4ae28a064ed2058f57b9cf9bc3 (patch)
tree02fb24ee79bc558eebc374aa77c6aed7964a69d2 /src/views/_sila/Overview
parent40c493597703305ae732b414bda83a4f00b25745 (diff)
downloadwebui-vue-f12e602ab84f2a4ae28a064ed2058f57b9cf9bc3.tar.xz
Fix a little global components
Issue: SILABMC-191
Diffstat (limited to 'src/views/_sila/Overview')
-rw-r--r--src/views/_sila/Overview/DateTime/DateTime.vue419
-rw-r--r--src/views/_sila/Overview/DateTime/index.js2
-rw-r--r--src/views/_sila/Overview/Inventory/Inventory.vue195
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryServiceIndicator.vue76
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableAssembly.vue153
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableBmcManager.vue245
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableChassis.vue191
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableDimmSlot.vue255
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableFans.vue190
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTablePowerSupplies.vue208
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableProcessors.vue251
-rw-r--r--src/views/_sila/Overview/Inventory/InventoryTableSystem.vue224
-rw-r--r--src/views/_sila/Overview/Inventory/index.js2
-rw-r--r--src/views/_sila/Overview/Network/ModalDns.vue92
-rw-r--r--src/views/_sila/Overview/Network/ModalHostname.vue110
-rw-r--r--src/views/_sila/Overview/Network/ModalIpv4.vue165
-rw-r--r--src/views/_sila/Overview/Network/ModalMacAddress.vue109
-rw-r--r--src/views/_sila/Overview/Network/Network.vue169
-rw-r--r--src/views/_sila/Overview/Network/NetworkGlobalSettings.vue161
-rw-r--r--src/views/_sila/Overview/Network/NetworkInterfaceSettings.vue117
-rw-r--r--src/views/_sila/Overview/Network/TableDns.vue145
-rw-r--r--src/views/_sila/Overview/Network/TableIpv4.vue169
-rw-r--r--src/views/_sila/Overview/Network/index.js2
-rw-r--r--src/views/_sila/Overview/Overview.vue6
-rw-r--r--src/views/_sila/Overview/OverviewDumps.vue2
-rw-r--r--src/views/_sila/Overview/OverviewEvents.vue4
-rw-r--r--src/views/_sila/Overview/OverviewFirmware.vue2
-rw-r--r--src/views/_sila/Overview/OverviewNetwork.vue2
-rw-r--r--src/views/_sila/Overview/OverviewPower.vue2
-rw-r--r--src/views/_sila/Overview/OverviewQuickLinks.vue2
-rw-r--r--src/views/_sila/Overview/OverviewServer.vue4
31 files changed, 3662 insertions, 12 deletions
diff --git a/src/views/_sila/Overview/DateTime/DateTime.vue b/src/views/_sila/Overview/DateTime/DateTime.vue
new file mode 100644
index 00000000..66871699
--- /dev/null
+++ b/src/views/_sila/Overview/DateTime/DateTime.vue
@@ -0,0 +1,419 @@
+<template>
+ <b-container fluid="xl">
+ <page-title />
+ <b-row>
+ <b-col md="8" xl="6">
+ <alert variant="info" class="mb-4">
+ <span>
+ {{ $t('pageDateTime.alert.message') }}
+ <b-link to="/profile-settings">
+ {{ $t('pageDateTime.alert.link') }}</b-link
+ >
+ </span>
+ </alert>
+ </b-col>
+ </b-row>
+ <page-section>
+ <b-row>
+ <b-col lg="3">
+ <dl>
+ <dt>{{ $t('pageDateTime.form.date') }}</dt>
+ <dd v-if="bmcTime">{{ bmcTime | formatDate }}</dd>
+ <dd v-else>--</dd>
+ </dl>
+ </b-col>
+ <b-col lg="3">
+ <dl>
+ <dt>{{ $t('pageDateTime.form.time.label') }}</dt>
+ <dd v-if="bmcTime">{{ bmcTime | formatTime }}</dd>
+ <dd v-else>--</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+ <page-section :section-title="$t('pageDateTime.configureSettings')">
+ <b-form novalidate @submit.prevent="submitForm">
+ <b-form-group
+ label="Configure date and time"
+ :disabled="loading"
+ label-sr-only
+ >
+ <b-form-radio
+ v-model="form.configurationSelected"
+ value="manual"
+ data-test-id="dateTime-radio-configureManual"
+ >
+ {{ $t('pageDateTime.form.manual') }}
+ </b-form-radio>
+ <b-row class="mt-3 ml-3">
+ <b-col sm="6" lg="4" xl="3">
+ <b-form-group
+ :label="$t('pageDateTime.form.date')"
+ label-for="input-manual-date"
+ >
+ <b-form-text id="date-format-help">YYYY-MM-DD</b-form-text>
+ <b-input-group>
+ <b-form-input
+ id="input-manual-date"
+ v-model="form.manual.date"
+ :state="getValidationState($v.form.manual.date)"
+ :disabled="ntpOptionSelected"
+ data-test-id="dateTime-input-manualDate"
+ class="form-control-with-button"
+ @blur="$v.form.manual.date.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.manual.date.pattern">
+ {{ $t('global.form.invalidFormat') }}
+ </div>
+ <div v-if="!$v.form.manual.date.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ </b-form-invalid-feedback>
+ <b-form-datepicker
+ v-model="form.manual.date"
+ class="btn-datepicker btn-icon-only"
+ button-only
+ right
+ :hide-header="true"
+ :locale="locale"
+ :label-help="
+ $t('global.calendar.useCursorKeysToNavigateCalendarDates')
+ "
+ :title="$t('global.calendar.selectDate')"
+ :disabled="ntpOptionSelected"
+ button-variant="link"
+ aria-controls="input-manual-date"
+ >
+ <template #button-content>
+ <icon-calendar />
+ <span class="sr-only">
+ {{ $t('global.calendar.selectDate') }}
+ </span>
+ </template>
+ </b-form-datepicker>
+ </b-input-group>
+ </b-form-group>
+ </b-col>
+ <b-col sm="6" lg="4" xl="3">
+ <b-form-group
+ :label="$t('pageDateTime.form.time.timezone', { timezone })"
+ label-for="input-manual-time"
+ >
+ <b-form-text id="time-format-help">HH:MM</b-form-text>
+ <b-input-group>
+ <b-form-input
+ id="input-manual-time"
+ v-model="form.manual.time"
+ :state="getValidationState($v.form.manual.time)"
+ :disabled="ntpOptionSelected"
+ data-test-id="dateTime-input-manualTime"
+ @blur="$v.form.manual.time.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.manual.time.pattern">
+ {{ $t('global.form.invalidFormat') }}
+ </div>
+ <div v-if="!$v.form.manual.time.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ </b-form-invalid-feedback>
+ </b-input-group>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ <b-form-radio
+ v-model="form.configurationSelected"
+ value="ntp"
+ data-test-id="dateTime-radio-configureNTP"
+ >
+ NTP
+ </b-form-radio>
+ <b-row class="mt-3 ml-3">
+ <b-col sm="6" lg="4" xl="3">
+ <b-form-group
+ :label="$t('pageDateTime.form.ntpServers.server1')"
+ label-for="input-ntp-1"
+ >
+ <b-input-group>
+ <b-form-input
+ id="input-ntp-1"
+ v-model="form.ntp.firstAddress"
+ :state="getValidationState($v.form.ntp.firstAddress)"
+ :disabled="manualOptionSelected"
+ data-test-id="dateTime-input-ntpServer1"
+ @blur="$v.form.ntp.firstAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.ntp.firstAddress.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ </b-form-invalid-feedback>
+ </b-input-group>
+ </b-form-group>
+ </b-col>
+ <b-col sm="6" lg="4" xl="3">
+ <b-form-group
+ :label="$t('pageDateTime.form.ntpServers.server2')"
+ label-for="input-ntp-2"
+ >
+ <b-input-group>
+ <b-form-input
+ id="input-ntp-2"
+ v-model="form.ntp.secondAddress"
+ :disabled="manualOptionSelected"
+ data-test-id="dateTime-input-ntpServer2"
+ />
+ </b-input-group>
+ </b-form-group>
+ </b-col>
+ <b-col sm="6" lg="4" xl="3">
+ <b-form-group
+ :label="$t('pageDateTime.form.ntpServers.server3')"
+ label-for="input-ntp-3"
+ >
+ <b-input-group>
+ <b-form-input
+ id="input-ntp-3"
+ v-model="form.ntp.thirdAddress"
+ :disabled="manualOptionSelected"
+ data-test-id="dateTime-input-ntpServer3"
+ />
+ </b-input-group>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ <b-button
+ variant="primary"
+ type="submit"
+ data-test-id="dateTime-button-saveSettings"
+ >
+ {{ $t('global.action.saveSettings') }}
+ </b-button>
+ </b-form-group>
+ </b-form>
+ </page-section>
+ </b-container>
+</template>
+
+<script>
+import Alert from '@/components/_sila/Global/Alert';
+import IconCalendar from '@carbon/icons-vue/es/calendar/20';
+import PageTitle from '@/components/_sila/Global/PageTitle';
+import PageSection from '@/components/_sila/Global/PageSection';
+
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import LoadingBarMixin, {
+ loading,
+} from '@/components/_sila/Mixins/LoadingBarMixin';
+import LocalTimezoneLabelMixin from '@/components/_sila/Mixins/LocalTimezoneLabelMixin';
+import VuelidateMixin from '@/components/_sila/Mixins/VuelidateMixin.js';
+
+import { mapState } from 'vuex';
+import { requiredIf, helpers } from 'vuelidate/lib/validators';
+
+const isoDateRegex = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/;
+const isoTimeRegex = /^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/;
+
+export default {
+ name: 'DateTime',
+ components: { Alert, IconCalendar, PageTitle, PageSection },
+ mixins: [
+ BVToastMixin,
+ LoadingBarMixin,
+ LocalTimezoneLabelMixin,
+ VuelidateMixin,
+ ],
+ beforeRouteLeave(to, from, next) {
+ this.hideLoader();
+ next();
+ },
+ data() {
+ return {
+ locale: this.$store.getters['global/languagePreference'],
+ form: {
+ configurationSelected: 'manual',
+ manual: {
+ date: '',
+ time: '',
+ },
+ ntp: { firstAddress: '', secondAddress: '', thirdAddress: '' },
+ },
+ loading,
+ };
+ },
+ validations() {
+ return {
+ form: {
+ manual: {
+ date: {
+ required: requiredIf(function () {
+ return this.form.configurationSelected === 'manual';
+ }),
+ pattern: helpers.regex('pattern', isoDateRegex),
+ },
+ time: {
+ required: requiredIf(function () {
+ return this.form.configurationSelected === 'manual';
+ }),
+ pattern: helpers.regex('pattern', isoTimeRegex),
+ },
+ },
+ ntp: {
+ firstAddress: {
+ required: requiredIf(function () {
+ return this.form.configurationSelected === 'ntp';
+ }),
+ },
+ },
+ },
+ };
+ },
+ computed: {
+ ...mapState('dateTime', ['ntpServers', 'isNtpProtocolEnabled']),
+ bmcTime() {
+ return this.$store.getters['global/bmcTime'];
+ },
+ ntpOptionSelected() {
+ return this.form.configurationSelected === 'ntp';
+ },
+ manualOptionSelected() {
+ return this.form.configurationSelected === 'manual';
+ },
+ isUtcDisplay() {
+ return this.$store.getters['global/isUtcDisplay'];
+ },
+ timezone() {
+ if (this.isUtcDisplay) {
+ return 'UTC';
+ }
+ return this.localOffset();
+ },
+ },
+ watch: {
+ ntpServers() {
+ this.setNtpValues();
+ },
+ manualDate() {
+ this.emitChange();
+ },
+ bmcTime() {
+ this.form.manual.date = this.$options.filters.formatDate(
+ this.$store.getters['global/bmcTime']
+ );
+ this.form.manual.time = this.$options.filters
+ .formatTime(this.$store.getters['global/bmcTime'])
+ .slice(0, 5);
+ },
+ },
+ created() {
+ this.startLoader();
+ this.setNtpValues();
+ Promise.all([
+ this.$store.dispatch('global/getBmcTime'),
+ this.$store.dispatch('dateTime/getNtpData'),
+ ]).finally(() => this.endLoader());
+ },
+ methods: {
+ emitChange() {
+ if (this.$v.$invalid) return;
+ this.$v.$reset(); //reset to re-validate on blur
+ this.$emit('change', {
+ manualDate: this.manualDate ? new Date(this.manualDate) : null,
+ });
+ },
+ setNtpValues() {
+ this.form.configurationSelected = this.isNtpProtocolEnabled
+ ? 'ntp'
+ : 'manual';
+ [
+ this.form.ntp.firstAddress = '',
+ this.form.ntp.secondAddress = '',
+ this.form.ntp.thirdAddress = '',
+ ] = [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]];
+ },
+ submitForm() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.startLoader();
+
+ let dateTimeForm = {};
+ let isNTPEnabled = this.form.configurationSelected === 'ntp';
+
+ if (!isNTPEnabled) {
+ const isUtcDisplay = this.$store.getters['global/isUtcDisplay'];
+ let date;
+
+ dateTimeForm.ntpProtocolEnabled = false;
+
+ if (isUtcDisplay) {
+ // Create UTC Date
+ date = this.getUtcDate(this.form.manual.date, this.form.manual.time);
+ } else {
+ // Create local Date
+ date = new Date(`${this.form.manual.date} ${this.form.manual.time}`);
+ }
+
+ dateTimeForm.updatedDateTime = date.toISOString();
+ } else {
+ dateTimeForm.ntpProtocolEnabled = true;
+
+ const ntpArray = [
+ this.form.ntp.firstAddress,
+ this.form.ntp.secondAddress,
+ this.form.ntp.thirdAddress,
+ ];
+
+ // Filter the ntpArray to remove empty strings,
+ // per Redfish spec there should be no empty strings or null on the ntp array.
+ const ntpArrayFiltered = ntpArray.filter((x) => x);
+
+ dateTimeForm.ntpServersArray = [...ntpArrayFiltered];
+
+ [this.ntpServers[0], this.ntpServers[1], this.ntpServers[2]] = [
+ ...dateTimeForm.ntpServersArray,
+ ];
+
+ this.setNtpValues();
+ }
+
+ this.$store
+ .dispatch('dateTime/updateDateTime', dateTimeForm)
+ .then((success) => {
+ this.successToast(success);
+ if (!isNTPEnabled) return;
+ // Shift address up if second address is empty
+ // to avoid refreshing after delay when updating NTP
+ if (!this.form.ntp.secondAddress && this.form.ntp.thirdAddres) {
+ this.form.ntp.secondAddress = this.form.ntp.thirdAddres;
+ this.form.ntp.thirdAddress = '';
+ }
+ })
+ .then(() => {
+ this.$store.dispatch('global/getBmcTime');
+ })
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => {
+ this.$v.form.$reset();
+ this.endLoader();
+ });
+ },
+ getUtcDate(date, time) {
+ // Split user input string values to create
+ // a UTC Date object
+ const datesArray = date.split('-');
+ const timeArray = time.split(':');
+ let utcDate = Date.UTC(
+ datesArray[0], // User input year
+ //UTC expects zero-index month value 0-11 (January-December)
+ //for reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#Parameters
+ parseInt(datesArray[1]) - 1, // User input month
+ datesArray[2], // User input day
+ timeArray[0], // User input hour
+ timeArray[1] // User input minute
+ );
+ return new Date(utcDate);
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/DateTime/index.js b/src/views/_sila/Overview/DateTime/index.js
new file mode 100644
index 00000000..2df21eae
--- /dev/null
+++ b/src/views/_sila/Overview/DateTime/index.js
@@ -0,0 +1,2 @@
+import DateTime from './DateTime.vue';
+export default DateTime;
diff --git a/src/views/_sila/Overview/Inventory/Inventory.vue b/src/views/_sila/Overview/Inventory/Inventory.vue
new file mode 100644
index 00000000..41258597
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/Inventory.vue
@@ -0,0 +1,195 @@
+<template>
+ <b-container fluid="xl">
+ <page-title />
+
+ <!-- Service indicators -->
+ <service-indicator />
+
+ <!-- Quicklinks section -->
+ <page-section :section-title="$t('pageInventory.quicklinkTitle')">
+ <b-row class="w-75">
+ <b-col v-for="column in quicklinkColumns" :key="column.id" xl="4">
+ <div v-for="item in column" :key="item.id">
+ <b-link
+ :href="item.href"
+ :data-ref="item.dataRef"
+ @click.prevent="scrollToOffset"
+ >
+ {{ item.linkText }}
+ </b-link>
+ </div>
+ </b-col>
+ </b-row>
+ </page-section>
+
+ <!-- System table -->
+ <table-system ref="system" />
+
+ <!-- BMC manager table -->
+ <table-bmc-manager ref="bmc" />
+
+ <!-- Chassis table -->
+ <table-chassis ref="chassis" />
+
+ <!-- DIMM slot table -->
+ <table-dimm-slot ref="dimms" />
+
+ <!-- Fans table -->
+ <table-fans ref="fans" />
+
+ <!-- Power supplies table -->
+ <table-power-supplies ref="powerSupply" />
+
+ <!-- Processors table -->
+ <table-processors ref="processors" />
+
+ <!-- Assembly table -->
+ <table-assembly ref="assembly" />
+ </b-container>
+</template>
+
+<script>
+import PageTitle from '@/components/_sila/Global/PageTitle';
+import ServiceIndicator from './InventoryServiceIndicator';
+import TableSystem from './InventoryTableSystem';
+import TablePowerSupplies from './InventoryTablePowerSupplies';
+import TableDimmSlot from './InventoryTableDimmSlot';
+import TableFans from './InventoryTableFans';
+import TableBmcManager from './InventoryTableBmcManager';
+import TableChassis from './InventoryTableChassis';
+import TableProcessors from './InventoryTableProcessors';
+import TableAssembly from './InventoryTableAssembly';
+import LoadingBarMixin from '@/components/_sila/Mixins/LoadingBarMixin';
+import PageSection from '@/components/_sila/Global/PageSection';
+import JumpLinkMixin from '@/components/_sila/Mixins/JumpLinkMixin';
+import { chunk } from 'lodash';
+
+export default {
+ components: {
+ PageTitle,
+ ServiceIndicator,
+ TableDimmSlot,
+ TablePowerSupplies,
+ TableSystem,
+ TableFans,
+ TableBmcManager,
+ TableChassis,
+ TableProcessors,
+ TableAssembly,
+ PageSection,
+ },
+ mixins: [LoadingBarMixin, JumpLinkMixin],
+ beforeRouteLeave(to, from, next) {
+ // Hide loader if user navigates away from page
+ // before requests complete
+ this.hideLoader();
+ next();
+ },
+ data() {
+ return {
+ links: [
+ {
+ id: 'system',
+ dataRef: 'system',
+ href: '#system',
+ linkText: this.$t('pageInventory.system'),
+ },
+ {
+ id: 'bmc',
+ dataRef: 'bmc',
+ href: '#bmc',
+ linkText: this.$t('pageInventory.bmcManager'),
+ },
+ {
+ id: 'chassis',
+ dataRef: 'chassis',
+ href: '#chassis',
+ linkText: this.$t('pageInventory.chassis'),
+ },
+ {
+ id: 'dimms',
+ dataRef: 'dimms',
+ href: '#dimms',
+ linkText: this.$t('pageInventory.dimmSlot'),
+ },
+ {
+ id: 'fans',
+ dataRef: 'fans',
+ href: '#fans',
+ linkText: this.$t('pageInventory.fans'),
+ },
+ {
+ id: 'powerSupply',
+ dataRef: 'powerSupply',
+ href: '#powerSupply',
+ linkText: this.$t('pageInventory.powerSupplies'),
+ },
+ {
+ id: 'processors',
+ dataRef: 'processors',
+ href: '#processors',
+ linkText: this.$t('pageInventory.processors'),
+ },
+ {
+ id: 'assembly',
+ dataRef: 'assembly',
+ href: '#assembly',
+ linkText: this.$t('pageInventory.assemblies'),
+ },
+ ],
+ };
+ },
+ computed: {
+ quicklinkColumns() {
+ // Chunk links array to 3 array's to display 3 items per column
+ return chunk(this.links, 3);
+ },
+ },
+ created() {
+ console.log(123456, process.env.VUE_APP_ENV_NAME);
+ this.startLoader();
+ const bmcManagerTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-bmc-manager-complete', () => resolve());
+ });
+ const chassisTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-chassis-complete', () => resolve());
+ });
+ const dimmSlotTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-dimm-slot-complete', () => resolve());
+ });
+ const fansTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-fans-complete', () => resolve());
+ });
+ const powerSuppliesTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-power-supplies-complete', () =>
+ resolve()
+ );
+ });
+ const processorsTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-processors-complete', () => resolve());
+ });
+ const serviceIndicatorPromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-service-complete', () => resolve());
+ });
+ const systemTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-system-complete', () => resolve());
+ });
+ const assemblyTablePromise = new Promise((resolve) => {
+ this.$root.$on('hardware-status-assembly-complete', () => resolve());
+ });
+ // Combine all child component Promises to indicate
+ // when page data load complete
+ Promise.all([
+ bmcManagerTablePromise,
+ chassisTablePromise,
+ dimmSlotTablePromise,
+ fansTablePromise,
+ powerSuppliesTablePromise,
+ processorsTablePromise,
+ serviceIndicatorPromise,
+ systemTablePromise,
+ assemblyTablePromise,
+ ]).finally(() => this.endLoader());
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryServiceIndicator.vue b/src/views/_sila/Overview/Inventory/InventoryServiceIndicator.vue
new file mode 100644
index 00000000..b4531be7
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryServiceIndicator.vue
@@ -0,0 +1,76 @@
+<template>
+ <page-section
+ :section-title="$t('pageInventory.systemIndicator.sectionTitle')"
+ >
+ <div class="form-background pl-4 pt-4 pb-1">
+ <b-row>
+ <b-col sm="6" md="3">
+ <dl>
+ <dt>{{ $t('pageInventory.systemIndicator.powerStatus') }}</dt>
+ <dd>
+ {{ $t(powerStatus) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" md="3">
+ <dl>
+ <dt>
+ {{ $t('pageInventory.systemIndicator.identifyLed') }}
+ </dt>
+ <dd>
+ <b-form-checkbox
+ id="identifyLedSwitchService"
+ v-model="systems.locationIndicatorActive"
+ data-test-id="inventoryService-toggle-identifyLed"
+ switch
+ @change="toggleIdentifyLedSwitch"
+ >
+ <span v-if="systems.locationIndicatorActive">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else>{{ $t('global.status.off') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </div>
+ </page-section>
+</template>
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+
+export default {
+ components: { PageSection },
+ mixins: [BVToastMixin],
+ computed: {
+ systems() {
+ let systemData = this.$store.getters['system/systems'][0];
+ return systemData ? systemData : {};
+ },
+ serverStatus() {
+ return this.$store.getters['global/serverStatus'];
+ },
+ powerStatus() {
+ if (this.serverStatus === 'unreachable') {
+ return `global.status.off`;
+ }
+ return `global.status.${this.serverStatus}`;
+ },
+ },
+ created() {
+ this.$store.dispatch('system/getSystem').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-service-complete');
+ });
+ },
+ methods: {
+ toggleIdentifyLedSwitch(state) {
+ this.$store
+ .dispatch('system/changeIdentifyLedState', state)
+ .catch(({ message }) => this.errorToast(message));
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableAssembly.vue b/src/views/_sila/Overview/Inventory/InventoryTableAssembly.vue
new file mode 100644
index 00000000..9c284533
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableAssembly.vue
@@ -0,0 +1,153 @@
+<template>
+ <page-section :section-title="$t('pageInventory.assemblies')">
+ <b-table
+ sort-icon-left
+ no-sort-reset
+ hover
+ responsive="md"
+ :items="items"
+ :fields="fields"
+ show-empty
+ :empty-text="$t('global.table.emptyMessage')"
+ :busy="isBusy"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandAssembly"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Toggle identify LED -->
+ <template #cell(identifyLed)="row">
+ <b-form-checkbox
+ v-if="hasIdentifyLed(row.item.identifyLed)"
+ v-model="row.item.identifyLed"
+ name="switch"
+ switch
+ @change="toggleIdentifyLedValue(row.item)"
+ >
+ <span v-if="row.item.identifyLed">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else> {{ $t('global.status.off') }} </span>
+ </b-form-checkbox>
+ <div v-else>--</div>
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <!-- Nmae -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ <!-- Serial number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <!-- Model-->
+ <dt>Model</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ <!-- Spare Part Number -->
+ <dt>Spare Part Number</dt>
+ <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+
+export default {
+ components: { IconChevron, PageSection },
+ mixins: [BVToastMixin, TableRowExpandMixin, DataFormatterMixin],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'name',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'partNumber',
+ label: this.$t('pageInventory.table.partNumber'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ assemblies() {
+ return this.$store.getters['assemblies/assemblies'];
+ },
+ items() {
+ if (this.assemblies) {
+ return this.assemblies;
+ } else {
+ return [];
+ }
+ },
+ },
+ created() {
+ this.$store.dispatch('assemblies/getAssemblyInfo').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-assembly-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ toggleIdentifyLedValue(row) {
+ this.$store
+ .dispatch('assemblies/updateIdentifyLedValue', {
+ uri: row.uri,
+ memberId: row.id,
+ identifyLed: row.identifyLed,
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ hasIdentifyLed(identifyLed) {
+ return typeof identifyLed === 'boolean';
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableBmcManager.vue b/src/views/_sila/Overview/Inventory/InventoryTableBmcManager.vue
new file mode 100644
index 00000000..e3375d57
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableBmcManager.vue
@@ -0,0 +1,245 @@
+<template>
+ <page-section :section-title="$t('pageInventory.bmcManager')">
+ <b-table
+ responsive="md"
+ hover
+ :items="items"
+ :fields="fields"
+ show-empty
+ :empty-text="$t('global.table.emptyMessage')"
+ :busy="isBusy"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandBmc"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+
+ <!-- Toggle identify LED -->
+ <template #cell(identifyLed)="row">
+ <b-form-checkbox
+ v-if="hasIdentifyLed(row.item.identifyLed)"
+ v-model="row.item.identifyLed"
+ name="switch"
+ switch
+ @change="toggleIdentifyLedValue(row.item)"
+ >
+ <span v-if="row.item.identifyLed">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else> {{ $t('global.status.off') }} </span>
+ </b-form-checkbox>
+ <div v-else>--</div>
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ <!-- Part number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ <!-- Serial number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ <!-- Spare part number -->
+ <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ <!-- UUID -->
+ <dt>{{ $t('pageInventory.table.uuid') }}:</dt>
+ <dd>{{ dataFormatter(item.uuid) }}</dd>
+ <!-- Service entry point UUID -->
+ <dt>{{ $t('pageInventory.table.serviceEntryPointUuid') }}:</dt>
+ <dd>{{ dataFormatter(item.serviceEntryPointUuid) }}</dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ <!-- Power state -->
+ <dt>{{ $t('pageInventory.table.power') }}:</dt>
+ <dd>{{ dataFormatter(item.powerState) }}</dd>
+ <!-- Health rollup -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.healthRollup) }}</dd>
+ <!-- BMC date and time -->
+ <dt>{{ $t('pageInventory.table.bmcDateTime') }}:</dt>
+ <dd>
+ {{ item.dateTime | formatDate }}
+ {{ item.dateTime | formatTime }}
+ </dd>
+ <!-- Reset date and time -->
+ <dt>{{ $t('pageInventory.table.lastResetTime') }}:</dt>
+ <dd>
+ {{ item.lastResetTime | formatDate }}
+ {{ item.lastResetTime | formatTime }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Manufacturer -->
+ <dt>{{ $t('pageInventory.table.manufacturer') }}:</dt>
+ <dd>{{ dataFormatter(item.manufacturer) }}</dd>
+ <!-- Description -->
+ <dt>{{ $t('pageInventory.table.description') }}:</dt>
+ <dd>{{ dataFormatter(item.description) }}</dd>
+ <!-- Manager type -->
+ <dt>{{ $t('pageInventory.table.managerType') }}:</dt>
+ <dd>{{ dataFormatter(item.managerType) }}</dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <!-- Firmware Version -->
+ <dl>
+ <dt>{{ $t('pageInventory.table.firmwareVersion') }}:</dt>
+ <dd>{{ item.firmwareVersion }}</dd>
+ </dl>
+ <!-- Graphical console -->
+ <p class="mt-1 mb-2 h6 float-none m-0">
+ {{ $t('pageInventory.table.graphicalConsole') }}
+ </p>
+ <dl class="ml-4">
+ <dt>{{ $t('pageInventory.table.connectTypesSupported') }}:</dt>
+ <dd>
+ {{ dataFormatterArray(item.graphicalConsoleConnectTypes) }}
+ </dd>
+ <dt>{{ $t('pageInventory.table.maxConcurrentSessions') }}:</dt>
+ <dd>
+ {{ dataFormatter(item.graphicalConsoleMaxSessions) }}
+ </dd>
+ <dt>{{ $t('pageInventory.table.serviceEnabled') }}:</dt>
+ <dd>
+ {{ dataFormatter(item.graphicalConsoleEnabled) }}
+ </dd>
+ </dl>
+ <!-- Serial console -->
+ <p class="mt-1 mb-2 h6 float-none m-0">
+ {{ $t('pageInventory.table.serialConsole') }}
+ </p>
+ <dl class="ml-4">
+ <dt>{{ $t('pageInventory.table.connectTypesSupported') }}:</dt>
+ <dd>
+ {{ dataFormatterArray(item.serialConsoleConnectTypes) }}
+ </dd>
+ <dt>{{ $t('pageInventory.table.maxConcurrentSessions') }}:</dt>
+ <dd>{{ dataFormatter(item.serialConsoleMaxSessions) }}</dd>
+ <dt>{{ $t('pageInventory.table.serviceEnabled') }}:</dt>
+ <dd>{{ dataFormatter(item.serialConsoleEnabled) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon },
+ mixins: [BVToastMixin, TableRowExpandMixin, DataFormatterMixin],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ bmc() {
+ return this.$store.getters['bmc/bmc'];
+ },
+ items() {
+ if (this.bmc) {
+ return [this.bmc];
+ } else {
+ return [];
+ }
+ },
+ },
+ created() {
+ this.$store.dispatch('bmc/getBmcInfo').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-bmc-manager-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ toggleIdentifyLedValue(row) {
+ this.$store
+ .dispatch('bmc/updateIdentifyLedValue', {
+ uri: row.uri,
+ identifyLed: row.identifyLed,
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ // TO DO: remove hasIdentifyLed method once the following story is merged:
+ // https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/43179
+ hasIdentifyLed(identifyLed) {
+ return typeof identifyLed === 'boolean';
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableChassis.vue b/src/views/_sila/Overview/Inventory/InventoryTableChassis.vue
new file mode 100644
index 00000000..a5eb5ae6
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableChassis.vue
@@ -0,0 +1,191 @@
+<template>
+ <page-section :section-title="$t('pageInventory.chassis')">
+ <b-table
+ responsive="md"
+ hover
+ :items="chassis"
+ :fields="fields"
+ show-empty
+ :empty-text="$t('global.table.emptyMessage')"
+ :busy="isBusy"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandChassis"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+ <!-- Toggle identify LED -->
+ <template #cell(identifyLed)="row">
+ <b-form-checkbox
+ v-if="hasIdentifyLed(row.item.identifyLed)"
+ v-model="row.item.identifyLed"
+ name="switch"
+ switch
+ @change="toggleIdentifyLedValue(row.item)"
+ >
+ <span v-if="row.item.identifyLed">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else> {{ $t('global.status.off') }} </span>
+ </b-form-checkbox>
+ <div v-else>--</div>
+ </template>
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ <!-- Part number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ <!-- Serial Number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd class="mb-2">
+ {{ dataFormatter(item.model) }}
+ </dd>
+ <!-- Asset tag -->
+ <dt>{{ $t('pageInventory.table.assetTag') }}:</dt>
+ <dd class="mb-2">
+ {{ dataFormatter(item.assetTag) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ <!-- Power state -->
+ <dt>{{ $t('pageInventory.table.power') }}:</dt>
+ <dd>{{ dataFormatter(item.power) }}</dd>
+ <!-- Health rollup -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.healthRollup) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Manufacturer -->
+ <dt>{{ $t('pageInventory.table.manufacturer') }}:</dt>
+ <dd>{{ dataFormatter(item.manufacturer) }}</dd>
+ <!-- Chassis Type -->
+ <dt>{{ $t('pageInventory.table.chassisType') }}:</dt>
+ <dd>{{ dataFormatter(item.chassisType) }}</dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Min power -->
+ <dt>{{ $t('pageInventory.table.minPowerWatts') }}:</dt>
+ <dd>{{ dataFormatter(item.minPowerWatts) }}</dd>
+ <!-- Max power -->
+ <dt>{{ $t('pageInventory.table.maxPowerWatts') }}:</dt>
+ <dd>{{ dataFormatter(item.maxPowerWatts) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon },
+ mixins: [BVToastMixin, TableRowExpandMixin, DataFormatterMixin],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ chassis() {
+ return this.$store.getters['chassis/chassis'];
+ },
+ },
+ created() {
+ this.$store.dispatch('chassis/getChassisInfo').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-chassis-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ toggleIdentifyLedValue(row) {
+ this.$store
+ .dispatch('chassis/updateIdentifyLedValue', {
+ uri: row.uri,
+ identifyLed: row.identifyLed,
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ // TO DO: Remove this method when the LocationIndicatorActive is added from backend.
+ hasIdentifyLed(identifyLed) {
+ return typeof identifyLed === 'boolean';
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableDimmSlot.vue b/src/views/_sila/Overview/Inventory/InventoryTableDimmSlot.vue
new file mode 100644
index 00000000..14160502
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableDimmSlot.vue
@@ -0,0 +1,255 @@
+<template>
+ <page-section :section-title="$t('pageInventory.dimmSlot')">
+ <b-row class="align-items-end">
+ <b-col sm="6" md="5" xl="4">
+ <search
+ @change-search="onChangeSearchInput"
+ @clear-search="onClearSearchInput"
+ />
+ </b-col>
+ <b-col sm="6" md="3" xl="2">
+ <table-cell-count
+ :filtered-items-count="filteredRows"
+ :total-number-of-cells="dimms.length"
+ ></table-cell-count>
+ </b-col>
+ </b-row>
+ <b-table
+ sort-icon-left
+ no-sort-reset
+ hover
+ sort-by="health"
+ responsive="md"
+ show-empty
+ :items="dimms"
+ :fields="fields"
+ :sort-desc="true"
+ :sort-compare="sortCompare"
+ :filter="searchFilter"
+ :empty-text="$t('global.table.emptyMessage')"
+ :empty-filtered-text="$t('global.table.emptySearchMessage')"
+ :busy="isBusy"
+ @filtered="onFiltered"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandDimms"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+ <!-- Toggle identify LED -->
+ <template #cell(identifyLed)="row">
+ <b-form-checkbox
+ v-model="row.item.identifyLed"
+ name="switch"
+ switch
+ @change="toggleIdentifyLedValue(row.item)"
+ >
+ <span v-if="row.item.identifyLed">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else> {{ $t('global.status.off') }} </span>
+ </b-form-checkbox>
+ </template>
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col sm="6" xl="6">
+ <dl>
+ <!-- Part Number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ </dl>
+ <dl>
+ <!-- Serial Number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ </dl>
+ <dl>
+ <!-- Spare Part Number -->
+ <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+ </dl>
+ <dl>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" xl="6">
+ <dl>
+ <!-- Memory Size in kb -->
+ <dt>{{ $t('pageInventory.table.memorySize') }}:</dt>
+ <dd>{{ dataFormatter(item.memorySize) }} KB</dd>
+ </dl>
+ <dl>
+ <!-- Status-->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ </dl>
+ <dl>
+ <!-- Enabled-->
+ <dt>{{ $t('pageInventory.table.enabled') }}:</dt>
+ <dd>{{ dataFormatter(item.enabled) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col sm="6" xl="6">
+ <dl>
+ <!-- Description -->
+ <dt>{{ $t('pageInventory.table.description') }}:</dt>
+ <dd>{{ dataFormatter(item.description) }}</dd>
+ </dl>
+ <dl>
+ <!-- Memory Type -->
+ <dt>{{ $t('pageInventory.table.memoryType') }}:</dt>
+ <dd>{{ dataFormatter(item.memoryType) }}</dd>
+ </dl>
+ <dl>
+ <!-- Base Module Type -->
+ <dt>{{ $t('pageInventory.table.baseModuleType') }}:</dt>
+ <dd>{{ dataFormatter(item.baseModuleType) }}</dd>
+ </dl>
+ <dl>
+ <!-- Capacity MiB -->
+ <dt>{{ $t('pageInventory.table.capacityMiB') }}:</dt>
+ <dd>{{ dataFormatter(item.capacityMiB) }}</dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" xl="6">
+ <dl>
+ <!-- Bus Width Bits -->
+ <dt>{{ $t('pageInventory.table.busWidthBits') }}:</dt>
+ <dd>{{ dataFormatter(item.busWidthBits) }}</dd>
+ </dl>
+ <dl>
+ <!-- Data Width Bits -->
+ <dt>{{ $t('pageInventory.table.dataWidthBits') }}:</dt>
+ <dd>{{ dataFormatter(item.dataWidthBits) }}</dd>
+ </dl>
+ <dl>
+ <!-- Operating Speed Mhz -->
+ <dt>{{ $t('pageInventory.table.operatingSpeedMhz') }}:</dt>
+ <dd>{{ dataFormatter(item.operatingSpeedMhz) }} MHz</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import TableCellCount from '@/components/_sila/Global/TableCellCount';
+
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import TableSortMixin from '@/components/_sila/Mixins/TableSortMixin';
+import Search from '@/components/_sila/Global/Search';
+import SearchFilterMixin, {
+ searchFilter,
+} from '@/components/_sila/Mixins/SearchFilterMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+ mixins: [
+ TableRowExpandMixin,
+ DataFormatterMixin,
+ TableSortMixin,
+ SearchFilterMixin,
+ ],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ searchFilter: searchFilter,
+ searchTotalFilteredRows: 0,
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.dimms.length;
+ },
+ dimms() {
+ return this.$store.getters['memory/dimms'];
+ },
+ },
+ created() {
+ this.$store.dispatch('memory/getDimms').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-dimm-slot-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ sortCompare(a, b, key) {
+ if (key === 'health') {
+ return this.sortStatus(a, b, key);
+ }
+ },
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ toggleIdentifyLedValue(row) {
+ this.$store
+ .dispatch('memory/updateIdentifyLedValue', {
+ uri: row.uri,
+ identifyLed: row.identifyLed,
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableFans.vue b/src/views/_sila/Overview/Inventory/InventoryTableFans.vue
new file mode 100644
index 00000000..068f41a4
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableFans.vue
@@ -0,0 +1,190 @@
+<template>
+ <page-section :section-title="$t('pageInventory.fans')">
+ <b-row class="align-items-end">
+ <b-col sm="6" md="5" xl="4">
+ <search
+ @change-search="onChangeSearchInput"
+ @clear-search="onClearSearchInput"
+ />
+ </b-col>
+ <b-col sm="6" md="3" xl="2">
+ <table-cell-count
+ :filtered-items-count="filteredRows"
+ :total-number-of-cells="fans.length"
+ ></table-cell-count>
+ </b-col>
+ </b-row>
+ <b-table
+ sort-icon-left
+ no-sort-reset
+ hover
+ responsive="md"
+ sort-by="health"
+ show-empty
+ :items="fans"
+ :fields="fields"
+ :sort-desc="true"
+ :sort-compare="sortCompare"
+ :filter="searchFilter"
+ :empty-text="$t('global.table.emptyMessage')"
+ :empty-filtered-text="$t('global.table.emptySearchMessage')"
+ :busy="isBusy"
+ @filtered="onFiltered"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandFans"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ </dl>
+ <dl>
+ <!-- Serial number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ </dl>
+ <dl>
+ <!-- Part number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ </dl>
+ <dl>
+ <!-- Fan speed -->
+ <dt>{{ $t('pageInventory.table.fanSpeed') }}:</dt>
+ <dd>{{ dataFormatter(item.speed) }}</dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ </dl>
+ <dl>
+ <!-- Health Rollup state -->
+ <dt>{{ $t('pageInventory.table.statusHealthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.healthRollup) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import TableCellCount from '@/components/_sila/Global/TableCellCount';
+
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import TableSortMixin from '@/components/_sila/Mixins/TableSortMixin';
+import Search from '@/components/_sila/Global/Search';
+import SearchFilterMixin, {
+ searchFilter,
+} from '@/components/_sila/Mixins/SearchFilterMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+ mixins: [
+ TableRowExpandMixin,
+ DataFormatterMixin,
+ TableSortMixin,
+ SearchFilterMixin,
+ ],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ sortable: false,
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'partNumber',
+ label: this.$t('pageInventory.table.partNumber'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'serialNumber',
+ label: this.$t('pageInventory.table.serialNumber'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ searchFilter: searchFilter,
+ searchTotalFilteredRows: 0,
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.fans.length;
+ },
+ fans() {
+ return this.$store.getters['fan/fans'];
+ },
+ },
+ created() {
+ this.$store.dispatch('fan/getFanInfo').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-fans-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ sortCompare(a, b, key) {
+ if (key === 'health') {
+ return this.sortStatus(a, b, key);
+ }
+ },
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTablePowerSupplies.vue b/src/views/_sila/Overview/Inventory/InventoryTablePowerSupplies.vue
new file mode 100644
index 00000000..a55b3e5e
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTablePowerSupplies.vue
@@ -0,0 +1,208 @@
+<template>
+ <page-section :section-title="$t('pageInventory.powerSupplies')">
+ <b-row class="align-items-end">
+ <b-col sm="6" md="5" xl="4">
+ <search
+ @change-search="onChangeSearchInput"
+ @clear-search="onClearSearchInput"
+ />
+ </b-col>
+ <b-col sm="6" md="3" xl="2">
+ <table-cell-count
+ :filtered-items-count="filteredRows"
+ :total-number-of-cells="powerSupplies.length"
+ ></table-cell-count>
+ </b-col>
+ </b-row>
+ <b-table
+ sort-icon-left
+ no-sort-reset
+ hover
+ responsive="md"
+ sort-by="health"
+ show-empty
+ :items="powerSupplies"
+ :fields="fields"
+ :sort-desc="true"
+ :sort-compare="sortCompare"
+ :filter="searchFilter"
+ :empty-text="$t('global.table.emptyMessage')"
+ :empty-filtered-text="$t('global.table.emptySearchMessage')"
+ :busy="isBusy"
+ @filtered="onFiltered"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandPowerSupplies"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ <!-- Part number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ <!-- Serial number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ <!-- Spare part number -->
+ <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ <!-- Status Health rollup state -->
+ <dt>{{ $t('pageInventory.table.statusHealthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.statusHealth) }}</dd>
+ <!-- Efficiency percent -->
+ <dt>{{ $t('pageInventory.table.efficiencyPercent') }}:</dt>
+ <dd>{{ dataFormatter(item.efficiencyPercent) }}</dd>
+ <!-- Power input watts -->
+ <dt>{{ $t('pageInventory.table.powerInputWatts') }}:</dt>
+ <dd>{{ dataFormatter(item.powerInputWatts) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Manufacturer -->
+ <dt>{{ $t('pageInventory.table.manufacturer') }}:</dt>
+ <dd>{{ dataFormatter(item.manufacturer) }}</dd>
+ </dl>
+ </b-col>
+ <b-col sm="6" xl="4">
+ <dl>
+ <!-- Firmware version -->
+ <dt>{{ $t('pageInventory.table.firmwareVersion') }}:</dt>
+ <dd>{{ dataFormatter(item.firmwareVersion) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import TableCellCount from '@/components/_sila/Global/TableCellCount';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import TableSortMixin from '@/components/_sila/Mixins/TableSortMixin';
+import Search from '@/components/_sila/Global/Search';
+import SearchFilterMixin, {
+ searchFilter,
+} from '@/components/_sila/Mixins/SearchFilterMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+ mixins: [
+ TableRowExpandMixin,
+ DataFormatterMixin,
+ TableSortMixin,
+ SearchFilterMixin,
+ ],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ sortable: false,
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ searchFilter: searchFilter,
+ searchTotalFilteredRows: 0,
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.powerSupplies.length;
+ },
+ powerSupplies() {
+ return this.$store.getters['powerSupply/powerSupplies'];
+ },
+ },
+ created() {
+ this.$store.dispatch('powerSupply/getAllPowerSupplies').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-power-supplies-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ sortCompare(a, b, key) {
+ if (key === 'health') {
+ return this.sortStatus(a, b, key);
+ }
+ },
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableProcessors.vue b/src/views/_sila/Overview/Inventory/InventoryTableProcessors.vue
new file mode 100644
index 00000000..c6c798cf
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableProcessors.vue
@@ -0,0 +1,251 @@
+<template>
+ <page-section :section-title="$t('pageInventory.processors')">
+ <!-- Search -->
+ <b-row class="align-items-end">
+ <b-col sm="6" md="5" xl="4">
+ <search
+ @change-search="onChangeSearchInput"
+ @clear-search="onClearSearchInput"
+ />
+ </b-col>
+ <b-col sm="6" md="3" xl="2">
+ <table-cell-count
+ :filtered-items-count="filteredRows"
+ :total-number-of-cells="processors.length"
+ ></table-cell-count>
+ </b-col>
+ </b-row>
+ <b-table
+ sort-icon-left
+ no-sort-reset
+ hover
+ responsive="md"
+ show-empty
+ :items="processors"
+ :fields="fields"
+ :sort-desc="true"
+ :filter="searchFilter"
+ :empty-text="$t('global.table.emptyMessage')"
+ :empty-filtered-text="$t('global.table.emptySearchMessage')"
+ :busy="isBusy"
+ @filtered="onFiltered"
+ >
+ <!-- Expand button -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandProcessors"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+
+ <!-- Toggle identify LED -->
+ <template #cell(identifyLed)="row">
+ <b-form-checkbox
+ v-if="hasIdentifyLed(row.item.identifyLed)"
+ v-model="row.item.identifyLed"
+ name="switch"
+ switch
+ @change="toggleIdentifyLedValue(row.item)"
+ >
+ <span v-if="row.item.identifyLed">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else> {{ $t('global.status.off') }} </span>
+ </b-form-checkbox>
+ <div v-else>--</div>
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageInventory.table.name') }}:</dt>
+ <dd>{{ dataFormatter(item.name) }}</dd>
+ <!-- Part Number -->
+ <dt>{{ $t('pageInventory.table.partNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.partNumber) }}</dd>
+ <!-- Serial Number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ <!-- Spare Part Number -->
+ <dt>{{ $t('pageInventory.table.sparePartNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.sparePartNumber) }}</dd>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ <!-- Asset Tag -->
+ <dt>{{ $t('pageInventory.table.assetTag') }}:</dt>
+ <dd>{{ dataFormatter(item.assetTag) }}</dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6" xl="6">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ <!-- Health Rollup -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.healthRollup) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col class="mt-1" sm="6" xl="6">
+ <dl>
+ <!-- Manufacturer -->
+ <dt>{{ $t('pageInventory.table.manufacturer') }}:</dt>
+ <dd>{{ dataFormatter(item.manufacturer) }}</dd>
+ <!-- Processor Type -->
+ <dt>{{ $t('pageInventory.table.processorType') }}:</dt>
+ <dd>{{ dataFormatter(item.processorType) }}</dd>
+ <!-- Processor Architecture -->
+ <dt>{{ $t('pageInventory.table.processorArchitecture') }}:</dt>
+ <dd>{{ dataFormatter(item.processorArchitecture) }}</dd>
+ <!-- Instruction Set -->
+ <dt>{{ $t('pageInventory.table.instructionSet') }}:</dt>
+ <dd>{{ dataFormatter(item.instructionSet) }}</dd>
+ <!-- Version -->
+ <dt>{{ $t('pageInventory.table.version') }}:</dt>
+ <dd>{{ dataFormatter(item.version) }}</dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-1" sm="6" xl="6">
+ <dl>
+ <!-- Min Speed MHz -->
+ <dt>{{ $t('pageInventory.table.minSpeedMHz') }}:</dt>
+ <dd>{{ dataFormatter(item.minSpeedMHz) }}</dd>
+ <!-- Max Speed MHz -->
+ <dt>{{ $t('pageInventory.table.maxSpeedMHz') }}:</dt>
+ <dd>{{ dataFormatter(item.maxSpeedMHz) }}</dd>
+ <!-- Total Cores -->
+ <dt>{{ $t('pageInventory.table.totalCores') }}:</dt>
+ <dd>{{ dataFormatter(item.totalCores) }}</dd>
+ <!-- Total Threads -->
+ <dt>{{ $t('pageInventory.table.totalThreads') }}:</dt>
+ <dd>{{ dataFormatter(item.totalThreads) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import TableCellCount from '@/components/_sila/Global/TableCellCount';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import TableSortMixin from '@/components/_sila/Mixins/TableSortMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import Search from '@/components/_sila/Global/Search';
+import SearchFilterMixin, {
+ searchFilter,
+} from '@/components/_sila/Mixins/SearchFilterMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon, Search, TableCellCount },
+ mixins: [
+ BVToastMixin,
+ TableRowExpandMixin,
+ DataFormatterMixin,
+ TableSortMixin,
+ SearchFilterMixin,
+ ],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ sortable: false,
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ sortable: true,
+ },
+ {
+ key: 'identifyLed',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ sortable: false,
+ },
+ ],
+ searchFilter: searchFilter,
+ searchTotalFilteredRows: 0,
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.processors.length;
+ },
+ processors() {
+ return this.$store.getters['processors/processors'];
+ },
+ },
+ created() {
+ this.$store.dispatch('processors/getProcessorsInfo').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-processors-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ toggleIdentifyLedValue(row) {
+ this.$store
+ .dispatch('processors/updateIdentifyLedValue', {
+ uri: row.uri,
+ identifyLed: row.identifyLed,
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ // TO DO: remove hasIdentifyLed when the following is merged:
+ // https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/37045
+ hasIdentifyLed(identifyLed) {
+ return typeof identifyLed === 'boolean';
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/InventoryTableSystem.vue b/src/views/_sila/Overview/Inventory/InventoryTableSystem.vue
new file mode 100644
index 00000000..eacc4a06
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/InventoryTableSystem.vue
@@ -0,0 +1,224 @@
+<template>
+ <page-section :section-title="$t('pageInventory.system')">
+ <b-table
+ responsive="md"
+ hover
+ show-empty
+ :items="systems"
+ :fields="fields"
+ :empty-text="$t('global.table.emptyMessage')"
+ :busy="isBusy"
+ >
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ data-test-id="hardwareStatus-button-expandSystem"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ <span class="sr-only">{{ expandRowLabel }}</span>
+ </b-button>
+ </template>
+
+ <!-- Health -->
+ <template #cell(health)="{ value }">
+ <status-icon :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+
+ <template #cell(locationIndicatorActive)="{ item }">
+ <b-form-checkbox
+ id="identifyLedSwitchSystem"
+ v-model="item.locationIndicatorActive"
+ data-test-id="inventorySystem-toggle-identifyLed"
+ switch
+ @change="toggleIdentifyLedSwitch"
+ >
+ <span v-if="item.locationIndicatorActive">
+ {{ $t('global.status.on') }}
+ </span>
+ <span v-else>{{ $t('global.status.off') }}</span>
+ </b-form-checkbox>
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col class="mt-2" sm="6">
+ <dl>
+ <!-- Serial number -->
+ <dt>{{ $t('pageInventory.table.serialNumber') }}:</dt>
+ <dd>{{ dataFormatter(item.serialNumber) }}</dd>
+ <!-- Model -->
+ <dt>{{ $t('pageInventory.table.model') }}:</dt>
+ <dd>{{ dataFormatter(item.model) }}</dd>
+ <!-- Asset tag -->
+ <dt>{{ $t('pageInventory.table.assetTag') }}:</dt>
+ <dd class="mb-2">
+ {{ dataFormatter(item.assetTag) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col class="mt-2" sm="6">
+ <dl>
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.statusState) }}</dd>
+ <!-- Power state -->
+ <dt>{{ $t('pageInventory.table.power') }}:</dt>
+ <dd>{{ dataFormatter(item.powerState) }}</dd>
+ <!-- Health rollup -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.healthRollup) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <div class="section-divider mb-3 mt-3"></div>
+ <b-row>
+ <b-col class="mt-1" sm="6">
+ <dl>
+ <!-- Manufacturer -->
+ <dt>{{ $t('pageInventory.table.manufacturer') }}:</dt>
+ <dd>{{ dataFormatter(item.manufacturer) }}</dd>
+ <!-- Description -->
+ <dt>{{ $t('pageInventory.table.description') }}:</dt>
+ <dd>{{ dataFormatter(item.description) }}</dd>
+ <!-- Sub Model -->
+ <dt>{{ $t('pageInventory.table.subModel') }}:</dt>
+ <dd>
+ {{ dataFormatter(item.subModel) }}
+ </dd>
+ <!-- System Type -->
+ <dt>{{ $t('pageInventory.table.systemType') }}:</dt>
+ <dd>
+ {{ dataFormatter(item.systemType) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col sm="6">
+ <!-- Memory Summary -->
+ <p class="mt-1 mb-2 h6 float-none m-0">
+ {{ $t('pageInventory.table.memorySummary') }}
+ </p>
+ <dl class="ml-4">
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.memorySummaryState) }}</dd>
+ <!-- Health -->
+ <dt>{{ $t('pageInventory.table.health') }}:</dt>
+ <dd>{{ dataFormatter(item.memorySummaryHealth) }}</dd>
+ <!-- Health Roll -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.memorySummaryHealthRollup) }}</dd>
+ <!-- Total system memory -->
+ <dt>{{ $t('pageInventory.table.totalSystemMemoryGiB') }}:</dt>
+ <dd>{{ dataFormatter(item.totalSystemMemoryGiB) }}GB</dd>
+ </dl>
+ <!-- Processor Summary -->
+ <p class="mt-1 mb-2 h6 float-none m-0">
+ {{ $t('pageInventory.table.processorSummary') }}
+ </p>
+ <dl class="ml-4">
+ <!-- Status state -->
+ <dt>{{ $t('pageInventory.table.statusState') }}:</dt>
+ <dd>{{ dataFormatter(item.processorSummaryState) }}</dd>
+ <!-- Health -->
+ <dt>{{ $t('pageInventory.table.health') }}:</dt>
+ <dd>{{ dataFormatter(item.processorSummaryHealth) }}</dd>
+ <!-- Health Rollup -->
+ <dt>{{ $t('pageInventory.table.healthRollup') }}:</dt>
+ <dd>{{ dataFormatter(item.processorSummaryHealthRoll) }}</dd>
+ <!-- Count -->
+ <dt>{{ $t('pageInventory.table.count') }}:</dt>
+ <dd>{{ dataFormatter(item.processorSummaryCount) }}</dd>
+ <!-- Core Count -->
+ <dt>{{ $t('pageInventory.table.coreCount') }}:</dt>
+ <dd>{{ dataFormatter(item.processorSummaryCoreCount) }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import PageSection from '@/components/_sila/Global/PageSection';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/_sila/Mixins/TableRowExpandMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+
+export default {
+ components: { IconChevron, PageSection, StatusIcon },
+ mixins: [BVToastMixin, TableRowExpandMixin, DataFormatterMixin],
+ data() {
+ return {
+ isBusy: true,
+ fields: [
+ {
+ key: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'id',
+ label: this.$t('pageInventory.table.id'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'hardwareType',
+ label: this.$t('pageInventory.table.hardwareType'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'health',
+ label: this.$t('pageInventory.table.health'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'locationNumber',
+ label: this.$t('pageInventory.table.locationNumber'),
+ formatter: this.dataFormatter,
+ },
+ {
+ key: 'locationIndicatorActive',
+ label: this.$t('pageInventory.table.identifyLed'),
+ formatter: this.dataFormatter,
+ },
+ ],
+ expandRowLabel: expandRowLabel,
+ };
+ },
+ computed: {
+ systems() {
+ return this.$store.getters['system/systems'];
+ },
+ },
+ created() {
+ this.$store.dispatch('system/getSystem').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('hardware-status-system-complete');
+ this.isBusy = false;
+ });
+ },
+ methods: {
+ toggleIdentifyLedSwitch(state) {
+ this.$store
+ .dispatch('system/changeIdentifyLedState', state)
+ .catch(({ message }) => this.errorToast(message));
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Inventory/index.js b/src/views/_sila/Overview/Inventory/index.js
new file mode 100644
index 00000000..c9fde8d2
--- /dev/null
+++ b/src/views/_sila/Overview/Inventory/index.js
@@ -0,0 +1,2 @@
+import Inventory from './Inventory.vue';
+export default Inventory;
diff --git a/src/views/_sila/Overview/Network/ModalDns.vue b/src/views/_sila/Overview/Network/ModalDns.vue
new file mode 100644
index 00000000..342ebe5e
--- /dev/null
+++ b/src/views/_sila/Overview/Network/ModalDns.vue
@@ -0,0 +1,92 @@
+<template>
+ <b-modal
+ id="modal-dns"
+ ref="modal"
+ :title="$t('pageNetwork.table.addDnsAddress')"
+ @hidden="resetForm"
+ >
+ <b-form id="form-dns" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.staticDns')"
+ label-for="staticDns"
+ >
+ <b-form-input
+ id="staticDns"
+ v-model="form.staticDns"
+ type="text"
+ :state="getValidationState($v.form.staticDns)"
+ @input="$v.form.staticDns.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.staticDns.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.staticDns.ipAddress">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button form="form-dns" type="submit" variant="primary" @click="onOk">
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/_sila/Mixins/VuelidateMixin.js';
+import { ipAddress, required } from 'vuelidate/lib/validators';
+
+export default {
+ mixins: [VuelidateMixin],
+ data() {
+ return {
+ form: {
+ staticDns: null,
+ },
+ };
+ },
+ validations() {
+ return {
+ form: {
+ staticDns: {
+ required,
+ ipAddress,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', [this.form.staticDns]);
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.staticDns = null;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/ModalHostname.vue b/src/views/_sila/Overview/Network/ModalHostname.vue
new file mode 100644
index 00000000..3ad152c1
--- /dev/null
+++ b/src/views/_sila/Overview/Network/ModalHostname.vue
@@ -0,0 +1,110 @@
+<template>
+ <b-modal
+ id="modal-hostname"
+ ref="modal"
+ :title="$t('pageNetwork.modal.editHostnameTitle')"
+ @hidden="resetForm"
+ >
+ <b-form id="hostname-settings" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.hostname')"
+ label-for="hostname"
+ >
+ <b-form-input
+ id="hostname"
+ v-model="form.hostname"
+ type="text"
+ :state="getValidationState($v.form.hostname)"
+ @input="$v.form.hostname.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.hostname.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.hostname.validateHostname">
+ {{ $t('global.form.lengthMustBeBetween', { min: 1, max: 64 }) }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button
+ form="hostname-settings"
+ type="submit"
+ variant="primary"
+ @click="onOk"
+ >
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/_sila/Mixins/VuelidateMixin.js';
+import { required, helpers } from 'vuelidate/lib/validators';
+
+const validateHostname = helpers.regex('validateHostname', /^\S{0,64}$/);
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ hostname: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ form: {
+ hostname: '',
+ },
+ };
+ },
+ watch: {
+ hostname() {
+ this.form.hostname = this.hostname;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ hostname: {
+ required,
+ validateHostname,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', { HostName: this.form.hostname });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.hostname = this.hostname;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/ModalIpv4.vue b/src/views/_sila/Overview/Network/ModalIpv4.vue
new file mode 100644
index 00000000..00742a11
--- /dev/null
+++ b/src/views/_sila/Overview/Network/ModalIpv4.vue
@@ -0,0 +1,165 @@
+<template>
+ <b-modal
+ id="modal-add-ipv4"
+ ref="modal"
+ :title="$t('pageNetwork.table.addIpv4Address')"
+ @hidden="resetForm"
+ >
+ <b-form id="form-ipv4" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.ipAddress')"
+ label-for="ipAddress"
+ >
+ <b-form-input
+ id="ipAddress"
+ v-model="form.ipAddress"
+ type="text"
+ :state="getValidationState($v.form.ipAddress)"
+ @input="$v.form.ipAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.ipAddress.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.ipAddress.ipAddress">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.gateway')"
+ label-for="gateway"
+ >
+ <b-form-input
+ id="gateway"
+ v-model="form.gateway"
+ type="text"
+ :state="getValidationState($v.form.gateway)"
+ @input="$v.form.gateway.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.gateway.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.gateway.ipAddress">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.subnetMask')"
+ label-for="subnetMask"
+ >
+ <b-form-input
+ id="subnetMask"
+ v-model="form.subnetMask"
+ type="text"
+ :state="getValidationState($v.form.subnetMask)"
+ @input="$v.form.subnetMask.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.subnetMask.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.subnetMask.ipAddress">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button form="form-ipv4" type="submit" variant="primary" @click="onOk">
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/_sila/Mixins/VuelidateMixin.js';
+import { ipAddress, required } from 'vuelidate/lib/validators';
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ defaultGateway: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ form: {
+ ipAddress: '',
+ gateway: '',
+ subnetMask: '',
+ },
+ };
+ },
+ watch: {
+ defaultGateway() {
+ this.form.gateway = this.defaultGateway;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ ipAddress: {
+ required,
+ ipAddress,
+ },
+ gateway: {
+ required,
+ ipAddress,
+ },
+ subnetMask: {
+ required,
+ ipAddress,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', {
+ Address: this.form.ipAddress,
+ Gateway: this.form.gateway,
+ SubnetMask: this.form.subnetMask,
+ });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.ipAddress = null;
+ this.form.gateway = this.defaultGateway;
+ this.form.subnetMask = null;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/ModalMacAddress.vue b/src/views/_sila/Overview/Network/ModalMacAddress.vue
new file mode 100644
index 00000000..98f4b019
--- /dev/null
+++ b/src/views/_sila/Overview/Network/ModalMacAddress.vue
@@ -0,0 +1,109 @@
+<template>
+ <b-modal
+ id="modal-mac-address"
+ ref="modal"
+ :title="$t('pageNetwork.modal.editMacAddressTitle')"
+ @hidden="resetForm"
+ >
+ <b-form id="mac-settings" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.macAddress')"
+ label-for="macAddress"
+ >
+ <b-form-input
+ id="mac-address"
+ v-model.trim="form.macAddress"
+ data-test-id="network-input-macAddress"
+ type="text"
+ :state="getValidationState($v.form.macAddress)"
+ @change="$v.form.macAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.macAddress.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ <div v-if="!$v.form.macAddress.macAddress">
+ {{ $t('global.form.invalidFormat') }}
+ </div>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button
+ form="mac-settings"
+ type="submit"
+ variant="primary"
+ @click="onOk"
+ >
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/_sila/Mixins/VuelidateMixin.js';
+import { macAddress, required } from 'vuelidate/lib/validators';
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ macAddress: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ form: {
+ macAddress: '',
+ },
+ };
+ },
+ watch: {
+ macAddress() {
+ this.form.macAddress = this.macAddress;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ macAddress: {
+ required,
+ macAddress: macAddress(),
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', { MACAddress: this.form.macAddress });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.macAddress = this.macAddress;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/Network.vue b/src/views/_sila/Overview/Network/Network.vue
new file mode 100644
index 00000000..2321b1bd
--- /dev/null
+++ b/src/views/_sila/Overview/Network/Network.vue
@@ -0,0 +1,169 @@
+<template>
+ <b-container fluid="xl">
+ <page-title :description="$t('pageNetwork.pageDescription')" />
+ <!-- Global settings for all interfaces -->
+ <network-global-settings />
+ <!-- Interface tabs -->
+ <page-section v-show="ethernetData">
+ <b-row>
+ <b-col>
+ <b-card no-body>
+ <b-tabs
+ active-nav-item-class="font-weight-bold"
+ card
+ content-class="mt-3"
+ >
+ <b-tab
+ v-for="(data, index) in ethernetData"
+ :key="data.Id"
+ :title="data.Id"
+ @click="getTabIndex(index)"
+ >
+ <!-- Interface settings -->
+ <network-interface-settings :tab-index="tabIndex" />
+ <!-- IPV4 table -->
+ <table-ipv-4 :tab-index="tabIndex" />
+ <!-- Static DNS table -->
+ <table-dns :tab-index="tabIndex" />
+ </b-tab>
+ </b-tabs>
+ </b-card>
+ </b-col>
+ </b-row>
+ </page-section>
+ <!-- Modals -->
+ <modal-ipv4 :default-gateway="defaultGateway" @ok="saveIpv4Address" />
+ <modal-dns @ok="saveDnsAddress" />
+ <modal-hostname :hostname="currentHostname" @ok="saveSettings" />
+ <modal-mac-address :mac-address="currentMacAddress" @ok="saveSettings" />
+ </b-container>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import LoadingBarMixin, {
+ loading,
+} from '@/components/_sila/Mixins/LoadingBarMixin';
+import ModalMacAddress from './ModalMacAddress.vue';
+import ModalHostname from './ModalHostname.vue';
+import ModalIpv4 from './ModalIpv4.vue';
+import ModalDns from './ModalDns.vue';
+import NetworkGlobalSettings from './NetworkGlobalSettings.vue';
+import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue';
+import PageSection from '@/components/_sila/Global/PageSection';
+import PageTitle from '@/components/_sila/Global/PageTitle';
+import TableIpv4 from './TableIpv4.vue';
+import TableDns from './TableDns.vue';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Network',
+ components: {
+ ModalHostname,
+ ModalMacAddress,
+ ModalIpv4,
+ ModalDns,
+ NetworkGlobalSettings,
+ NetworkInterfaceSettings,
+ PageSection,
+ PageTitle,
+ TableDns,
+ TableIpv4,
+ },
+ mixins: [BVToastMixin, DataFormatterMixin, LoadingBarMixin],
+ beforeRouteLeave(to, from, next) {
+ this.hideLoader();
+ next();
+ },
+ data() {
+ return {
+ currentHostname: '',
+ currentMacAddress: '',
+ defaultGateway: '',
+ loading,
+ tabIndex: 0,
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ ethernetData() {
+ this.getModalInfo();
+ },
+ },
+ created() {
+ this.startLoader();
+ const globalSettings = new Promise((resolve) => {
+ this.$root.$on('network-global-settings-complete', () => resolve());
+ });
+ const interfaceSettings = new Promise((resolve) => {
+ this.$root.$on('network-interface-settings-complete', () => resolve());
+ });
+ const networkTableDns = new Promise((resolve) => {
+ this.$root.$on('network-table-dns-complete', () => resolve());
+ });
+ const networkTableIpv4 = new Promise((resolve) => {
+ this.$root.$on('network-table-ipv4-complete', () => resolve());
+ });
+ // Combine all child component Promises to indicate
+ // when page data load complete
+ Promise.all([
+ this.$store.dispatch('network/getEthernetData'),
+ globalSettings,
+ interfaceSettings,
+ networkTableDns,
+ networkTableIpv4,
+ ]).finally(() => this.endLoader());
+ },
+ methods: {
+ getModalInfo() {
+ this.defaultGateway = this.$store.getters[
+ 'network/globalNetworkSettings'
+ ][this.tabIndex].defaultGateway;
+
+ this.currentHostname = this.$store.getters[
+ 'network/globalNetworkSettings'
+ ][this.tabIndex].hostname;
+
+ this.currentMacAddress = this.$store.getters[
+ 'network/globalNetworkSettings'
+ ][this.tabIndex].macAddress;
+ },
+ getTabIndex(selectedIndex) {
+ this.tabIndex = selectedIndex;
+ this.$store.dispatch('network/setSelectedTabIndex', this.tabIndex);
+ this.$store.dispatch(
+ 'network/setSelectedTabId',
+ this.ethernetData[selectedIndex].Id
+ );
+ this.getModalInfo();
+ },
+ saveIpv4Address(modalFormData) {
+ this.startLoader();
+ this.$store
+ .dispatch('network/saveIpv4Address', modalFormData)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
+ saveDnsAddress(modalFormData) {
+ this.startLoader();
+ this.$store
+ .dispatch('network/saveDnsAddress', modalFormData)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
+ saveSettings(modalFormData) {
+ this.startLoader();
+ this.$store
+ .dispatch('network/saveSettings', modalFormData)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/NetworkGlobalSettings.vue b/src/views/_sila/Overview/Network/NetworkGlobalSettings.vue
new file mode 100644
index 00000000..44035ae6
--- /dev/null
+++ b/src/views/_sila/Overview/Network/NetworkGlobalSettings.vue
@@ -0,0 +1,161 @@
+<template>
+ <page-section
+ v-if="firstInterface"
+ :section-title="$t('pageNetwork.networkSettings')"
+ >
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>
+ {{ $t('pageNetwork.hostname') }}
+ <b-button variant="link" class="p-1" @click="initSettingsModal()">
+ <icon-edit :title="$t('pageNetwork.modal.editHostnameTitle')" />
+ </b-button>
+ </dt>
+ <dd>{{ dataFormatter(firstInterface.hostname) }}</dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useDomainName') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useDomainNameSwitch"
+ v-model="useDomainNameState"
+ data-test-id="networkSettings-switch-useDomainName"
+ switch
+ @change="changeDomainNameState"
+ >
+ <span v-if="useDomainNameState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useDns') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useDnsSwitch"
+ v-model="useDnsState"
+ data-test-id="networkSettings-switch-useDns"
+ switch
+ @change="changeDnsState"
+ >
+ <span v-if="useDnsState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.useNtp') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="useNtpSwitch"
+ v-model="useNtpState"
+ data-test-id="networkSettings-switch-useNtp"
+ switch
+ @change="changeNtpState"
+ >
+ <span v-if="useNtpState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import IconEdit from '@carbon/icons-vue/es/edit/16';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import PageSection from '@/components/_sila/Global/PageSection';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'GlobalNetworkSettings',
+ components: { IconEdit, PageSection },
+ mixins: [BVToastMixin, DataFormatterMixin],
+
+ data() {
+ return {
+ hostname: '',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ firstInterface() {
+ return this.$store.getters['network/globalNetworkSettings'][0];
+ },
+ useDomainNameState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDomainNameEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useDnsState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDnsEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useNtpState: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useNtpEnabled;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ },
+ created() {
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-global-settings-complete');
+ });
+ },
+ methods: {
+ changeDomainNameState(state) {
+ this.$store
+ .dispatch('network/saveDomainNameState', state)
+ .then((success) => {
+ this.successToast(success);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDnsState(state) {
+ this.$store
+ .dispatch('network/saveDnsState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeNtpState(state) {
+ this.$store
+ .dispatch('network/saveNtpState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ initSettingsModal() {
+ this.$bvModal.show('modal-hostname');
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/NetworkInterfaceSettings.vue b/src/views/_sila/Overview/Network/NetworkInterfaceSettings.vue
new file mode 100644
index 00000000..657a2270
--- /dev/null
+++ b/src/views/_sila/Overview/Network/NetworkInterfaceSettings.vue
@@ -0,0 +1,117 @@
+<template>
+ <div>
+ <page-section>
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.linkStatus') }}</dt>
+ <dd>
+ {{ dataFormatter(linkStatus) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl>
+ <dt>{{ $t('pageNetwork.speed') }}</dt>
+ <dd>
+ {{ dataFormatter(linkSpeed) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+ <page-section :section-title="$t('pageNetwork.interfaceSection')">
+ <b-row>
+ <b-col md="3">
+ <dl>
+ <dt>
+ {{ $t('pageNetwork.fqdn') }}
+ </dt>
+ <dd>
+ {{ dataFormatter(fqdn) }}
+ </dd>
+ </dl>
+ </b-col>
+ <b-col md="3">
+ <dl class="text-nowrap">
+ <dt>
+ {{ $t('pageNetwork.macAddress') }}
+ <b-button
+ variant="link"
+ class="p-1"
+ @click="initMacAddressModal()"
+ >
+ <icon-edit
+ :title="$t('pageNetwork.modal.editMacAddressTitle')"
+ />
+ </b-button>
+ </dt>
+ <dd>
+ {{ dataFormatter(macAddress) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </page-section>
+ </div>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import IconEdit from '@carbon/icons-vue/es/edit/16';
+import PageSection from '@/components/_sila/Global/PageSection';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv4Table',
+ components: {
+ IconEdit,
+ PageSection,
+ },
+ mixins: [BVToastMixin, DataFormatterMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ selectedInterface: '',
+ linkStatus: '',
+ linkSpeed: '',
+ fqdn: '',
+ macAddress: '',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getSettings();
+ },
+ },
+ created() {
+ this.getSettings();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-interface-settings-complete');
+ });
+ },
+ methods: {
+ getSettings() {
+ this.selectedInterface = this.tabIndex;
+ this.linkStatus = this.ethernetData[this.selectedInterface].LinkStatus;
+ this.linkSpeed = this.ethernetData[this.selectedInterface].SpeedMbps;
+ this.fqdn = this.ethernetData[this.selectedInterface].FQDN;
+ this.macAddress = this.ethernetData[this.selectedInterface].MACAddress;
+ },
+ initMacAddressModal() {
+ this.$bvModal.show('modal-mac-address');
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/TableDns.vue b/src/views/_sila/Overview/Network/TableDns.vue
new file mode 100644
index 00000000..3b3cc4b4
--- /dev/null
+++ b/src/views/_sila/Overview/Network/TableDns.vue
@@ -0,0 +1,145 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.staticDns')">
+ <b-row>
+ <b-col lg="6">
+ <div class="text-right">
+ <b-button variant="primary" @click="initDnsModal()">
+ <icon-add />
+ {{ $t('pageNetwork.table.addDnsAddress') }}
+ </b-button>
+ </div>
+ <b-table
+ responsive="md"
+ hover
+ :fields="dnsTableFields"
+ :items="form.dnsStaticTableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item, index }">
+ <table-row-action
+ v-for="(action, actionIndex) in item.actions"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onDnsTableAction(action, $event, index)"
+ >
+ <template #icon>
+ <icon-edit v-if="action.value === 'edit'" />
+ <icon-trashcan v-if="action.value === 'delete'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </b-col>
+ </b-row>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import IconAdd from '@carbon/icons-vue/es/add--alt/20';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import PageSection from '@/components/_sila/Global/PageSection';
+import TableRowAction from '@/components/_sila/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'DNSTable',
+ components: {
+ IconAdd,
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ dnsStaticTableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ dnsTableFields: [
+ {
+ key: 'address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getStaticDnsItems();
+ },
+ ethernetData() {
+ this.getStaticDnsItems();
+ },
+ },
+ created() {
+ this.getStaticDnsItems();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-dns-complete');
+ });
+ },
+ methods: {
+ getStaticDnsItems() {
+ const index = this.tabIndex;
+ const dns = this.ethernetData[index].StaticNameServers || [];
+ this.form.dnsStaticTableItems = dns.map((server) => {
+ return {
+ address: server,
+ actions: [
+ {
+ value: 'delete',
+ title: this.$t('pageNetwork.table.deleteDns'),
+ },
+ ],
+ };
+ });
+ },
+ onDnsTableAction(action, $event, index) {
+ if ($event === 'delete') {
+ this.deleteDnsTableRow(index);
+ }
+ },
+ deleteDnsTableRow(index) {
+ this.form.dnsStaticTableItems.splice(index, 1);
+ const newDnsArray = this.form.dnsStaticTableItems.map((dns) => {
+ return dns.address;
+ });
+ this.$store
+ .dispatch('network/editDnsAddress', newDnsArray)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ initDnsModal() {
+ this.$bvModal.show('modal-dns');
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/TableIpv4.vue b/src/views/_sila/Overview/Network/TableIpv4.vue
new file mode 100644
index 00000000..4f9f9df1
--- /dev/null
+++ b/src/views/_sila/Overview/Network/TableIpv4.vue
@@ -0,0 +1,169 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.ipv4')">
+ <b-row>
+ <b-col>
+ <h3 class="h5">
+ {{ $t('pageNetwork.ipv4Addresses') }}
+ </h3>
+ </b-col>
+ <b-col class="text-right">
+ <b-button variant="primary" @click="initAddIpv4Address()">
+ <icon-add />
+ {{ $t('pageNetwork.table.addIpv4Address') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-table
+ responsive="md"
+ hover
+ :fields="ipv4TableFields"
+ :items="form.ipv4TableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item, index }">
+ <table-row-action
+ v-for="(action, actionIndex) in item.actions"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onIpv4TableAction(action, $event, index)"
+ >
+ <template #icon>
+ <icon-edit v-if="action.value === 'edit'" />
+ <icon-trashcan v-if="action.value === 'delete'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
+import IconAdd from '@carbon/icons-vue/es/add--alt/20';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import LoadingBarMixin from '@/components/_sila/Mixins/LoadingBarMixin';
+import PageSection from '@/components/_sila/Global/PageSection';
+import TableRowAction from '@/components/_sila/Global/TableRowAction';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv4Table',
+ components: {
+ IconAdd,
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin, LoadingBarMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ ipv4TableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ ipv4TableFields: [
+ {
+ key: 'Address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ {
+ key: 'Gateway',
+ label: this.$t('pageNetwork.table.gateway'),
+ },
+ {
+ key: 'SubnetMask',
+ label: this.$t('pageNetwork.table.subnet'),
+ },
+ {
+ key: 'AddressOrigin',
+ label: this.$t('pageNetwork.table.addressOrigin'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getIpv4TableItems();
+ },
+ ethernetData() {
+ this.getIpv4TableItems();
+ },
+ },
+ created() {
+ this.getIpv4TableItems();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-ipv4-complete');
+ });
+ },
+ methods: {
+ getIpv4TableItems() {
+ const index = this.tabIndex;
+ const addresses = this.ethernetData[index].IPv4Addresses || [];
+ this.form.ipv4TableItems = addresses.map((ipv4) => {
+ return {
+ Address: ipv4.Address,
+ SubnetMask: ipv4.SubnetMask,
+ Gateway: ipv4.Gateway,
+ AddressOrigin: ipv4.AddressOrigin,
+ actions: [
+ {
+ value: 'delete',
+ title: this.$t('pageNetwork.table.deleteIpv4'),
+ },
+ ],
+ };
+ });
+ },
+ onIpv4TableAction(action, $event, index) {
+ if ($event === 'delete') {
+ this.deleteIpv4TableRow(index);
+ }
+ },
+ deleteIpv4TableRow(index) {
+ this.form.ipv4TableItems.splice(index, 1);
+ const newIpv4Array = this.form.ipv4TableItems.map((ipv4) => {
+ const { Address, SubnetMask, Gateway } = ipv4;
+ return {
+ Address,
+ SubnetMask,
+ Gateway,
+ };
+ });
+ this.$store
+ .dispatch('network/editIpv4Address', newIpv4Array)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ initAddIpv4Address() {
+ this.$bvModal.show('modal-add-ipv4');
+ },
+ },
+};
+</script>
diff --git a/src/views/_sila/Overview/Network/index.js b/src/views/_sila/Overview/Network/index.js
new file mode 100644
index 00000000..97bf0397
--- /dev/null
+++ b/src/views/_sila/Overview/Network/index.js
@@ -0,0 +1,2 @@
+import Network from './Network.vue';
+export default Network;
diff --git a/src/views/_sila/Overview/Overview.vue b/src/views/_sila/Overview/Overview.vue
index 9960f373..9f97fb3e 100644
--- a/src/views/_sila/Overview/Overview.vue
+++ b/src/views/_sila/Overview/Overview.vue
@@ -26,7 +26,7 @@
</template>
<script>
-import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import LoadingBarMixin from '@/components/_sila/Mixins/LoadingBarMixin';
import OverviewDumps from './OverviewDumps.vue';
import OverviewEvents from './OverviewEvents.vue';
import OverviewFirmware from './OverviewFirmware.vue';
@@ -35,8 +35,8 @@ import OverviewNetwork from './OverviewNetwork';
import OverviewPower from './OverviewPower';
import OverviewQuickLinks from './OverviewQuickLinks';
import OverviewServer from './OverviewServer';
-import PageSection from '@/components/Global/PageSection';
-import PageTitle from '@/components/Global/PageTitle';
+import PageSection from '@/components/_sila/Global/PageSection';
+import PageTitle from '@/components/_sila/Global/PageTitle';
export default {
name: 'Overview',
diff --git a/src/views/_sila/Overview/OverviewDumps.vue b/src/views/_sila/Overview/OverviewDumps.vue
index a2ae4e4e..27f5067d 100644
--- a/src/views/_sila/Overview/OverviewDumps.vue
+++ b/src/views/_sila/Overview/OverviewDumps.vue
@@ -20,7 +20,7 @@
<script>
import OverviewCard from './OverviewCard';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
export default {
name: 'Dumps',
diff --git a/src/views/_sila/Overview/OverviewEvents.vue b/src/views/_sila/Overview/OverviewEvents.vue
index b73c0b48..c54bc5b9 100644
--- a/src/views/_sila/Overview/OverviewEvents.vue
+++ b/src/views/_sila/Overview/OverviewEvents.vue
@@ -32,8 +32,8 @@
<script>
import OverviewCard from './OverviewCard';
-import StatusIcon from '@/components/Global/StatusIcon';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import StatusIcon from '@/components/_sila/Global/StatusIcon';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
export default {
name: 'Events',
diff --git a/src/views/_sila/Overview/OverviewFirmware.vue b/src/views/_sila/Overview/OverviewFirmware.vue
index f1f9ce53..9167c75c 100644
--- a/src/views/_sila/Overview/OverviewFirmware.vue
+++ b/src/views/_sila/Overview/OverviewFirmware.vue
@@ -18,7 +18,7 @@
<script>
import OverviewCard from './OverviewCard';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
export default {
name: 'Firmware',
diff --git a/src/views/_sila/Overview/OverviewNetwork.vue b/src/views/_sila/Overview/OverviewNetwork.vue
index b81e5c73..7010b991 100644
--- a/src/views/_sila/Overview/OverviewNetwork.vue
+++ b/src/views/_sila/Overview/OverviewNetwork.vue
@@ -49,7 +49,7 @@
<script>
import OverviewCard from './OverviewCard';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
export default {
name: 'Network',
diff --git a/src/views/_sila/Overview/OverviewPower.vue b/src/views/_sila/Overview/OverviewPower.vue
index 0d84c76c..ffda495f 100644
--- a/src/views/_sila/Overview/OverviewPower.vue
+++ b/src/views/_sila/Overview/OverviewPower.vue
@@ -24,7 +24,7 @@
<script>
import OverviewCard from './OverviewCard';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
import { mapGetters } from 'vuex';
export default {
diff --git a/src/views/_sila/Overview/OverviewQuickLinks.vue b/src/views/_sila/Overview/OverviewQuickLinks.vue
index bc579b03..6f74fd91 100644
--- a/src/views/_sila/Overview/OverviewQuickLinks.vue
+++ b/src/views/_sila/Overview/OverviewQuickLinks.vue
@@ -27,7 +27,7 @@
<script>
import ArrowRight16 from '@carbon/icons-vue/es/arrow--right/16';
-import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import BVToastMixin from '@/components/_sila/Mixins/BVToastMixin';
export default {
name: 'QuickLinks',
diff --git a/src/views/_sila/Overview/OverviewServer.vue b/src/views/_sila/Overview/OverviewServer.vue
index d066d391..7dded5ba 100644
--- a/src/views/_sila/Overview/OverviewServer.vue
+++ b/src/views/_sila/Overview/OverviewServer.vue
@@ -1,7 +1,7 @@
<template>
<overview-card
:title="$t('pageOverview.serverInformation')"
- :to="`/hardware-status/inventory`"
+ :to="`/hardware-inventory`"
>
<b-row class="mt-3">
<b-col lg="6">
@@ -18,7 +18,7 @@
<script>
import OverviewCard from './OverviewCard';
-import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import DataFormatterMixin from '@/components/_sila/Mixins/DataFormatterMixin';
import { mapState } from 'vuex';
export default {