blob: cfdc0a3e6b40a3a76b8218c31552daba8f9f3d8e [file] [log] [blame]
Yoshie Muranaka92a0a4a2020-07-15 10:30:31 -07001<template>
2 <b-container fluid="xl">
3 <page-title :description="$t('pageFirmware.pageDescription')" />
4 <!-- Operation in progress alert -->
5 <alert v-if="isOperationInProgress" variant="info" class="mb-5">
6 <p>
7 {{ $t('pageFirmware.alert.operationInProgress') }}
8 </p>
9 </alert>
10 <!-- Shutdown server warning alert -->
11 <alert v-else-if="!isHostOff" variant="warning" class="mb-5">
12 <p class="font-weight-bold mb-1">
13 {{ $t('pageFirmware.alert.serverShutdownRequiredBeforeUpdate') }}
14 </p>
15 {{ $t('pageFirmware.alert.serverShutdownRequiredInfo') }}
16 <template v-slot:action>
17 <b-btn variant="link" class="text-nowrap" @click="onClickShutDown">
18 {{ $t('pageFirmware.alert.shutDownServer') }}
19 </b-btn>
20 </template>
21 </alert>
22 <b-row class="mb-4">
23 <!-- Firmware on system -->
24 <b-col md="10" lg="12" xl="8" class="pr-xl-4">
25 <page-section :section-title="$t('pageFirmware.firmwareOnSystem')">
26 <b-card-group deck>
27 <!-- Current FW -->
28 <b-card header-bg-variant="success">
29 <template v-slot:header>
30 <dl class="mb-0">
31 <dt>{{ $t('pageFirmware.current') }}</dt>
32 <dd class="mb-0">{{ systemFirmwareVersion }}</dd>
33 </dl>
34 </template>
35 <b-row>
36 <b-col xs="6">
37 <dl class="my-0">
38 <dt>{{ $t('pageFirmware.bmcStatus') }}</dt>
39 <dd>{{ $t('pageFirmware.running') }}</dd>
40 </dl>
41 </b-col>
42 <b-col xs="6">
43 <dl class="my-0">
44 <dt>{{ $t('pageFirmware.hostStatus') }}</dt>
45 <dd v-if="hostStatus === 'on'">
46 {{ $t('global.status.on') }}
47 </dd>
48 <dd v-else-if="hostStatus === 'off'">
49 {{ $t('global.status.off') }}
50 </dd>
51 <dd v-else>
52 {{ $t('global.status.notAvailable') }}
53 </dd>
54 </dl>
55 </b-col>
56 </b-row>
57 </b-card>
58
59 <!-- Backup FW -->
60 <b-card>
61 <template v-slot:header>
62 <dl class="mb-0">
63 <dt>{{ $t('pageFirmware.backup') }}</dt>
64 <dd class="mb-0">{{ backupFirmwareVersion }}</dd>
65 </dl>
66 </template>
67 <b-row>
68 <b-col xs="6">
69 <dl class="my-0">
70 <dt>{{ $t('pageFirmware.state') }}</dt>
71 <dd>{{ backupFirmwareStatus }}</dd>
72 </dl>
73 </b-col>
74 </b-row>
75 </b-card>
76 </b-card-group>
77 </page-section>
78
79 <!-- Change to backup image -->
80 <page-section :section-title="$t('pageFirmware.changeToBackupImage')">
81 <dl class="mb-5">
82 <dt>
83 {{ $t('pageFirmware.backupImage') }}
84 </dt>
85 <dd>{{ backupFirmwareVersion }}</dd>
86 </dl>
87 <b-btn
88 v-b-modal.modal-reboot-backup
89 type="button"
90 variant="primary"
91 :disabled="isPageDisabled || !isRebootFromBackupAvailable"
92 >
93 {{ $t('pageFirmware.changeAndRebootBmc') }}
94 </b-btn>
95 </page-section>
96 </b-col>
97
98 <!-- Update code -->
99 <b-col sm="8" xl="4" class="update-code pl-xl-4">
100 <page-section :section-title="$t('pageFirmware.updateCode')">
101 <b-form @submit.prevent="onSubmitUpload">
102 <b-form-group
103 :label="$t('pageFirmware.form.uploadLocation')"
104 :disabled="isPageDisabled"
105 >
106 <b-form-radio v-model="isWorkstationSelected" :value="true">
107 {{ $t('pageFirmware.form.workstation') }}
108 </b-form-radio>
109 <b-form-radio v-model="isWorkstationSelected" :value="false">
110 {{ $t('pageFirmware.form.tftpServer') }}
111 </b-form-radio>
112 </b-form-group>
113
114 <!-- Workstation Upload -->
115 <template v-if="isWorkstationSelected">
116 <b-form-group
117 :label="$t('pageFirmware.form.imageFile')"
118 label-for="image-file"
119 >
120 <b-form-text id="image-file-help-block">
121 {{ $t('pageFirmware.form.onlyTarFilesAccepted') }}
122 </b-form-text>
123 <b-form-file
124 id="image-file"
125 v-model="file"
126 accept=".tar"
127 aria-describedby="image-file-help-block"
Yoshie Muranakacd933e32020-08-16 22:09:26 -0700128 :browse-text="$t('global.fileUpload.browseText')"
129 :drop-placeholder="$t('global.fileUpload.dropPlaceholder')"
130 :placeholder="$t('global.fileUpload.placeholder')"
Yoshie Muranaka92a0a4a2020-07-15 10:30:31 -0700131 :disabled="isPageDisabled"
132 :state="getValidationState($v.file)"
133 @input="$v.file.$touch()"
134 />
135 <b-form-invalid-feedback role="alert">
136 {{ $t('global.form.required') }}
137 </b-form-invalid-feedback>
138 </b-form-group>
139 </template>
140
141 <!-- TFTP Server Upload -->
142 <template v-else>
143 <b-form-group
144 :label="$t('pageFirmware.form.tftpServerIpAddress')"
145 label-for="tftp-ip"
146 >
147 <b-form-input
148 id="tftp-id"
149 v-model="tftpIpAddress"
150 type="text"
Yoshie Muranaka92a0a4a2020-07-15 10:30:31 -0700151 :state="getValidationState($v.tftpIpAddress)"
152 :disabled="isPageDisabled"
153 @input="$v.tftpIpAddress.$touch()"
154 />
155 <b-form-invalid-feedback role="alert">
156 {{ $t('global.form.fieldRequired') }}
157 </b-form-invalid-feedback>
158 </b-form-group>
159 <b-form-group
160 :label="$t('pageFirmware.form.imageFileName')"
161 label-for="tftp-file-name"
162 >
163 <b-form-input
164 id="tftp-file-name"
165 v-model="tftpFileName"
166 type="text"
167 :state="getValidationState($v.tftpFileName)"
168 :disabled="isPageDisabled"
169 @input="$v.tftpFileName.$touch()"
170 />
171 <b-form-invalid-feedback role="alert">
172 {{ $t('global.form.fieldRequired') }}
173 </b-form-invalid-feedback>
174 </b-form-group>
175 </template>
176
177 <!-- Info alert -->
178 <alert variant="info" class="mt-4 mb-5">
179 <p class="font-weight-bold mb-1">
180 {{ $t('pageFirmware.alert.updateProcess') }}
181 </p>
182 <p>{{ $t('pageFirmware.alert.updateProcessInfo') }}</p>
183 </alert>
184 <b-form-group>
185 <b-btn type="submit" variant="primary" :disabled="isPageDisabled">
186 {{ $t('pageFirmware.form.uploadAndRebootBmc') }}
187 </b-btn>
188 </b-form-group>
189 </b-form>
190 </page-section>
191 </b-col>
192 </b-row>
193
194 <!-- Modals -->
195 <modal-upload @ok="uploadFirmware" />
196 <modal-reboot-backup
Yoshie Muranaka3a359432020-08-14 08:48:47 -0700197 :current="systemFirmwareVersion"
Yoshie Muranaka92a0a4a2020-07-15 10:30:31 -0700198 :backup="backupFirmwareVersion"
199 @ok="rebootFromBackup"
200 />
201 </b-container>
202</template>
203
204<script>
205import { requiredIf } from 'vuelidate/lib/validators';
206import { mapGetters } from 'vuex';
207
208import PageSection from '@/components/Global/PageSection';
209import PageTitle from '@/components/Global/PageTitle';
210import Alert from '@/components/Global/Alert';
211import ModalUpload from './FirmwareModalUpload';
212import ModalRebootBackup from './FirmwareModalRebootBackup';
213
214import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
215import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
216import BVToastMixin from '@/components/Mixins/BVToastMixin';
217
218export default {
219 name: 'Firmware',
220 components: {
221 Alert,
222 ModalRebootBackup,
223 ModalUpload,
224 PageSection,
225 PageTitle
226 },
227 mixins: [BVToastMixin, LoadingBarMixin, VuelidateMixin],
228 data() {
229 return {
230 isWorkstationSelected: true,
231 file: null,
232 tftpIpAddress: null,
233 tftpFileName: null,
234 timeoutId: null
235 };
236 },
237 computed: {
238 hostStatus() {
239 return this.$store.getters['global/hostStatus'];
240 },
241 isHostOff() {
242 return this.hostStatus === 'off' ? true : false;
243 },
244 isOperationInProgress() {
245 return this.$store.getters['controls/isOperationInProgress'];
246 },
247 ...mapGetters('firmware', [
248 'backupFirmwareStatus',
249 'backupFirmwareVersion',
250 'isRebootFromBackupAvailable',
251 'systemFirmwareVersion'
252 ]),
253 isPageDisabled() {
254 return !this.isHostOff || this.loading || this.isOperationInProgress;
255 }
256 },
257 watch: {
258 isWorkstationSelected: function() {
259 this.$v.$reset();
260 this.file = null;
261 this.tftpIpAddress = null;
262 this.tftpFileName = null;
263 }
264 },
265 created() {
266 this.startLoader();
267 this.$store.dispatch('firmware/getUpdateServiceApplyTime');
268 Promise.all([
269 this.$store.dispatch('global/getHostStatus'),
270 this.$store.dispatch('firmware/getSystemFirwareVersion')
271 ]).finally(() => this.endLoader());
272 },
273 beforeRouteLeave(to, from, next) {
274 this.hideLoader();
275 this.clearRebootTimeout();
276 next();
277 },
278 validations() {
279 return {
280 file: {
281 required: requiredIf(function() {
282 return this.isWorkstationSelected;
283 })
284 },
285 tftpIpAddress: {
286 required: requiredIf(function() {
287 return !this.isWorkstationSelected;
288 })
289 },
290 tftpFileName: {
291 required: requiredIf(function() {
292 return !this.isWorkstationSelected;
293 })
294 }
295 };
296 },
297 methods: {
298 uploadFirmware() {
299 const startTime = this.$options.filters.formatTime(new Date());
300 this.setRebootTimeout(360000); //6 minute timeout
301 this.infoToast(
302 this.$t('pageFirmware.toast.infoUploadStartTimeMessage', { startTime }),
303 this.$t('pageFirmware.toast.infoUploadStartTimeTitle')
304 );
305 if (this.isWorkstationSelected) {
306 this.dispatchWorkstationUpload();
307 } else {
308 this.dispatchTftpUpload();
309 }
310 },
311 dispatchWorkstationUpload() {
312 this.$store
313 .dispatch('firmware/uploadFirmware', this.file)
314 .then(success =>
315 this.infoToast(
316 success,
317 this.$t('pageFirmware.toast.successUploadTitle')
318 )
319 )
320 .catch(({ message }) => {
321 this.errorToast(message);
322 this.clearRebootTimeout();
323 });
324 },
325 dispatchTftpUpload() {
326 const data = {
327 address: this.tftpIpAddress,
328 filename: this.tftpFileName
329 };
330 this.$store
331 .dispatch('firmware/uploadFirmwareTFTP', data)
332 .then(success =>
333 this.infoToast(
334 success,
335 this.$t('pageFirmware.toast.successUploadTitle')
336 )
337 )
338 .catch(({ message }) => {
339 this.errorToast(message);
340 this.clearRebootTimeout();
341 });
342 },
343 rebootFromBackup() {
344 this.setRebootTimeout();
345 this.$store
346 .dispatch('firmware/switchFirmwareAndReboot')
347 .then(success =>
348 this.infoToast(success, this.$t('global.status.success'))
349 )
350 .catch(({ message }) => {
351 this.errorToast(message);
352 this.clearRebootTimeout();
353 });
354 },
355 setRebootTimeout(timeoutMs = 60000) {
356 // Set a timeout to disable page interactions while
357 // an upload or BMC reboot is in progress
358 this.startLoader();
359 this.timeoutId = setTimeout(() => {
360 this.endLoader();
361 this.infoToast(
362 this.$t('pageFirmware.toast.infoRefreshApplicationMessage'),
363 this.$t('pageFirmware.toast.infoRefreshApplicationTitle')
364 );
365 }, timeoutMs);
366 },
367 clearRebootTimeout() {
368 if (this.timeoutId) {
369 clearTimeout(this.timeoutId);
370 this.endLoader();
371 }
372 },
373 onSubmitUpload() {
374 this.$v.$touch();
375 if (this.$v.$invalid) return;
376 this.$bvModal.show('modal-upload');
377 },
378 onClickShutDown() {
379 this.$bvModal
380 .msgBoxConfirm(this.$t('pageFirmware.modal.serverShutdownMessage'), {
381 title: this.$t('pageFirmware.modal.serverShutdownWillCauseOutage'),
382 okTitle: this.$t('pageFirmware.modal.shutDownServer'),
383 okVariant: 'danger'
384 })
385 .then(shutdownConfirmed => {
386 if (shutdownConfirmed)
387 this.$store.dispatch('controls/hostSoftPowerOff');
388 });
389 }
390 }
391};
392</script>
393
394<style lang="scss" scoped>
395.update-code {
396 border-left: none;
397 @include media-breakpoint-up(xl) {
398 border-left: 1px solid gray('300');
399 }
400}
401</style>