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
diff --git a/src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue b/src/env/components/FirmwareSingleImage/FirmwareAlertServerPower.vue
new file mode 100644
index 0000000..f7ac0fc
--- /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 0000000..857adf0
--- /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 0000000..c47f60f
--- /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 0000000..f13b8e0
--- /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
similarity index 100%
rename from src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue
rename to 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 0000000..d6c52f9
--- /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 b7ad4c5..2e601bd 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>
+    <alerts-server-power
+      v-if="isServerPowerOffRequired"
+      :is-host-off="isHostOff"
+    />
+
+    <!-- Firmware cards -->
     <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>
+        <!-- BMC Firmware -->
+        <bmc-cards :is-page-disabled="isPageDisabled" />
 
-            <!-- 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>
-    <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>
-
-              <!-- 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 @@
     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 5157525..0000000
--- 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 b16cac8..ae4d633 100644
--- a/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
+++ b/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
@@ -4,83 +4,107 @@
 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,
+    isSingleFileUploadEnabled: (state) => state.hostFirmware.length === 0,
+    activeBmcFirmware: (state) => {
+      return state.bmcFirmware.find(
+        (firmware) => firmware.id === state.bmcActiveFirmwareId
+      );
+    },
+    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: {
-    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;
-    },
+    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 };
-        })
-        .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,
-          });
+          const id = Links?.ActiveSoftwareImage['@odata.id'].split('/').pop();
+          commit('setActiveBmcFirmwareId', id);
         })
         .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 @@
           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: {