diff options
author | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-07-15 20:30:31 +0300 |
---|---|---|
committer | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-08-13 19:32:03 +0300 |
commit | 92a0a4ac1f25271c1861d7c97a0d8344dd578905 (patch) | |
tree | d028a659a04223c6659a201044cee1f490bbf0ca /src/views/Configuration | |
parent | e9fc6125da79176d32aa0d0b89e36aff67347fc8 (diff) | |
download | webui-vue-92a0a4ac1f25271c1861d7c97a0d8344dd578905.tar.xz |
Add Firmware page
Adds ability to upload a fimware image by local workstation
or TFTP. Also adds ability to reboot BMC from the backup image.
- Add route definition, component view, and store for
Firmware page
- Get ActiveSoftwareImage location at /redfish/v1/Managers/bmc
- Get backup by checking for an image id that is not the same as
the active image /redfish/v1/UpdateService/FirmwareInventory
- Switch running firmware image by making PATCH request to
/redfish/v1/Managers/bmc
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I04450e5a170d374122908c4c0349ba3b6e93ed2c
Diffstat (limited to 'src/views/Configuration')
-rw-r--r-- | src/views/Configuration/Firmware/Firmware.vue | 401 | ||||
-rw-r--r-- | src/views/Configuration/Firmware/FirmwareModalRebootBackup.vue | 33 | ||||
-rw-r--r-- | src/views/Configuration/Firmware/FirmwareModalUpload.vue | 18 | ||||
-rw-r--r-- | src/views/Configuration/Firmware/index.js | 2 |
4 files changed, 454 insertions, 0 deletions
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; |