Update single file firmware upload page

These updates are visible with IBM dotenv variables. Updates
include changes to layout and verbiage.

- Added a global application refresh listener, so app refresh
  can be called from components outside of the application header

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I5ebe6452a0360b6cced5597b648cde33e97c5a1f
diff --git a/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
index fe87e08..517e5fa 100644
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
+++ b/src/env/components/FirmwareSingleImage/FirmwareSingleImage.vue
@@ -1,219 +1,270 @@
 <template>
   <b-container fluid="xl">
-    <page-title :description="$t('pageFirmware.pageDescriptionSingleImage')" />
-    <!-- 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 #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')">
+    <page-title />
+    <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>
+    <b-row>
+      <b-col xl="10">
+        <page-section>
           <b-card-group deck>
-            <!-- Current FW -->
-            <b-card header-bg-variant="success">
-              <template #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 -->
+            <!-- Running image -->
             <b-card>
               <template #header>
-                <dl class="mb-0">
-                  <dt>{{ $t('pageFirmware.backup') }}</dt>
-                  <dd class="mb-0">{{ backupFirmwareVersion }}</dd>
-                </dl>
+                <p class="font-weight-bold m-0">
+                  {{ $t('pageFirmware.singleFileUpload.runningImage') }}
+                </p>
               </template>
-              <b-row>
-                <b-col xs="6">
-                  <dl class="my-0">
-                    <dt>{{ $t('pageFirmware.state') }}</dt>
-                    <dd>{{ backupFirmwareStatus }}</dd>
-                  </dl>
-                </b-col>
-              </b-row>
+              <dl class="mb-0">
+                <dt>{{ $t('pageFirmware.singleFileUpload.bmcAndServer') }}</dt>
+                <dd class="mb-0">{{ systemFirmwareVersion }}</dd>
+              </dl>
+            </b-card>
+
+            <!-- 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>
-
-        <!-- 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-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
-                :label="$t('pageFirmware.form.imageFile')"
-                label-for="image-file"
+                :label="$t('pageFirmware.singleFileUpload.fileSource')"
+                :disabled="isPageDisabled"
               >
-                <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"
-                  :browse-text="$t('global.fileUpload.browseText')"
-                  :drop-placeholder="$t('global.fileUpload.dropPlaceholder')"
-                  :placeholder="$t('global.fileUpload.placeholder')"
-                  :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-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>
-            </template>
 
-            <!-- TFTP Server Upload -->
-            <template v-else>
-              <b-form-group
-                :label="$t('pageFirmware.form.tftpServerAddress')"
-                label-for="tftp-ip"
-              >
-                <b-form-text id="server-address-help-block">
-                  {{ $t('pageFirmware.form.tftpServerAddressHelper') }}
-                </b-form-text>
-                <b-form-input
-                  id="tftp-id"
-                  v-model="tftpIpAddress"
-                  type="text"
-                  :state="getValidationState($v.tftpIpAddress)"
-                  :disabled="isPageDisabled"
-                  aria-describedby="server-address-help-block"
-                  @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>
+              <!-- 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>
 
-            <!-- 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.updateProcessInfoSingleImage') }}</p>
-            </alert>
-            <b-form-group>
-              <b-btn type="submit" variant="primary" :disabled="isPageDisabled">
-                {{ $t('pageFirmware.form.uploadAndRebootBmc') }}
+              <!-- 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>
-            </b-form-group>
-          </b-form>
+              <alert
+                v-if="!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>
       </b-col>
     </b-row>
 
     <!-- Modals -->
-    <modal-upload @ok="uploadFirmware" />
-    <modal-reboot-backup
-      :current="systemFirmwareVersion"
+    <modal-update-firmware
+      :running="systemFirmwareVersion"
       :backup="backupFirmwareVersion"
-      @ok="rebootFromBackup"
+      @ok="updateFirmware"
     />
+    <modal-switch-to-running
+      :backup="backupFirmwareVersion"
+      @ok="switchToRunning"
+    />
+
+    <!-- Toasts -->
+    <b-toast id="switch-images" variant="info" solid :no-auto-hide="true">
+      <template #toast-title>
+        <status-icon status="info" class="toast-icon" />
+        <strong>{{
+          $t('pageFirmware.singleFileUpload.toast.rebootStarted')
+        }}</strong>
+      </template>
+      <p class="m-0">
+        {{ $t('pageFirmware.singleFileUpload.toast.rebootStartedMessage') }}
+      </p>
+    </b-toast>
+
+    <b-toast id="switch-images-2" variant="info" solid :no-auto-hide="true">
+      <template #toast-title>
+        <status-icon status="info" class="toast-icon" />
+        <strong>{{
+          $t('pageFirmware.singleFileUpload.toast.verifySwitch')
+        }}</strong>
+      </template>
+      <p>
+        {{ $t('pageFirmware.singleFileUpload.toast.verifySwitchMessage') }}
+      </p>
+      <b-link @click="onClickRefresh">{{ $t('global.action.refresh') }}</b-link>
+    </b-toast>
+
+    <b-toast id="update-firmware" variant="info" solid :no-auto-hide="true">
+      <template #toast-title>
+        <status-icon status="info" class="toast-icon" />
+        <strong>{{
+          $t('pageFirmware.singleFileUpload.toast.updateStarted')
+        }}</strong>
+      </template>
+      <p>
+        {{ $t('pageFirmware.singleFileUpload.toast.updateStartedMessage') }}
+      </p>
+      <p class="m-0">
+        {{ $t('pageFirmware.singleFileUpload.toast.startTime') }}
+        {{ $options.filters.formatTime(new Date()) }}
+      </p>
+    </b-toast>
+
+    <b-toast id="update-firmware-2" variant="info" solid :no-auto-hide="true">
+      <template #toast-title>
+        <status-icon status="info" class="toast-icon" />
+        <strong>{{
+          $t('pageFirmware.singleFileUpload.toast.verifyUpdate')
+        }}</strong>
+      </template>
+      <p>
+        {{ $t('pageFirmware.singleFileUpload.toast.verifyUpdateMessage') }}
+      </p>
+      <b-link @click="onClickRefresh">{{ $t('global.action.refresh') }}</b-link>
+    </b-toast>
   </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 PageSection from '@/components/Global/PageSection';
 import PageTitle from '@/components/Global/PageTitle';
 import Alert from '@/components/Global/Alert';
-import ModalUpload from './FirmwareSingleImageModalUpload';
-import ModalRebootBackup from './FirmwareSingleImageModalRebootBackup';
+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';
@@ -223,10 +274,13 @@
   name: 'FirmwareSingleImage',
   components: {
     Alert,
-    ModalRebootBackup,
-    ModalUpload,
+    FormFile,
+    IconSwitch,
+    ModalSwitchToRunning,
+    ModalUpdateFirmware,
     PageSection,
     PageTitle,
+    StatusIcon,
   },
   mixins: [BVToastMixin, LoadingBarMixin, VuelidateMixin],
   beforeRouteLeave(to, from, next) {
@@ -238,8 +292,7 @@
     return {
       isWorkstationSelected: true,
       file: null,
-      tftpIpAddress: null,
-      tftpFileName: null,
+      tftpFileAddress: null,
       timeoutId: null,
       loading,
     };
@@ -263,13 +316,18 @@
     isPageDisabled() {
       return !this.isHostOff || this.loading || this.isOperationInProgress;
     },
+    showBackupImageStatus() {
+      return (
+        this.backupFirmwareStatus === 'Critical' ||
+        this.backupFirmwareStatus === 'Warning'
+      );
+    },
   },
   watch: {
     isWorkstationSelected: function () {
       this.$v.$reset();
       this.file = null;
-      this.tftpIpAddress = null;
-      this.tftpFileName = null;
+      this.tftpFileAddress = null;
     },
   },
   created() {
@@ -287,12 +345,7 @@
           return this.isWorkstationSelected;
         }),
       },
-      tftpIpAddress: {
-        required: requiredIf(function () {
-          return !this.isWorkstationSelected;
-        }),
-      },
-      tftpFileName: {
+      tftpFileAddress: {
         required: requiredIf(function () {
           return !this.isWorkstationSelected;
         }),
@@ -300,13 +353,9 @@
     };
   },
   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')
-      );
+    updateFirmware() {
+      this.setRebootTimeout(360000, 'update-firmware-2'); //6 minute timeout
+      this.$bvToast.show('update-firmware');
       if (this.isWorkstationSelected) {
         this.dispatchWorkstationUpload();
       } else {
@@ -316,57 +365,41 @@
     dispatchWorkstationUpload() {
       this.$store
         .dispatch('firmwareSingleImage/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('firmwareSingleImage/uploadFirmwareTFTP', data)
-        .then((success) =>
-          this.infoToast(
-            success,
-            this.$t('pageFirmware.toast.successUploadTitle')
-          )
+        .dispatch(
+          'firmwareSingleImage/uploadFirmwareTFTP',
+          this.tftpFileAddress
         )
         .catch(({ message }) => {
           this.errorToast(message);
           this.clearRebootTimeout();
         });
     },
-    rebootFromBackup() {
-      this.setRebootTimeout();
+    switchToRunning() {
+      this.setRebootTimeout(60000, 'switch-images-2');
       this.$store
         .dispatch('firmwareSingleImage/switchFirmwareAndReboot')
-        .then((success) =>
-          this.infoToast(success, this.$t('global.status.success'))
-        )
+        .then(() => this.$bvToast.show('switch-images'))
         .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
+    setRebootTimeout(timeoutMs = 60000, toastId) {
+      // Set a timeout to disable page interactions
+      // during a BMC reboot
       this.startLoader();
       this.timeoutId = setTimeout(() => {
         this.endLoader();
-        this.infoToast(
-          this.$t('pageFirmware.toast.infoRefreshApplicationMessage'),
-          this.$t('pageFirmware.toast.infoRefreshApplicationTitle')
-        );
+        if (toastId) {
+          this.$bvToast.show(toastId);
+        }
       }, timeoutMs);
     },
     clearRebootTimeout() {
@@ -378,30 +411,15 @@
     onSubmitUpload() {
       this.$v.$touch();
       if (this.$v.$invalid) return;
-      this.$bvModal.show('modal-upload');
+      this.$bvModal.show('modal-update-firmware');
     },
-    onClickShutDown() {
-      this.$bvModal
-        .msgBoxConfirm(this.$t('pageFirmware.modal.serverShutdownMessage'), {
-          title: this.$t('pageFirmware.modal.serverShutdownWillCauseOutage'),
-          okTitle: this.$t('pageFirmware.modal.shutDownServer'),
-          cancelTitle: this.$t('global.action.cancel'),
-          okVariant: 'danger',
-        })
-        .then((shutdownConfirmed) => {
-          if (shutdownConfirmed)
-            this.$store.dispatch('controls/hostSoftPowerOff');
-        });
+    onFileUpload(file) {
+      this.file = file;
+      this.$v.file.$touch();
+    },
+    onClickRefresh() {
+      this.$root.$emit('refresh-application');
     },
   },
 };
 </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/env/components/FirmwareSingleImage/FirmwareSingleImageModalRebootBackup.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalRebootBackup.vue
deleted file mode 100644
index f64065a..0000000
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalRebootBackup.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-<template>
-  <b-modal
-    id="modal-reboot-backup"
-    :ok-title="$t('pageFirmware.modal.rebootFromBackup.primaryAction')"
-    :cancel-title="$t('global.action.cancel')"
-    :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/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue
new file mode 100644
index 0000000..56f505d
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalSwitchToRunning.vue
@@ -0,0 +1,31 @@
+<template>
+  <b-modal
+    id="modal-switch-to-running"
+    :ok-title="$t('pageFirmware.singleFileUpload.modal.switchImages')"
+    :cancel-title="$t('global.action.cancel')"
+    :title="$t('pageFirmware.singleFileUpload.modal.switchRunningImage')"
+    @ok="$emit('ok')"
+  >
+    <p>
+      {{ $t('pageFirmware.singleFileUpload.modal.switchRunningImageInfo') }}
+    </p>
+    <p class="m-0">
+      {{
+        $t('pageFirmware.singleFileUpload.modal.switchRunningImageInfo2', {
+          backup,
+        })
+      }}
+    </p>
+  </b-modal>
+</template>
+
+<script>
+export default {
+  props: {
+    backup: {
+      type: String,
+      required: true,
+    },
+  },
+};
+</script>
diff --git a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue
new file mode 100644
index 0000000..5157525
--- /dev/null
+++ b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpdateFirmware.vue
@@ -0,0 +1,44 @@
+<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/components/FirmwareSingleImage/FirmwareSingleImageModalUpload.vue b/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpload.vue
deleted file mode 100644
index e544b9f..0000000
--- a/src/env/components/FirmwareSingleImage/FirmwareSingleImageModalUpload.vue
+++ /dev/null
@@ -1,21 +0,0 @@
-<template>
-  <b-modal
-    id="modal-upload"
-    :title="$t('pageFirmware.modal.uploadAndRebootSingleImage.title')"
-    :ok-title="
-      $t('pageFirmware.modal.uploadAndRebootSingleImage.primaryAction')
-    "
-    :cancel-title="$t('global.action.cancel')"
-    @ok="$emit('ok')"
-  >
-    <p>
-      {{ $t('pageFirmware.modal.uploadAndRebootSingleImage.message1') }}
-    </p>
-    <p>
-      {{ $t('pageFirmware.modal.uploadAndRebootSingleImage.message2') }}
-    </p>
-    <p class="font-weight-bold">
-      {{ $t('pageFirmware.modal.uploadAndRebootSingleImage.message3') }}
-    </p>
-  </b-modal>
-</template>
diff --git a/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js b/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
index ffc4bc4..91bcb9f 100644
--- a/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
+++ b/src/env/store/FirmwareSingleImage/FirmwareSingleImageStore.js
@@ -13,7 +13,7 @@
       version: '--',
       id: null,
       location: null,
-      status: '--',
+      status: null,
     },
     applyTime: null,
   },
@@ -72,7 +72,7 @@
             version: backupData.data?.Version,
             id: backupData.data?.Id,
             location: backupData.data?.['@odata.id'],
-            status: backupData.data?.Status?.State,
+            status: backupData.data?.Status?.Health,
           });
         })
         .catch((error) => console.log(error));
@@ -110,17 +110,15 @@
         .post('/redfish/v1/UpdateService', image, {
           headers: { 'Content-Type': 'application/octet-stream' },
         })
-        .then(() => dispatch('getSystemFirwareVersion'))
-        .then(() => i18n.t('pageFirmware.toast.successUploadMessage'))
         .catch((error) => {
           console.log(error);
           throw new Error(i18n.t('pageFirmware.toast.errorUploadAndReboot'));
         });
     },
-    async uploadFirmwareTFTP({ state, dispatch }, { address, filename }) {
+    async uploadFirmwareTFTP({ state, dispatch }, fileAddress) {
       const data = {
         TransferProtocol: 'TFTP',
-        ImageURI: `${address}/${filename}`,
+        ImageURI: fileAddress,
       };
       if (state.applyTime !== 'Immediate') {
         // ApplyTime must be set to Immediate before making
@@ -132,8 +130,6 @@
           '/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate',
           data
         )
-        .then(() => dispatch('getSystemFirwareVersion'))
-        .then(() => i18n.t('pageFirmware.toast.successUploadMessage'))
         .catch((error) => {
           console.log(error);
           throw new Error(i18n.t('pageFirmware.toast.errorUploadAndReboot'));
@@ -150,10 +146,11 @@
       };
       return await api
         .patch('/redfish/v1/Managers/bmc', data)
-        .then(() => i18n.t('pageFirmware.toast.successRebootFromBackup'))
         .catch((error) => {
           console.log(error);
-          throw new Error(i18n.t('pageFirmware.toast.errorRebootFromBackup'));
+          throw new Error(
+            i18n.t('pageFirmware.singleFileUpload.toast.errorSwitchImages')
+          );
         });
     },
   },
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index 228b25c..c2023df 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -48,6 +48,9 @@
       });
     },
   },
+  mounted() {
+    this.$root.$on('refresh-application', () => this.refresh());
+  },
   methods: {
     refresh() {
       // Changing the component :key value will trigger
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index a4af709..d3b6d75 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -13,6 +13,7 @@
       "enable": "Enable",
       "export": "Export",
       "filter": "Filter",
+      "refresh": "Refresh",
       "replace": "Replace",
       "save": "Save",
       "saveSettings": "Save settings",
@@ -261,29 +262,19 @@
   },
   "pageFirmware": {
     "backup": "Backup:",
-    "backupImage": "Backup image",
     "bmcStatus": "BMC status",
-    "changeAndRebootBmc": "Change image and reboot BMC",
-    "changeToBackupImage": "Change to backup image",
     "current": "Current:",
     "firmwareOnBmc": "Firmware on BMC",
     "firmwareOnHost": "Firmware on host",
-    "firmwareOnSystem": "Firmware on system",
     "hostStatus": "Host status",
     "makeCurrentVersion": "Make current version",
     "pageDescription": "Update firmware by uploading a BMC or Host image file from your workstation or TFTP server",
-    "pageDescriptionSingleImage": "Update firmware by uploading a system image file from your workstation or TFTP server",
     "running": "Running",
     "state": "State",
     "updateCode": "Update code",
     "alert": {
-      "operationInProgress": "Server power operation in progress.",
-      "serverShutdownRequiredBeforeUpdate": "Server shutdown required before update",
-      "serverShutdownRequiredInfo": "Shutdown will be orderly - OS will shutdown before the server shuts down.",
-      "shutDownServer": "Shut down server",
       "updateProcess": "Update process",
-      "updateProcessInfo": "The new image will be uploaded and activated. After that, the BMC or host will reboot automatically to run from the new image.",
-      "updateProcessInfoSingleImage": "The new image will be uploaded and activated. After that, the BMC will reboot automatically to run from the new image."
+      "updateProcessInfo": "The new image will be uploaded and activated. After that, the BMC or host will reboot automatically to run from the new image."
     },
     "form": {
       "imageFile": "Image file",
@@ -299,9 +290,6 @@
     },
     "modal": {
       "connectionToBmcWillBeLost": "Connection to BMC will be lost",
-      "serverShutdownMessage": "There will be a server outage until the server is powered back on. Are you sure you want to shut down?",
-      "serverShutdownWillCauseOutage": "Server shutdown will cause outage",
-      "shutDownServer": "Shut down server",
       "rebootFromBackup": {
         "message1": "A BMC reboot is required before the system can run the backup image %{backup}. The reboot will cause a disconnection, and may require logging in again.",
         "message2": "The current firmware image %{current} will be moved to backup. During the reboot, server cannot be powered back on.",
@@ -309,13 +297,6 @@
         "primaryAction": "Reboot BMC from backup image",
         "title": "@:pageFirmware.modal.connectionToBmcWillBeLost"
       },
-      "uploadAndRebootSingleImage": {
-        "message1": "A BMC reboot is required before the system can run the new firmware image. The reboot will cause a disconnection, and may require logging in again.",
-        "message2": "During the reboot, the server cannot be powered back on. The backup image will be permanently deleted.",
-        "message3": "Are you sure you want to upload the new firmware image and reboot the BMC?",
-        "primaryAction": "Upload and reboot BMC",
-        "title": "@:pageFirmware.modal.connectionToBmcWillBeLost"
-      },
       "uploadAndRebootBmcOrHost": {
         "message1": "A BMC or host reboot is required before the system can run the new firmware image. The reboot will cause a disconnection, and may require logging in again.",
         "message2": "The backup image will be permanently deleted.",
@@ -334,6 +315,45 @@
       "successRebootFromBackup": "Successfully started reboot from backup image.",
       "successUploadMessage": "The upload was successful. During code update, the BMC will be not be responsive. Wait for the code update notification before making any changes.",
       "successUploadTitle": "Code update started"
+    },
+    "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",
+      "alert": {
+        "operationInProgress": "Server power operation in progress.",
+        "serverMustBePoweredOffTo": "Server must be powered off to:",
+        "serverMustBePoweredOffToUpdateFirmware": "Server must be powered off to update firmware",
+        "switchRunningAndBackupImages": "Switch running and backup images",
+        "updateFirmware": "Update firmware",
+        "viewServerPowerOperations": "View server power operations"
+      },
+      "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?"
+      },
+      "toast": {
+        "errorSwitchImages": "Error switching running and backup images.",
+        "rebootStarted": "Reboot started",
+        "rebootStartedMessage": "Successfully started reboot from backup image.",
+        "startTime": "Start time:",
+        "updateStarted": "Update started",
+        "updateStartedMessage": "Wait for the firmware update notification before making any changes.",
+        "verifySwitch": "Verify switch",
+        "verifySwitchMessage": "Refresh the application to verify the running and backup images switched.",
+        "verifyUpdate": "Verify update",
+        "verifyUpdateMessage": "Refresh the application to verify firmware updated successfully"
+      }
     }
   },
   "pageHardwareStatus": {