summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYoshie Muranaka <yoshiemuranaka@gmail.com>2021-02-11 20:59:13 +0300
committerDerick Montague <derick.montague@ibm.com>2021-02-25 18:06:40 +0300
commit7bc85e4936f1fe0178bae3bedcdc11c5e00564d6 (patch)
treeb1bfd50d875b2b7442b4d816bdf989caa3852cdc /src
parenta36a32f112b2554109502c569fb4b9d64e88053d (diff)
downloadwebui-vue-7bc85e4936f1fe0178bae3bedcdc11c5e00564d6.tar.xz
Update single file firmware card layout
Updates will enable a more dynamic firmware page layout by inspecting all firmware images for host images. GETs all firmware inventory at Redfish endpoint '/redfish/v1/UpdateService/FirmwareInventory' then checks if any image includes RelatedItem '/redfish/v1/Systems/system/Bios' to determine whether the UI should show combined or separate firmware cards for BMC and Host. This is part of an effort to make the firmware page more dynamic. These changes are only visisble with ibm dotenv variables. Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com> Change-Id: I8542a27c6ff421bcb24c8b2570dbe150d5c1ce6c
Diffstat (limited to 'src')
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue60
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareCardsBmc.vue145
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareCardsHost.vue75
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareFormUpdate.vue235
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareModalSwitchToRunning.vue (renamed from src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue)0
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareModalUpdateFirmware.vue48
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue389
-rw-r--r--src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue44
-rw-r--r--src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js146
-rw-r--r--src/locales/en-US.json32
10 files changed, 712 insertions, 462 deletions
diff --git a/src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue b/src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue
new file mode 100644
index 00000000..f7ac0fc9
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue
@@ -0,0 +1,60 @@
+<template>
+ <b-row>
+ <b-col xl="10">
+ <!-- Operation in progress alert -->
+ <alert v-if="isOperationInProgress" variant="info" class="mb-5">
+ <p>
+ {{ $t('pageFirmware.singleFileUpload.alert.operationInProgress') }}
+ </p>
+ </alert>
+ <!-- Power off server warning alert -->
+ <alert v-else-if="!isHostOff" variant="warning" class="mb-5">
+ <p class="mb-0">
+ {{
+ $t('pageFirmware.singleFileUpload.alert.serverMustBePoweredOffTo')
+ }}
+ </p>
+ <ul class="m-0">
+ <li>
+ {{
+ $t(
+ 'pageFirmware.singleFileUpload.alert.switchRunningAndBackupImages'
+ )
+ }}
+ </li>
+ <li>
+ {{ $t('pageFirmware.singleFileUpload.alert.updateFirmware') }}
+ </li>
+ </ul>
+ <template #action>
+ <b-link to="/control/server-power-operations">
+ {{
+ $t(
+ 'pageFirmware.singleFileUpload.alert.viewServerPowerOperations'
+ )
+ }}
+ </b-link>
+ </template>
+ </alert>
+ </b-col>
+ </b-row>
+</template>
+
+<script>
+import Alert from '@/components/Global/Alert';
+
+export default {
+ components: { Alert },
+ props: {
+ isHostOff: {
+ required: true,
+ type: Boolean,
+ },
+ },
+ computed: {
+ isOperationInProgress() {
+ return this.$store.getters['controls/isOperationInProgress'];
+ },
+ },
+};
+</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareCardsBmc.vue b/src/env/components/FirmwareSingleImage/FirmwareCardsBmc.vue
new file mode 100644
index 00000000..857adf0f
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareCardsBmc.vue
@@ -0,0 +1,145 @@
+<template>
+ <div>
+ <page-section :section-title="sectionTitle">
+ <b-card-group deck>
+ <!-- Running image -->
+ <b-card>
+ <template #header>
+ <p class="font-weight-bold m-0">
+ {{ $t('pageFirmware.singleFileUpload.cardTitleRunning') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.singleFileUpload.cardBodyVersion') }}</dt>
+ <dd class="mb-0">{{ runningVersion }}</dd>
+ </dl>
+ </b-card>
+
+ <!-- Backup image -->
+ <b-card>
+ <template #header>
+ <p class="font-weight-bold m-0">
+ {{ $t('pageFirmware.singleFileUpload.cardTitleBackup') }}
+ </p>
+ </template>
+ <dl>
+ <dt>{{ $t('pageFirmware.singleFileUpload.cardBodyVersion') }}</dt>
+ <dd>
+ <status-icon v-if="showBackupImageStatus" status="danger" />
+ <span v-if="showBackupImageStatus" class="sr-only">
+ {{ backupStatus }}
+ </span>
+ {{ backupVersion }}
+ </dd>
+ </dl>
+ <b-btn
+ v-b-modal.modal-switch-to-running
+ data-test-id="firmware-button-switchToRunning"
+ variant="link"
+ size="sm"
+ class="py-0 px-1 mt-2"
+ :disabled="isPageDisabled || !backup"
+ >
+ <icon-switch class="d-none d-sm-inline-block" />
+ {{ $t('pageFirmware.singleFileUpload.cardActionSwitchToRunning') }}
+ </b-btn>
+ </b-card>
+ </b-card-group>
+ </page-section>
+ <modal-switch-to-running :backup="backupVersion" @ok="switchToRunning" />
+ </div>
+</template>
+
+<script>
+import IconSwitch from '@carbon/icons-vue/es/arrows--horizontal/20';
+import PageSection from '@/components/Global/PageSection';
+import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+
+import ModalSwitchToRunning from './FirmwareModalSwitchToRunning';
+
+export default {
+ components: { IconSwitch, ModalSwitchToRunning, PageSection },
+ mixins: [BVToastMixin, LoadingBarMixin],
+ props: {
+ isPageDisabled: {
+ required: true,
+ type: Boolean,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ loading,
+ };
+ },
+ computed: {
+ isSingleFileUploadEnabled() {
+ return this.$store.getters[
+ 'firmwareSingleImage/isSingleFileUploadEnabled'
+ ];
+ },
+ sectionTitle() {
+ if (this.isSingleFileUploadEnabled) {
+ return this.$t(
+ 'pageFirmware.singleFileUpload.sectionTitleBmcCardsCombined'
+ );
+ }
+ return this.$t('pageFirmware.singleFileUpload.sectionTitleBmcCards');
+ },
+ running() {
+ return this.$store.getters['firmwareSingleImage/activeBmcFirmware'];
+ },
+ backup() {
+ return this.$store.getters['firmwareSingleImage/backupBmcFirmware'];
+ },
+ runningVersion() {
+ return this.running?.version || '--';
+ },
+ backupVersion() {
+ return this.backup?.version || '--';
+ },
+ backupStatus() {
+ return this.backup?.status || null;
+ },
+ showBackupImageStatus() {
+ return (
+ this.backupStatus === 'Critical' || this.backupStatus === 'Warning'
+ );
+ },
+ },
+ methods: {
+ switchToRunning() {
+ this.startLoader();
+ const timerId = setTimeout(() => {
+ this.endLoader();
+ this.infoToast(
+ this.$t('pageFirmware.singleFileUpload.toast.verifySwitchMessage'),
+ {
+ title: this.$t('pageFirmware.singleFileUpload.toast.verifySwitch'),
+ refreshAction: true,
+ }
+ );
+ }, 60000);
+
+ this.$store
+ .dispatch('firmwareSingleImage/switchFirmwareAndReboot')
+ .then(() =>
+ this.infoToast(
+ this.$t('pageFirmware.singleFileUpload.toast.rebootStartedMessage'),
+ {
+ title: this.$t(
+ 'pageFirmware.singleFileUpload.toast.rebootStarted'
+ ),
+ }
+ )
+ )
+ .catch(({ message }) => {
+ this.errorToast(message);
+ clearTimeout(timerId);
+ this.endLoader();
+ });
+ },
+ },
+};
+</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareCardsHost.vue b/src/env/components/FirmwareSingleImage/FirmwareCardsHost.vue
new file mode 100644
index 00000000..c47f60f5
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareCardsHost.vue
@@ -0,0 +1,75 @@
+<template>
+ <page-section
+ :section-title="$t('pageFirmware.singleFileUpload.sectionTitleHostCards')"
+ >
+ <b-card-group deck>
+ <!-- Running image -->
+ <b-card>
+ <template #header>
+ <p class="font-weight-bold m-0">
+ {{ $t('pageFirmware.singleFileUpload.cardTitleRunning') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.singleFileUpload.cardBodyVersion') }}</dt>
+ <dd class="mb-0">{{ runningVersion }}</dd>
+ </dl>
+ </b-card>
+
+ <!-- Backup image -->
+ <b-card>
+ <template #header>
+ <p class="font-weight-bold m-0">
+ {{ $t('pageFirmware.singleFileUpload.cardTitleBackup') }}
+ </p>
+ </template>
+ <dl class="mb-0">
+ <dt>{{ $t('pageFirmware.singleFileUpload.cardBodyVersion') }}</dt>
+ <dd class="mb-0">
+ <status-icon v-if="showBackupImageStatus" status="danger" />
+ <span v-if="showBackupImageStatus" class="sr-only">
+ {{ backupStatus }}
+ </span>
+ {{ backupVersion }}
+ </dd>
+ </dl>
+ </b-card>
+ </b-card-group>
+ </page-section>
+</template>
+
+<script>
+import PageSection from '@/components/Global/PageSection';
+
+export default {
+ components: { PageSection },
+ computed: {
+ running() {
+ return this.$store.getters['firmwareSingleImage/activeHostFirmware'];
+ },
+ backup() {
+ return this.$store.getters['firmwareSingleImage/backupHostFirmware'];
+ },
+ runningVersion() {
+ return this.running?.version || '--';
+ },
+ backupVersion() {
+ return this.backup?.version || '--';
+ },
+ backupStatus() {
+ return this.backup?.status || null;
+ },
+ showBackupImageStatus() {
+ return (
+ this.backupStatus === 'Critical' || this.backupStatus === 'Warning'
+ );
+ },
+ },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-section {
+ margin-top: -$spacer * 1.5;
+}
+</style>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareFormUpdate.vue b/src/env/components/FirmwareSingleImage/FirmwareFormUpdate.vue
new file mode 100644
index 00000000..f13b8e00
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareFormUpdate.vue
@@ -0,0 +1,235 @@
+<template>
+ <div>
+ <div class="form-background p-3">
+ <b-form @submit.prevent="onSubmitUpload">
+ <b-form-group
+ v-if="isTftpUploadAvailable"
+ :label="
+ $t('pageFirmware.singleFileUpload.form.updateFirmware.fileSource')
+ "
+ :disabled="isPageDisabled"
+ >
+ <b-form-radio v-model="isWorkstationSelected" :value="true">
+ {{
+ $t(
+ 'pageFirmware.singleFileUpload.form.updateFirmware.workstation'
+ )
+ }}
+ </b-form-radio>
+ <b-form-radio v-model="isWorkstationSelected" :value="false">
+ {{
+ $t('pageFirmware.singleFileUpload.form.updateFirmware.tftpServer')
+ }}
+ </b-form-radio>
+ </b-form-group>
+
+ <!-- Workstation Upload -->
+ <template v-if="isWorkstationSelected">
+ <b-form-group
+ :label="
+ $t('pageFirmware.singleFileUpload.form.updateFirmware.imageFile')
+ "
+ label-for="image-file"
+ >
+ <b-form-text id="image-file-help-block">
+ {{
+ $t(
+ 'pageFirmware.singleFileUpload.form.updateFirmware.imageFileHelperText'
+ )
+ }}
+ </b-form-text>
+ <form-file
+ id="image-file"
+ accept=".tar"
+ :disabled="isPageDisabled"
+ :state="getValidationState($v.file)"
+ aria-describedby="image-file-help-block"
+ @input="onFileUpload($event)"
+ >
+ <template #invalid>
+ <b-form-invalid-feedback role="alert">
+ {{ $t('global.form.required') }}
+ </b-form-invalid-feedback>
+ </template>
+ </form-file>
+ </b-form-group>
+ </template>
+
+ <!-- TFTP Server Upload -->
+ <template v-else>
+ <b-form-group
+ :label="
+ $t(
+ 'pageFirmware.singleFileUpload.form.updateFirmware.fileAddress'
+ )
+ "
+ label-for="tftp-address"
+ >
+ <b-form-input
+ id="tftp-address"
+ v-model="tftpFileAddress"
+ type="text"
+ :state="getValidationState($v.tftpFileAddress)"
+ :disabled="isPageDisabled"
+ @input="$v.tftpFileAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ {{ $t('global.form.fieldRequired') }}
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </template>
+ <b-btn
+ data-test-id="firmware-button-startUpdate"
+ type="submit"
+ variant="primary"
+ :disabled="isPageDisabled"
+ >
+ {{
+ $t('pageFirmware.singleFileUpload.form.updateFirmware.startUpdate')
+ }}
+ </b-btn>
+ <alert
+ v-if="isServerPowerOffRequired && !isHostOff"
+ variant="warning"
+ :small="true"
+ class="mt-4"
+ >
+ <p class="col-form-label">
+ {{
+ $t(
+ 'pageFirmware.singleFileUpload.alert.serverMustBePoweredOffToUpdateFirmware'
+ )
+ }}
+ </p>
+ </alert>
+ </b-form>
+ </div>
+
+ <!-- Modals -->
+ <modal-update-firmware @ok="updateFirmware" />
+ </div>
+</template>
+
+<script>
+import { requiredIf } from 'vuelidate/lib/validators';
+
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+
+import Alert from '@/components/Global/Alert';
+import FormFile from '@/components/Global/FormFile';
+import ModalUpdateFirmware from './FirmwareModalUpdateFirmware';
+
+export default {
+ components: { Alert, FormFile, ModalUpdateFirmware },
+ mixins: [BVToastMixin, LoadingBarMixin, VuelidateMixin],
+ props: {
+ isPageDisabled: {
+ required: true,
+ type: Boolean,
+ default: false,
+ },
+ isHostOff: {
+ required: true,
+ type: Boolean,
+ },
+ },
+ data() {
+ return {
+ loading,
+ isWorkstationSelected: true,
+ file: null,
+ tftpFileAddress: null,
+ isServerPowerOffRequired:
+ process.env.VUE_APP_SERVER_OFF_REQUIRED === 'true',
+ };
+ },
+ computed: {
+ isTftpUploadAvailable() {
+ return this.$store.getters['firmwareSingleImage/isTftpUploadAvailable'];
+ },
+ },
+ watch: {
+ isWorkstationSelected: function () {
+ this.$v.$reset();
+ this.file = null;
+ this.tftpFileAddress = null;
+ },
+ },
+ validations() {
+ return {
+ file: {
+ required: requiredIf(function () {
+ return this.isWorkstationSelected;
+ }),
+ },
+ tftpFileAddress: {
+ required: requiredIf(function () {
+ return !this.isWorkstationSelected;
+ }),
+ },
+ };
+ },
+ created() {
+ this.$store.dispatch('firmwareSingleImage/getUpdateServiceSettings');
+ },
+ methods: {
+ updateFirmware() {
+ this.startLoader();
+ const timerId = setTimeout(() => {
+ this.endLoader();
+ this.infoToast(
+ this.$t('pageFirmware.singleFileUpload.toast.verifyUpdateMessage'),
+ {
+ title: this.$t('pageFirmware.singleFileUpload.toast.verifyUpdate'),
+ refreshAction: true,
+ }
+ );
+ }, 360000);
+ this.infoToast(
+ this.$t('pageFirmware.singleFileUpload.toast.updateStartedMessage'),
+ {
+ title: this.$t('pageFirmware.singleFileUpload.toast.updateStarted'),
+ timestamp: true,
+ }
+ );
+ if (this.isWorkstationSelected) {
+ this.dispatchWorkstationUpload(timerId);
+ } else {
+ this.dispatchTftpUpload(timerId);
+ }
+ },
+ dispatchWorkstationUpload(timerId) {
+ this.$store
+ .dispatch('firmwareSingleImage/uploadFirmware', this.file)
+ .catch(({ message }) => {
+ this.endLoader();
+ this.errorToast(message);
+ clearTimeout(timerId);
+ });
+ },
+ dispatchTftpUpload(timerId) {
+ this.$store
+ .dispatch(
+ 'firmwareSingleImage/uploadFirmwareTFTP',
+ this.tftpFileAddress
+ )
+ .catch(({ message }) => {
+ this.endLoader();
+ this.errorToast(message);
+ clearTimeout(timerId);
+ });
+ },
+ onSubmitUpload() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$bvModal.show('modal-update-firmware');
+ },
+ onFileUpload(file) {
+ this.file = file;
+ this.$v.file.$touch();
+ },
+ },
+};
+</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue b/src/env/components/FirmwareSingleImage/FirmwareModalSwitchToRunning.vue
index 56f505d0..56f505d0 100644
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue
+++ b/src/env/components/FirmwareSingleImage/FirmwareModalSwitchToRunning.vue
diff --git a/src/env/components/FirmwareSingleImage/FirmwareModalUpdateFirmware.vue b/src/env/components/FirmwareSingleImage/FirmwareModalUpdateFirmware.vue
new file mode 100644
index 00000000..d6c52f9c
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareModalUpdateFirmware.vue
@@ -0,0 +1,48 @@
+<template>
+ <b-modal
+ id="modal-update-firmware"
+ :title="$t('pageFirmware.singleFileUpload.sectionTitleUpdateFirmware')"
+ :ok-title="
+ $t('pageFirmware.singleFileUpload.form.updateFirmware.startUpdate')
+ "
+ :cancel-title="$t('global.action.cancel')"
+ @ok="$emit('ok')"
+ >
+ <template v-if="isSingleFileUploadEnabled">
+ <p>
+ {{ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo') }}
+ </p>
+ <p>
+ {{
+ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo2', {
+ running: runningBmcVersion,
+ })
+ }}
+ </p>
+ <p class="m-0">
+ {{ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo3') }}
+ </p>
+ </template>
+ <template v-else>
+ {{ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfoDefault') }}
+ </template>
+ </b-modal>
+</template>
+
+<script>
+export default {
+ computed: {
+ runningBmc() {
+ return this.$store.getters['firmwareSingleImage/activeBmcFirmware'];
+ },
+ runningBmcVersion() {
+ return this.runningBmc?.version || '--';
+ },
+ isSingleFileUploadEnabled() {
+ return this.$store.getters[
+ 'firmwareSingleImage/isSingleFileUploadEnabled'
+ ];
+ },
+ },
+};
+</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
index b7ad4c53..2e601bd4 100644
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
+++ b/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
@@ -1,244 +1,68 @@
<template>
<b-container fluid="xl">
<page-title />
- <b-row v-if="isServerPowerOffRequired">
- <b-col xl="10">
- <!-- Operation in progress alert -->
- <alert v-if="isOperationInProgress" variant="info" class="mb-5">
- <p>
- {{ $t('pageFirmware.singleFileUpload.alert.operationInProgress') }}
- </p>
- </alert>
- <!-- Power off server warning alert -->
- <alert v-else-if="!isHostOff" variant="warning" class="mb-5">
- <p class="mb-0">
- {{
- $t('pageFirmware.singleFileUpload.alert.serverMustBePoweredOffTo')
- }}
- </p>
- <ul class="m-0">
- <li>
- {{
- $t(
- 'pageFirmware.singleFileUpload.alert.switchRunningAndBackupImages'
- )
- }}
- </li>
- <li>
- {{ $t('pageFirmware.singleFileUpload.alert.updateFirmware') }}
- </li>
- </ul>
- <template #action>
- <b-link to="/control/server-power-operations">
- {{
- $t(
- 'pageFirmware.singleFileUpload.alert.viewServerPowerOperations'
- )
- }}
- </b-link>
- </template>
- </alert>
- </b-col>
- </b-row>
- <b-row>
- <b-col xl="10">
- <page-section>
- <b-card-group deck>
- <!-- Running image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.singleFileUpload.runningImage') }}
- </p>
- </template>
- <dl class="mb-0">
- <dt>{{ $t('pageFirmware.singleFileUpload.bmcAndServer') }}</dt>
- <dd class="mb-0">{{ systemFirmwareVersion }}</dd>
- </dl>
- </b-card>
+ <alerts-server-power
+ v-if="isServerPowerOffRequired"
+ :is-host-off="isHostOff"
+ />
- <!-- Backup image -->
- <b-card>
- <template #header>
- <p class="font-weight-bold m-0">
- {{ $t('pageFirmware.singleFileUpload.backupImage') }}
- </p>
- </template>
- <dl>
- <dt>
- {{ $t('pageFirmware.singleFileUpload.bmcAndServer') }}
- </dt>
- <dd>
- <status-icon v-if="showBackupImageStatus" status="danger" />
- <span v-if="showBackupImageStatus" class="sr-only">
- {{ backupFirmwareStatus }}
- </span>
- {{ backupFirmwareVersion }}
- </dd>
- </dl>
- <b-btn
- v-b-modal.modal-switch-to-running
- data-test-id="firmware-button-switchToRunning"
- variant="link"
- size="sm"
- class="py-0 px-1 mt-2"
- :disabled="isPageDisabled || !isRebootFromBackupAvailable"
- >
- <icon-switch class="d-none d-sm-inline-block" />
- {{ $t('pageFirmware.singleFileUpload.switchToRunning') }}
- </b-btn>
- </b-card>
- </b-card-group>
- </page-section>
- </b-col>
- </b-row>
+ <!-- Firmware cards -->
<b-row>
- <!-- Update firmware -->
- <b-col sm="8" md="6" xl="4">
- <page-section
- :section-title="$t('pageFirmware.singleFileUpload.updateFirmware')"
- >
- <div class="form-background p-3">
- <b-form @submit.prevent="onSubmitUpload">
- <b-form-group
- v-if="isTftpUploadAvailable"
- :label="$t('pageFirmware.singleFileUpload.fileSource')"
- :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>
- <form-file
- id="image-file"
- accept=".tar"
- :disabled="isPageDisabled"
- :state="getValidationState($v.file)"
- aria-describedby="image-file-help-block"
- @input="onFileUpload($event)"
- >
- <template #invalid>
- <b-form-invalid-feedback role="alert">
- {{ $t('global.form.required') }}
- </b-form-invalid-feedback>
- </template>
- </form-file>
- </b-form-group>
- </template>
+ <b-col xl="10">
+ <!-- BMC Firmware -->
+ <bmc-cards :is-page-disabled="isPageDisabled" />
- <!-- TFTP Server Upload -->
- <template v-else>
- <b-form-group
- :label="$t('pageFirmware.singleFileUpload.fileAddress')"
- label-for="tftp-address"
- >
- <b-form-input
- id="tftp-address"
- v-model="tftpFileAddress"
- type="text"
- :state="getValidationState($v.tftpFileAddress)"
- :disabled="isPageDisabled"
- @input="$v.tftpFileAddress.$touch()"
- />
- <b-form-invalid-feedback role="alert">
- {{ $t('global.form.fieldRequired') }}
- </b-form-invalid-feedback>
- </b-form-group>
- </template>
- <b-btn
- data-test-id="firmware-button-startUpdate"
- type="submit"
- variant="primary"
- :disabled="isPageDisabled"
- >
- {{ $t('pageFirmware.singleFileUpload.startUpdate') }}
- </b-btn>
- <alert
- v-if="isServerPowerOffRequired && !isHostOff"
- variant="warning"
- :small="true"
- class="mt-4"
- >
- <p class="col-form-label">
- {{
- $t(
- 'pageFirmware.singleFileUpload.alert.serverMustBePoweredOffToUpdateFirmware'
- )
- }}
- </p>
- </alert>
- </b-form>
- </div>
- </page-section>
+ <!-- Host Firmware -->
+ <host-cards v-if="!isSingleFileUploadEnabled" />
</b-col>
</b-row>
- <!-- Modals -->
- <modal-update-firmware
- :running="systemFirmwareVersion"
- :backup="backupFirmwareVersion"
- @ok="updateFirmware"
- />
- <modal-switch-to-running
- :backup="backupFirmwareVersion"
- @ok="switchToRunning"
- />
+ <!-- Update firmware-->
+ <page-section
+ :section-title="
+ $t('pageFirmware.singleFileUpload.sectionTitleUpdateFirmware')
+ "
+ >
+ <b-row>
+ <b-col sm="8" md="6" xl="4">
+ <!-- Update form -->
+ <form-update
+ :is-host-off="isHostOff"
+ :is-page-disabled="isPageDisabled"
+ />
+ </b-col>
+ </b-row>
+ </page-section>
</b-container>
</template>
<script>
-import { requiredIf } from 'vuelidate/lib/validators';
-import { mapGetters } from 'vuex';
-import IconSwitch from '@carbon/icons-vue/es/arrows--horizontal/20';
-
+import AlertsServerPower from './FirmwareAlertServerPower';
+import BmcCards from './FirmwareCardsBmc';
+import FormUpdate from './FirmwareFormUpdate';
+import HostCards from './FirmwareCardsHost';
import PageSection from '@/components/Global/PageSection';
import PageTitle from '@/components/Global/PageTitle';
-import Alert from '@/components/Global/Alert';
-import FormFile from '@/components/Global/FormFile';
-import StatusIcon from '@/components/Global/StatusIcon';
-import ModalUpdateFirmware from './FirmwareSingleImageModalUpdateFirmware';
-import ModalSwitchToRunning from './FirmwareSingleImageModalSwitchToRunning';
-import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
-import BVToastMixin from '@/components/Mixins/BVToastMixin';
export default {
name: 'FirmwareSingleImage',
components: {
- Alert,
- FormFile,
- IconSwitch,
- ModalSwitchToRunning,
- ModalUpdateFirmware,
+ AlertsServerPower,
+ BmcCards,
+ FormUpdate,
+ HostCards,
PageSection,
PageTitle,
- StatusIcon,
},
- mixins: [BVToastMixin, LoadingBarMixin, VuelidateMixin],
+ mixins: [LoadingBarMixin],
beforeRouteLeave(to, from, next) {
this.hideLoader();
next();
},
data() {
return {
- isWorkstationSelected: true,
- file: null,
- tftpFileAddress: null,
- timeoutId: null,
loading,
isServerPowerOffRequired:
process.env.VUE_APP_SERVER_OFF_REQUIRED === 'true',
@@ -251,152 +75,23 @@ export default {
isHostOff() {
return this.hostStatus === 'off' ? true : false;
},
- isOperationInProgress() {
- return this.$store.getters['controls/isOperationInProgress'];
+ isSingleFileUploadEnabled() {
+ return this.$store.getters[
+ 'firmwareSingleImage/isSingleFileUploadEnabled'
+ ];
},
- ...mapGetters('firmwareSingleImage', [
- 'backupFirmwareStatus',
- 'backupFirmwareVersion',
- 'isRebootFromBackupAvailable',
- 'systemFirmwareVersion',
- 'isTftpUploadAvailable',
- ]),
isPageDisabled() {
if (this.isServerPowerOffRequired) {
return !this.isHostOff || this.loading || this.isOperationInProgress;
}
return this.loading || this.isOperationInProgress;
},
- showBackupImageStatus() {
- return (
- this.backupFirmwareStatus === 'Critical' ||
- this.backupFirmwareStatus === 'Warning'
- );
- },
- },
- watch: {
- isWorkstationSelected: function () {
- this.$v.$reset();
- this.file = null;
- this.tftpFileAddress = null;
- },
},
created() {
this.startLoader();
- this.$store.dispatch('firmwareSingleImage/getUpdateServiceSettings');
- Promise.all([
- this.$store.dispatch('global/getHostStatus'),
- this.$store.dispatch('firmwareSingleImage/getFirmwareInformation'),
- ]).finally(() => this.endLoader());
- },
- validations() {
- return {
- file: {
- required: requiredIf(function () {
- return this.isWorkstationSelected;
- }),
- },
- tftpFileAddress: {
- required: requiredIf(function () {
- return !this.isWorkstationSelected;
- }),
- },
- };
- },
- methods: {
- updateFirmware() {
- this.setRebootTimeout(360000, () => {
- this.infoToast(
- this.$t('pageFirmware.singleFileUpload.toast.verifyUpdateMessage'),
- {
- title: this.$t('pageFirmware.singleFileUpload.toast.verifyUpdate'),
- refreshAction: true,
- }
- );
- });
- this.infoToast(
- this.$t('pageFirmware.singleFileUpload.toast.updateStartedMessage'),
- {
- title: this.$t('pageFirmware.singleFileUpload.toast.updateStarted'),
- timestamp: true,
- }
- );
- if (this.isWorkstationSelected) {
- this.dispatchWorkstationUpload();
- } else {
- this.dispatchTftpUpload();
- }
- },
- dispatchWorkstationUpload() {
- this.$store
- .dispatch('firmwareSingleImage/uploadFirmware', this.file)
- .catch(({ message }) => {
- this.errorToast(message);
- this.clearRebootTimeout();
- });
- },
- dispatchTftpUpload() {
- this.$store
- .dispatch(
- 'firmwareSingleImage/uploadFirmwareTFTP',
- this.tftpFileAddress
- )
- .catch(({ message }) => {
- this.errorToast(message);
- this.clearRebootTimeout();
- });
- },
- switchToRunning() {
- this.setRebootTimeout(60000, () => {
- this.infoToast(
- this.$t('pageFirmware.singleFileUpload.toast.verifySwitchMessage'),
- {
- title: this.$t('pageFirmware.singleFileUpload.toast.verifySwitch'),
- refreshAction: true,
- }
- );
- });
- this.$store
- .dispatch('firmwareSingleImage/switchFirmwareAndReboot')
- .then(() =>
- this.infoToast(
- this.$t('pageFirmware.singleFileUpload.toast.rebootStartedMessage'),
- {
- title: this.$t(
- 'pageFirmware.singleFileUpload.toast.rebootStarted'
- ),
- }
- )
- )
- .catch(({ message }) => {
- this.errorToast(message);
- this.clearRebootTimeout();
- });
- },
- setRebootTimeout(timeoutMs = 60000, callback) {
- // Set a timeout to disable page interactions
- // during a BMC reboot
- this.startLoader();
- this.timeoutId = setTimeout(() => {
- this.endLoader();
- if (callback) callback();
- }, timeoutMs);
- },
- clearRebootTimeout() {
- if (this.timeoutId) {
- clearTimeout(this.timeoutId);
- this.endLoader();
- }
- },
- onSubmitUpload() {
- this.$v.$touch();
- if (this.$v.$invalid) return;
- this.$bvModal.show('modal-update-firmware');
- },
- onFileUpload(file) {
- this.file = file;
- this.$v.file.$touch();
- },
+ this.$store
+ .dispatch('firmwareSingleImage/getFirmwareInformation')
+ .finally(() => this.endLoader());
},
};
</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue
deleted file mode 100644
index 51575253..00000000
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<template>
- <b-modal
- id="modal-update-firmware"
- :title="$t('pageFirmware.singleFileUpload.updateFirmware')"
- :ok-title="$t('pageFirmware.singleFileUpload.startUpdate')"
- :cancel-title="$t('global.action.cancel')"
- @ok="$emit('ok')"
- >
- <p>
- {{ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo') }}
- </p>
- <p v-if="showMessage">
- {{
- $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo2', {
- backup,
- running,
- })
- }}
- </p>
- <p class="m-0">
- {{ $t('pageFirmware.singleFileUpload.modal.updateFirmwareInfo3') }}
- </p>
- </b-modal>
-</template>
-
-<script>
-export default {
- props: {
- backup: {
- type: String,
- required: true,
- },
- running: {
- type: String,
- required: true,
- },
- },
- computed: {
- showMessage() {
- return this.backup !== this.running;
- },
- },
-};
-</script>
diff --git a/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js b/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
index b16cac8a..ae4d6333 100644
--- a/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
+++ b/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
@@ -4,83 +4,107 @@ import i18n from '@/i18n';
const FirmwareSingleImageStore = {
namespaced: true,
state: {
- activeFirmware: {
- version: '--',
- id: null,
- location: null,
- },
- backupFirmware: {
- version: '--',
- id: null,
- location: null,
- status: null,
- },
+ bmcFirmware: [],
+ hostFirmware: [],
+ bmcActiveFirmwareId: null,
+ hostActiveFirmwareId: null,
applyTime: null,
tftpAvailable: false,
},
getters: {
- systemFirmwareVersion: (state) => state.activeFirmware.version,
- backupFirmwareVersion: (state) => state.backupFirmware.version,
- backupFirmwareStatus: (state) => state.backupFirmware.status,
- isRebootFromBackupAvailable: (state) =>
- state.backupFirmware.id ? true : false,
- bmcFirmwareCurrentVersion: (state) => state.activeFirmware.version, //this getter is needed for the Overview page,
isTftpUploadAvailable: (state) => state.tftpAvailable,
- },
- mutations: {
- setActiveFirmware: (state, { version, id, location }) => {
- state.activeFirmware.version = version;
- state.activeFirmware.id = id;
- state.activeFirmware.location = location;
+ isSingleFileUploadEnabled: (state) => state.hostFirmware.length === 0,
+ activeBmcFirmware: (state) => {
+ return state.bmcFirmware.find(
+ (firmware) => firmware.id === state.bmcActiveFirmwareId
+ );
},
- setBackupFirmware: (state, { version, id, location, status }) => {
- state.backupFirmware.version = version;
- state.backupFirmware.id = id;
- state.backupFirmware.location = location;
- state.backupFirmware.status = status;
+ activeHostFirmware: (state) => {
+ return state.hostFirmware.find(
+ (firmware) => firmware.id === state.hostActiveFirmwareId
+ );
},
+ backupBmcFirmware: (state) => {
+ return state.bmcFirmware.find(
+ (firmware) => firmware.id !== state.bmcActiveFirmwareId
+ );
+ },
+ backupHostFirmware: (state) => {
+ return state.hostFirmware.find(
+ (firmware) => firmware.id !== state.hostActiveFirmwareId
+ );
+ },
+ bmcFirmwareCurrentVersion: (_, getters) =>
+ getters.activeBmcFirmware?.version, //this getter is needed for the Overview page,
+ },
+ mutations: {
+ setActiveBmcFirmwareId: (state, id) => (state.bmcActiveFirmwareId = id),
+ setActiveHostFirmwareId: (state, id) => (state.hostActiveFirmwareId = id),
+ setBmcFirmware: (state, firmware) => (state.bmcFirmware = firmware),
+ setHostFirmware: (state, firmware) => (state.hostFirmware = firmware),
setApplyTime: (state, applyTime) => (state.applyTime = applyTime),
setTftpUploadAvailable: (state, tftpAvailable) =>
(state.tftpAvailable = tftpAvailable),
},
actions: {
- async getFirmwareInformation({ commit }) {
- return await api
+ async getFirmwareInformation({ dispatch }) {
+ dispatch('getActiveHostFirmware');
+ dispatch('getActiveBmcFirmware');
+ return await dispatch('getFirmwareInventory');
+ },
+ getActiveBmcFirmware({ commit }) {
+ return api
.get('/redfish/v1/Managers/bmc')
.then(({ data: { Links } }) => {
- const currentLocation = Links.ActiveSoftwareImage['@odata.id'];
- // Check SoftwareImages list for not ActiveSoftwareImage id
- const backupLocation = Links.SoftwareImages.map(
- (item) => item['@odata.id']
- ).find((location) => {
- const id = location.split('/').pop();
- const currentId = currentLocation.split('/').pop();
- return id !== currentId;
- });
- return { currentLocation, backupLocation };
+ const id = Links?.ActiveSoftwareImage['@odata.id'].split('/').pop();
+ commit('setActiveBmcFirmwareId', id);
})
- .then(async ({ currentLocation, backupLocation }) => {
- const currentData = await api.get(currentLocation);
- let backupData = {};
-
- if (backupLocation) {
- backupData = await api.get(backupLocation);
- }
-
- commit('setActiveFirmware', {
- version: currentData?.data?.Version,
- id: currentData?.data?.Id,
- location: currentData?.data?.['@odata.id'],
- });
- commit('setBackupFirmware', {
- version: backupData.data?.Version,
- id: backupData.data?.Id,
- location: backupData.data?.['@odata.id'],
- status: backupData.data?.Status?.Health,
- });
+ .catch((error) => console.log(error));
+ },
+ getActiveHostFirmware({ commit }) {
+ return api
+ .get('/redfish/v1/Systems/system/Bios')
+ .then(({ data: { Links } }) => {
+ const id = Links?.ActiveSoftwareImage['@odata.id'].split('/').pop();
+ commit('setActiveHostFirmwareId', id);
})
.catch((error) => console.log(error));
},
+ async getFirmwareInventory({ commit }) {
+ const inventoryList = await api
+ .get('/redfish/v1/UpdateService/FirmwareInventory')
+ .then(({ data: { Members = [] } = {} }) =>
+ Members.map((item) => api.get(item['@odata.id']))
+ )
+ .catch((error) => console.log(error));
+ await api
+ .all(inventoryList)
+ .then((response) => {
+ const bmcFirmware = [];
+ const hostFirmware = [];
+ response.forEach(({ data }) => {
+ const firmwareType = data?.RelatedItem?.[0]?.['@odata.id']
+ .split('/')
+ .pop();
+ const item = {
+ version: data?.Version,
+ id: data?.Id,
+ location: data?.['@odata.id'],
+ status: data?.Status?.Health,
+ };
+ if (firmwareType === 'bmc') {
+ bmcFirmware.push(item);
+ } else if (firmwareType === 'Bios') {
+ hostFirmware.push(item);
+ }
+ });
+ commit('setBmcFirmware', bmcFirmware);
+ commit('setHostFirmware', hostFirmware);
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ },
getUpdateServiceSettings({ commit }) {
api
.get('/redfish/v1/UpdateService')
@@ -147,8 +171,8 @@ const FirmwareSingleImageStore = {
throw new Error(i18n.t('pageFirmware.toast.errorUploadAndReboot'));
});
},
- async switchFirmwareAndReboot({ state }) {
- const backupLoaction = state.backupFirmware.location;
+ async switchFirmwareAndReboot({ getters }) {
+ const backupLoaction = getters.backupBmcFirmware.location;
const data = {
Links: {
ActiveSoftwareImage: {
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 8ca98bf0..714027bb 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -317,14 +317,14 @@
"successUploadTitle": "Code update started"
},
"singleFileUpload": {
- "backupImage": "Backup image",
- "bmcAndServer": "BMC and server:",
- "fileAddress": "File address",
- "fileSource": "File source",
- "runningImage": "Running image",
- "startUpdate": "Start update",
- "switchToRunning": "Switch to running",
- "updateFirmware": "Update firmware",
+ "cardActionSwitchToRunning": "Switch to running",
+ "cardBodyVersion": "Version",
+ "cardTitleBackup": "Backup image",
+ "cardTitleRunning": "Running image",
+ "sectionTitleBmcCards": "BMC",
+ "sectionTitleBmcCardsCombined": "BMC and server",
+ "sectionTitleHostCards": "Host",
+ "sectionTitleUpdateFirmware": "Update firmware",
"alert": {
"operationInProgress": "Server power operation in progress.",
"serverMustBePoweredOffTo": "Server must be powered off to:",
@@ -333,14 +333,26 @@
"updateFirmware": "Update firmware",
"viewServerPowerOperations": "View server power operations"
},
+ "form": {
+ "updateFirmware": {
+ "fileAddress": "File address",
+ "fileSource": "File source",
+ "imageFile": "Image file",
+ "imageFileHelperText": "Only .tar files accepted",
+ "startUpdate": "Start update",
+ "tftpServer": "TFTP server",
+ "workstation": "Workstation"
+ }
+ },
"modal": {
"switchImages": "Switch images",
"switchRunningImage": "Switch running image",
"switchRunningImageInfo": "A BMC reboot is required to run the backup image. The application might be unresponsive during this time.",
"switchRunningImageInfo2": "Are you sure you want to switch to the backup image (%{backup})?",
"updateFirmwareInfo": "The BMC will reboot during the update process. The server cannot be powered on until the update is finished.",
- "updateFirmwareInfo2": "The running image (%{running}) will be copied to backup and the backup image (%{backup}) will be permanently deleted.",
- "updateFirmwareInfo3": "Are you sure you want to proceed with the update?"
+ "updateFirmwareInfo2": "The running image (%{running}) will be copied to backup. The backup image will be deleted.",
+ "updateFirmwareInfo3": "Are you sure you want to proceed with the update?",
+ "updateFirmwareInfoDefault": "The new image will be uploaded and activated. After that, the BMC or host will reboot automatically to run from the new image."
},
"toast": {
"errorSwitchImages": "Error switching running and backup images.",