summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/assets/styles/bmc/custom/_card.scss5
-rw-r--r--src/assets/styles/bmc/custom/_index.scss1
-rw-r--r--src/components/AppNavigation/AppNavigation.vue2
-rw-r--r--src/locales/en-US.json63
-rw-r--r--src/main.js2
-rw-r--r--src/router/index.js8
-rw-r--r--src/store/modules/Configuration/FirmwareStore.js149
-rw-r--r--src/views/Configuration/Firmware/Firmware.vue401
-rw-r--r--src/views/Configuration/Firmware/FirmwareModalRebootBackup.vue33
-rw-r--r--src/views/Configuration/Firmware/FirmwareModalUpload.vue18
-rw-r--r--src/views/Configuration/Firmware/index.js2
-rw-r--r--src/views/Overview/Overview.vue4
12 files changed, 676 insertions, 12 deletions
diff --git a/src/assets/styles/bmc/custom/_card.scss b/src/assets/styles/bmc/custom/_card.scss
new file mode 100644
index 00000000..12721890
--- /dev/null
+++ b/src/assets/styles/bmc/custom/_card.scss
@@ -0,0 +1,5 @@
+.card {
+ .bg-success {
+ background-color: $success-light !important;
+ }
+} \ No newline at end of file
diff --git a/src/assets/styles/bmc/custom/_index.scss b/src/assets/styles/bmc/custom/_index.scss
index 0c393c57..b67712bb 100644
--- a/src/assets/styles/bmc/custom/_index.scss
+++ b/src/assets/styles/bmc/custom/_index.scss
@@ -6,6 +6,7 @@
@import "./bootstrap-grid";
@import "./buttons";
@import "./calendar";
+@import "./card";
@import "./dropdown";
@import "./forms";
@import "./modal";
diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue
index 51b586cd..5101d82a 100644
--- a/src/components/AppNavigation/AppNavigation.vue
+++ b/src/components/AppNavigation/AppNavigation.vue
@@ -105,7 +105,7 @@
{{ $t('appNavigation.dateTimeSettings') }}
</b-nav-item>
<b-nav-item
- href="javascript:void(0)"
+ to="/configuration/firmware"
data-test-id="nav-container-firmware"
>
{{ $t('appNavigation.firmware') }}
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index fa00c86e..5ad6b78b 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -173,6 +173,69 @@
"successDelete": "Successfully deleted %{count} log. | Successfully deleted %{count} logs."
}
},
+ "pageFirmware": {
+ "backup": "Backup:",
+ "backupImage": "Backup image",
+ "bmcStatus": "BMC status",
+ "changeAndRebootBmc": "Change image and reboot BMC",
+ "changeToBackupImage": "Change to backup image",
+ "current": "Current:",
+ "firmwareOnSystem": "Firmware on system",
+ "hostStatus": "Host status",
+ "pageDescription": "Update firmware by uploading a system image file from your workstation or TFTP server",
+ "running": "Running",
+ "state": "State",
+ "updateCode": "Update code",
+ "alert": {
+ "operationInProgress": "Server power operation in progress.",
+ "serverShutdownRequiredBeforeUpdate": "Server shutdown required before update",
+ "serverShutdownRequiredInfo": "Shutdown will be orderly - OS will shutdown before the server shuts down.",
+ "shutDownServer": "Shut down server",
+ "updateProcess": "Update process",
+ "updateProcessInfo": "The new image will be uploaded and activated. After that, the BMC will reboot automatically to run from the new image."
+ },
+ "form": {
+ "imageFile": "Image file",
+ "imageFileName": "Image file name",
+ "onlyTarFilesAccepted": "Only .tar files accepted",
+ "tftpServer": "TFTP server",
+ "tftpServerIpAddress": "TFTP server IP address",
+ "uploadAndRebootBmc": "Upload and reboot BMC",
+ "uploadLocation": "Upload location",
+ "workstation": "Workstation"
+ },
+ "modal": {
+ "connectionToBmcWillBeLost": "Connection to BMC will be lost",
+ "serverShutdownMessage": "There will be a server outage until the server is powered back on. Are you sure you want to shut down?",
+ "serverShutdownWillCauseOutage": "Server shutdown will cause outage",
+ "shutDownServer": "Shut down server",
+ "rebootFromBackup": {
+ "message1": "A BMC reboot is required before the system can run the backup image %{backup}. The reboot will cause a disconnection, and may require logging in again.",
+ "message2": "The current firmware image %{current} will be moved to backup. During the reboot, server cannot be powered back on.",
+ "message3": "Are you sure you want to reboot the BMC from backup image %{backup}?",
+ "primaryAction": "Reboot BMC from backup image",
+ "title": "@:pageFirmware.modal.connectionToBmcWillBeLost"
+ },
+ "uploadAndReboot": {
+ "message1": "A BMC reboot is required before the system can run the new firmware image. The reboot will cause a disconnection, and may require logging in again.",
+ "message2": "During the reboot, the server cannot be powered back on. The backup image will be permanently deleted.",
+ "message3": "Are you sure you want to upload the new firmware image and reboot the BMC?",
+ "primaryAction": "Upload and reboot BMC",
+ "title": "@:pageFirmware.modal.connectionToBmcWillBeLost"
+ }
+ },
+ "toast": {
+ "errorRebootFromBackup": "Error rebooting from backup image.",
+ "errorUploadAndReboot": "Error uploading image.",
+ "infoRefreshApplicationMessage": "Refresh the application to confirm the code update has completed and was successful.",
+ "infoRefreshApplicationTitle": "Verify code update",
+ "infoUploadStartTimeMessage": "Start time: %{startTime}",
+ "infoUploadStartTimeTitle": "Upload started",
+ "successRebootFromBackup": "Successfully started reboot from backup image.",
+ "successUploadMessage": "The upload was successful. During code update, the BMC will be not be responsive. Wait for the code update notification before making any changes.",
+ "successUploadTitle": "Code update started"
+ }
+ },
"pageHardwareStatus": {
"dimmSlot": "DIMM slot",
"fans": "Fans",
diff --git a/src/main.js b/src/main.js
index 8336cb3d..497c751c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,6 +7,7 @@ import {
BadgePlugin,
ButtonPlugin,
BVConfigPlugin,
+ CardPlugin,
CollapsePlugin,
DropdownPlugin,
FormPlugin,
@@ -94,6 +95,7 @@ Vue.use(BVConfigPlugin, {
variant: 'primary'
}
});
+Vue.use(CardPlugin);
Vue.use(CollapsePlugin);
Vue.use(DropdownPlugin);
Vue.use(FormPlugin);
diff --git a/src/router/index.js b/src/router/index.js
index eace2bb6..f3b8d8a3 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -90,6 +90,14 @@ const routes = [
}
},
{
+ path: '/configuration/firmware',
+ name: 'firmware',
+ component: () => import('@/views/Configuration/Firmware'),
+ meta: {
+ title: 'appPageTitle.firmware'
+ }
+ },
+ {
path: '/control/kvm',
name: 'kvm',
component: () => import('@/views/Control/Kvm'),
diff --git a/src/store/modules/Configuration/FirmwareStore.js b/src/store/modules/Configuration/FirmwareStore.js
index 5ec9173a..ead51994 100644
--- a/src/store/modules/Configuration/FirmwareStore.js
+++ b/src/store/modules/Configuration/FirmwareStore.js
@@ -1,27 +1,158 @@
-import api from '../../api';
+import api from '@/store/api';
+import i18n from '@/i18n';
const FirmwareStore = {
namespaced: true,
state: {
- bmcFirmwareVersion: '--'
+ activeFirmware: {
+ version: '--',
+ id: null,
+ location: null
+ },
+ backupFirmware: {
+ version: '--',
+ id: null,
+ location: null,
+ status: '--'
+ },
+ applyTime: null
},
getters: {
- bmcFirmwareVersion: state => state.bmcFirmwareVersion
+ systemFirmwareVersion: state => state.activeFirmware.version,
+ backupFirmwareVersion: state => state.backupFirmware.version,
+ backupFirmwareStatus: state => state.backupFirmware.status,
+ isRebootFromBackupAvailable: state =>
+ state.backupFirmware.id ? true : false
},
mutations: {
- setBmcFirmwareVersion: (state, bmcFirmwareVersion) =>
- (state.bmcFirmwareVersion = bmcFirmwareVersion)
+ setActiveFirmware: (state, { version, id, location }) => {
+ state.activeFirmware.version = version;
+ state.activeFirmware.id = id;
+ state.activeFirmware.location = location;
+ },
+ setBackupFirmware: (state, { version, id, location, status }) => {
+ state.backupFirmware.version = version;
+ state.backupFirmware.id = id;
+ state.backupFirmware.location = location;
+ state.backupFirmware.status = status;
+ },
+ setApplyTime: (state, applyTime) => (state.applyTime = applyTime)
},
actions: {
- async getBmcFirmware({ commit }) {
+ async getSystemFirwareVersion({ commit, state }) {
return await api
.get('/redfish/v1/Managers/bmc')
- .then(response => {
- const bmcFirmwareVersion = response.data.FirmwareVersion;
- commit('setBmcFirmwareVersion', bmcFirmwareVersion);
+ .then(({ data: { Links: { ActiveSoftwareImage } } }) => {
+ const location = ActiveSoftwareImage['@odata.id'];
+ return api.get(location);
})
+ .then(({ data }) => {
+ const version = data.Version;
+ const id = data.Id;
+ const location = data['@odata.id'];
+ commit('setActiveFirmware', { version, id, location });
+ // TODO: temporary workaround to get 'Backup' Firmware
+ // information
+ return api.get('/redfish/v1/UpdateService/FirmwareInventory');
+ })
+ .then(({ data: { Members } }) => {
+ // TODO: temporary workaround to get 'Backup' Firmware
+ // information
+ // Check FirmwareInventory list for not ActiveSoftwareImage id
+ const backupLocation = Members.map(item => item['@odata.id']).find(
+ location => {
+ const id = location.split('/').pop();
+ return id !== state.activeFirmware.id;
+ }
+ );
+ if (backupLocation) {
+ return api.get(backupLocation);
+ }
+ })
+ .then(({ data } = {}) => {
+ if (!data) return;
+ const version = data.Version;
+ const id = data.Id;
+ const location = data['@odata.id'];
+ const status = data.Status ? data.Status.State : '--';
+ commit('setBackupFirmware', { version, id, location, status });
+ })
+ .catch(error => console.log(error));
+ },
+ getUpdateServiceApplyTime({ commit }) {
+ api
+ .get('/redfish/v1/UpdateService')
+ .then(({ data }) => {
+ const applyTime =
+ data.HttpPushUriOptions.HttpPushUriApplyTime.ApplyTime;
+ commit('setApplyTime', applyTime);
+ })
+ .catch(error => console.log(error));
+ },
+ setApplyTimeImmediate({ commit }) {
+ const data = {
+ HttpPushUriOptions: {
+ HttpPushUriApplyTime: {
+ ApplyTime: 'Immediate'
+ }
+ }
+ };
+ return api
+ .patch('/redfish/v1/UpdateService', data)
+ .then(() => commit('setApplyTime', 'Immediate'))
+ .catch(error => console.log(error));
+ },
+ async uploadFirmware({ state, dispatch }, image) {
+ if (state.applyTime !== 'Immediate') {
+ // ApplyTime must be set to Immediate before making
+ // request to update firmware
+ await dispatch('setApplyTimeImmediate');
+ }
+ return await api
+ .post('/redfish/v1/UpdateService', image, {
+ headers: { 'Content-Type': 'application/octet-stream' }
+ })
+ .then(() => dispatch('getSystemFirwareVersion'))
+ .then(() => i18n.t('pageFirmware.toast.successUploadMessage'))
+ .catch(error => {
+ console.log(error);
+ throw new Error(i18n.t('pageFirmware.toast.errorUploadAndReboot'));
+ });
+ },
+ async uploadFirmwareTFTP({ state, dispatch }, { address, filename }) {
+ const data = {
+ TransferProtocol: 'TFTP',
+ ImageURI: `${address}/${filename}`
+ };
+ if (state.applyTime !== 'Immediate') {
+ // ApplyTime must be set to Immediate before making
+ // request to update firmware
+ await dispatch('setApplyTimeImmediate');
+ }
+ return await api
+ .post('/redfish/v1/UpdateService', data)
+ .then(() => dispatch('getSystemFirwareVersion'))
+ .then(() => i18n.t('pageFirmware.toast.successUploadMessage'))
+ .catch(error => {
+ console.log(error);
+ throw new Error(i18n.t('pageFirmware.toast.errorUploadAndReboot'));
+ });
+ },
+ async switchFirmwareAndReboot({ state }) {
+ const backupLoaction = state.backupFirmware.location;
+ const data = {
+ Links: {
+ ActiveSoftwareImage: {
+ '@odata.id': backupLoaction
+ }
+ }
+ };
+ return await api
+ .patch('/redfish/v1/Managers/bmc', data)
+ .then(() => i18n.t('pageFirmware.toast.successRebootFromBackup'))
.catch(error => {
console.log(error);
+ throw new Error(i18n.t('pageFirmware.toast.errorRebootFromBackup'));
});
}
}
diff --git a/src/views/Configuration/Firmware/Firmware.vue b/src/views/Configuration/Firmware/Firmware.vue
new file mode 100644
index 00000000..248b0ab2
--- /dev/null
+++ b/src/views/Configuration/Firmware/Firmware.vue
@@ -0,0 +1,401 @@
+<template>
+ <b-container fluid="xl">
+ <page-title :description="$t('pageFirmware.pageDescription')" />
+ <!-- Operation in progress alert -->
+ <alert v-if="isOperationInProgress" variant="info" class="mb-5">
+ <p>
+ {{ $t('pageFirmware.alert.operationInProgress') }}
+ </p>
+ </alert>
+ <!-- Shutdown server warning alert -->
+ <alert v-else-if="!isHostOff" variant="warning" class="mb-5">
+ <p class="font-weight-bold mb-1">
+ {{ $t('pageFirmware.alert.serverShutdownRequiredBeforeUpdate') }}
+ </p>
+ {{ $t('pageFirmware.alert.serverShutdownRequiredInfo') }}
+ <template v-slot:action>
+ <b-btn variant="link" class="text-nowrap" @click="onClickShutDown">
+ {{ $t('pageFirmware.alert.shutDownServer') }}
+ </b-btn>
+ </template>
+ </alert>
+ <b-row class="mb-4">
+ <!-- Firmware on system -->
+ <b-col md="10" lg="12" xl="8" class="pr-xl-4">
+ <page-section :section-title="$t('pageFirmware.firmwareOnSystem')">
+ <b-card-group deck>
+ <!-- Current FW -->
+ <b-card header-bg-variant="success">
+ <template v-slot:header>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.current') }}</dt>
+ <dd class="mb-0">{{ systemFirmwareVersion }}</dd>
+ </dl>
+ </template>
+ <b-row>
+ <b-col xs="6">
+ <dl class="my-0">
+ <dt>{{ $t('pageFirmware.bmcStatus') }}</dt>
+ <dd>{{ $t('pageFirmware.running') }}</dd>
+ </dl>
+ </b-col>
+ <b-col xs="6">
+ <dl class="my-0">
+ <dt>{{ $t('pageFirmware.hostStatus') }}</dt>
+ <dd v-if="hostStatus === 'on'">
+ {{ $t('global.status.on') }}
+ </dd>
+ <dd v-else-if="hostStatus === 'off'">
+ {{ $t('global.status.off') }}
+ </dd>
+ <dd v-else>
+ {{ $t('global.status.notAvailable') }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-card>
+
+ <!-- Backup FW -->
+ <b-card>
+ <template v-slot:header>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.backup') }}</dt>
+ <dd class="mb-0">{{ backupFirmwareVersion }}</dd>
+ </dl>
+ </template>
+ <b-row>
+ <b-col xs="6">
+ <dl class="my-0">
+ <dt>{{ $t('pageFirmware.state') }}</dt>
+ <dd>{{ backupFirmwareStatus }}</dd>
+ </dl>
+ </b-col>
+ </b-row>
+ </b-card>
+ </b-card-group>
+ </page-section>
+
+ <!-- Change to backup image -->
+ <page-section :section-title="$t('pageFirmware.changeToBackupImage')">
+ <dl class="mb-5">
+ <dt>
+ {{ $t('pageFirmware.backupImage') }}
+ </dt>
+ <dd>{{ backupFirmwareVersion }}</dd>
+ </dl>
+ <b-btn
+ v-b-modal.modal-reboot-backup
+ type="button"
+ variant="primary"
+ :disabled="isPageDisabled || !isRebootFromBackupAvailable"
+ >
+ {{ $t('pageFirmware.changeAndRebootBmc') }}
+ </b-btn>
+ </page-section>
+ </b-col>
+
+ <!-- Update code -->
+ <b-col sm="8" xl="4" class="update-code pl-xl-4">
+ <page-section :section-title="$t('pageFirmware.updateCode')">
+ <b-form @submit.prevent="onSubmitUpload">
+ <b-form-group
+ :label="$t('pageFirmware.form.uploadLocation')"
+ :disabled="isPageDisabled"
+ >
+ <b-form-radio v-model="isWorkstationSelected" :value="true">
+ {{ $t('pageFirmware.form.workstation') }}
+ </b-form-radio>
+ <b-form-radio v-model="isWorkstationSelected" :value="false">
+ {{ $t('pageFirmware.form.tftpServer') }}
+ </b-form-radio>
+ </b-form-group>
+
+ <!-- Workstation Upload -->
+ <template v-if="isWorkstationSelected">
+ <b-form-group
+ :label="$t('pageFirmware.form.imageFile')"
+ label-for="image-file"
+ >
+ <b-form-text id="image-file-help-block">
+ {{ $t('pageFirmware.form.onlyTarFilesAccepted') }}
+ </b-form-text>
+ <b-form-file
+ id="image-file"
+ v-model="file"
+ accept=".tar"
+ aria-describedby="image-file-help-block"
+ :disabled="isPageDisabled"
+ :state="getValidationState($v.file)"
+ @input="$v.file.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ {{ $t('global.form.required') }}
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </template>
+
+ <!-- TFTP Server Upload -->
+ <template v-else>
+ <b-form-group
+ :label="$t('pageFirmware.form.tftpServerIpAddress')"
+ label-for="tftp-ip"
+ >
+ <b-form-input
+ id="tftp-id"
+ v-model="tftpIpAddress"
+ type="text"
+ :browse-text="$t('global.fileUpload.browseText')"
+ :drop-placeholder="$t('global.fileUpload.dropPlaceholder')"
+ :placeholder="$t('global.fileUpload.placeholder')"
+ :state="getValidationState($v.tftpIpAddress)"
+ :disabled="isPageDisabled"
+ @input="$v.tftpIpAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ {{ $t('global.form.fieldRequired') }}
+ </b-form-invalid-feedback>
+ </b-form-group>
+ <b-form-group
+ :label="$t('pageFirmware.form.imageFileName')"
+ label-for="tftp-file-name"
+ >
+ <b-form-input
+ id="tftp-file-name"
+ v-model="tftpFileName"
+ type="text"
+ :state="getValidationState($v.tftpFileName)"
+ :disabled="isPageDisabled"
+ @input="$v.tftpFileName.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ {{ $t('global.form.fieldRequired') }}
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </template>
+
+ <!-- Info alert -->
+ <alert variant="info" class="mt-4 mb-5">
+ <p class="font-weight-bold mb-1">
+ {{ $t('pageFirmware.alert.updateProcess') }}
+ </p>
+ <p>{{ $t('pageFirmware.alert.updateProcessInfo') }}</p>
+ </alert>
+ <b-form-group>
+ <b-btn type="submit" variant="primary" :disabled="isPageDisabled">
+ {{ $t('pageFirmware.form.uploadAndRebootBmc') }}
+ </b-btn>
+ </b-form-group>
+ </b-form>
+ </page-section>
+ </b-col>
+ </b-row>
+
+ <!-- Modals -->
+ <modal-upload @ok="uploadFirmware" />
+ <modal-reboot-backup
+ :current="currentFirmwareVersion"
+ :backup="backupFirmwareVersion"
+ @ok="rebootFromBackup"
+ />
+ </b-container>
+</template>
+
+<script>
+import { requiredIf } from 'vuelidate/lib/validators';
+import { mapGetters } from 'vuex';
+
+import PageSection from '@/components/Global/PageSection';
+import PageTitle from '@/components/Global/PageTitle';
+import Alert from '@/components/Global/Alert';
+import ModalUpload from './FirmwareModalUpload';
+import ModalRebootBackup from './FirmwareModalRebootBackup';
+
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+
+export default {
+ name: 'Firmware',
+ components: {
+ Alert,
+ ModalRebootBackup,
+ ModalUpload,
+ PageSection,
+ PageTitle
+ },
+ mixins: [BVToastMixin, LoadingBarMixin, VuelidateMixin],
+ data() {
+ return {
+ isWorkstationSelected: true,
+ file: null,
+ tftpIpAddress: null,
+ tftpFileName: null,
+ timeoutId: null
+ };
+ },
+ computed: {
+ hostStatus() {
+ return this.$store.getters['global/hostStatus'];
+ },
+ isHostOff() {
+ return this.hostStatus === 'off' ? true : false;
+ },
+ isOperationInProgress() {
+ return this.$store.getters['controls/isOperationInProgress'];
+ },
+ ...mapGetters('firmware', [
+ 'backupFirmwareStatus',
+ 'backupFirmwareVersion',
+ 'isRebootFromBackupAvailable',
+ 'systemFirmwareVersion'
+ ]),
+ isPageDisabled() {
+ return !this.isHostOff || this.loading || this.isOperationInProgress;
+ }
+ },
+ watch: {
+ isWorkstationSelected: function() {
+ this.$v.$reset();
+ this.file = null;
+ this.tftpIpAddress = null;
+ this.tftpFileName = null;
+ }
+ },
+ created() {
+ this.startLoader();
+ this.$store.dispatch('firmware/getUpdateServiceApplyTime');
+ Promise.all([
+ this.$store.dispatch('global/getHostStatus'),
+ this.$store.dispatch('firmware/getSystemFirwareVersion')
+ ]).finally(() => this.endLoader());
+ },
+ beforeRouteLeave(to, from, next) {
+ this.hideLoader();
+ this.clearRebootTimeout();
+ next();
+ },
+ validations() {
+ return {
+ file: {
+ required: requiredIf(function() {
+ return this.isWorkstationSelected;
+ })
+ },
+ tftpIpAddress: {
+ required: requiredIf(function() {
+ return !this.isWorkstationSelected;
+ })
+ },
+ tftpFileName: {
+ required: requiredIf(function() {
+ return !this.isWorkstationSelected;
+ })
+ }
+ };
+ },
+ methods: {
+ uploadFirmware() {
+ const startTime = this.$options.filters.formatTime(new Date());
+ this.setRebootTimeout(360000); //6 minute timeout
+ this.infoToast(
+ this.$t('pageFirmware.toast.infoUploadStartTimeMessage', { startTime }),
+ this.$t('pageFirmware.toast.infoUploadStartTimeTitle')
+ );
+ if (this.isWorkstationSelected) {
+ this.dispatchWorkstationUpload();
+ } else {
+ this.dispatchTftpUpload();
+ }
+ },
+ dispatchWorkstationUpload() {
+ this.$store
+ .dispatch('firmware/uploadFirmware', this.file)
+ .then(success =>
+ this.infoToast(
+ success,
+ this.$t('pageFirmware.toast.successUploadTitle')
+ )
+ )
+ .catch(({ message }) => {
+ this.errorToast(message);
+ this.clearRebootTimeout();
+ });
+ },
+ dispatchTftpUpload() {
+ const data = {
+ address: this.tftpIpAddress,
+ filename: this.tftpFileName
+ };
+ this.$store
+ .dispatch('firmware/uploadFirmwareTFTP', data)
+ .then(success =>
+ this.infoToast(
+ success,
+ this.$t('pageFirmware.toast.successUploadTitle')
+ )
+ )
+ .catch(({ message }) => {
+ this.errorToast(message);
+ this.clearRebootTimeout();
+ });
+ },
+ rebootFromBackup() {
+ this.setRebootTimeout();
+ this.$store
+ .dispatch('firmware/switchFirmwareAndReboot')
+ .then(success =>
+ this.infoToast(success, this.$t('global.status.success'))
+ )
+ .catch(({ message }) => {
+ this.errorToast(message);
+ this.clearRebootTimeout();
+ });
+ },
+ setRebootTimeout(timeoutMs = 60000) {
+ // Set a timeout to disable page interactions while
+ // an upload or BMC reboot is in progress
+ this.startLoader();
+ this.timeoutId = setTimeout(() => {
+ this.endLoader();
+ this.infoToast(
+ this.$t('pageFirmware.toast.infoRefreshApplicationMessage'),
+ this.$t('pageFirmware.toast.infoRefreshApplicationTitle')
+ );
+ }, timeoutMs);
+ },
+ clearRebootTimeout() {
+ if (this.timeoutId) {
+ clearTimeout(this.timeoutId);
+ this.endLoader();
+ }
+ },
+ onSubmitUpload() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$bvModal.show('modal-upload');
+ },
+ onClickShutDown() {
+ this.$bvModal
+ .msgBoxConfirm(this.$t('pageFirmware.modal.serverShutdownMessage'), {
+ title: this.$t('pageFirmware.modal.serverShutdownWillCauseOutage'),
+ okTitle: this.$t('pageFirmware.modal.shutDownServer'),
+ okVariant: 'danger'
+ })
+ .then(shutdownConfirmed => {
+ if (shutdownConfirmed)
+ this.$store.dispatch('controls/hostSoftPowerOff');
+ });
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+.update-code {
+ border-left: none;
+ @include media-breakpoint-up(xl) {
+ border-left: 1px solid gray('300');
+ }
+}
+</style>
diff --git a/src/views/Configuration/Firmware/FirmwareModalRebootBackup.vue b/src/views/Configuration/Firmware/FirmwareModalRebootBackup.vue
new file mode 100644
index 00000000..a8fb3ad5
--- /dev/null
+++ b/src/views/Configuration/Firmware/FirmwareModalRebootBackup.vue
@@ -0,0 +1,33 @@
+<template>
+ <b-modal
+ id="modal-reboot-backup"
+ :ok-title="$t('pageFirmware.modal.rebootFromBackup.primaryAction')"
+ :title="$t('pageFirmware.modal.rebootFromBackup.title')"
+ @ok="$emit('ok')"
+ >
+ <p>
+ {{ $t('pageFirmware.modal.rebootFromBackup.message1', { backup }) }}
+ </p>
+ <p>
+ {{ $t('pageFirmware.modal.rebootFromBackup.message2', { current }) }}
+ </p>
+ <p class="font-weight-bold">
+ {{ $t('pageFirmware.modal.rebootFromBackup.message3', { backup }) }}
+ </p>
+ </b-modal>
+</template>
+
+<script>
+export default {
+ props: {
+ current: {
+ type: String,
+ required: true
+ },
+ backup: {
+ type: String,
+ required: true
+ }
+ }
+};
+</script>
diff --git a/src/views/Configuration/Firmware/FirmwareModalUpload.vue b/src/views/Configuration/Firmware/FirmwareModalUpload.vue
new file mode 100644
index 00000000..d092becd
--- /dev/null
+++ b/src/views/Configuration/Firmware/FirmwareModalUpload.vue
@@ -0,0 +1,18 @@
+<template>
+ <b-modal
+ id="modal-upload"
+ :title="$t('pageFirmware.modal.uploadAndReboot.title')"
+ :ok-title="$t('pageFirmware.modal.uploadAndReboot.primaryAction')"
+ @ok="$emit('ok')"
+ >
+ <p>
+ {{ $t('pageFirmware.modal.uploadAndReboot.message1') }}
+ </p>
+ <p>
+ {{ $t('pageFirmware.modal.uploadAndReboot.message2') }}
+ </p>
+ <p class="font-weight-bold">
+ {{ $t('pageFirmware.modal.uploadAndReboot.message3') }}
+ </p>
+ </b-modal>
+</template>
diff --git a/src/views/Configuration/Firmware/index.js b/src/views/Configuration/Firmware/index.js
new file mode 100644
index 00000000..ad15cc03
--- /dev/null
+++ b/src/views/Configuration/Firmware/index.js
@@ -0,0 +1,2 @@
+import Firmware from './Firmware.vue';
+export default Firmware;
diff --git a/src/views/Overview/Overview.vue b/src/views/Overview/Overview.vue
index 46944cc8..ac484816 100644
--- a/src/views/Overview/Overview.vue
+++ b/src/views/Overview/Overview.vue
@@ -106,7 +106,7 @@ export default {
mixins: [LoadingBarMixin],
computed: mapState({
server: state => state.system.systems[0],
- bmcFirmwareVersion: state => state.firmware.bmcFirmwareVersion,
+ bmcFirmwareVersion: state => state.firmware.activeFirmware.version,
powerCapValue: state => state.powerControl.powerCapValue,
powerConsumptionValue: state => state.powerControl.powerConsumptionValue,
serverManufacturer() {
@@ -139,7 +139,7 @@ export default {
});
Promise.all([
this.$store.dispatch('system/getSystem'),
- this.$store.dispatch('firmware/getBmcFirmware'),
+ this.$store.dispatch('firmware/getSystemFirwareVersion'),
this.$store.dispatch('powerControl/getPowerControl'),
quicklinksPromise,
networkPromise,