diff options
author | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-02-27 01:23:15 +0300 |
---|---|---|
committer | Derick Montague <derick.montague@ibm.com> | 2020-03-10 22:50:39 +0300 |
commit | c05ff648da07d8e2221d67eac83b6c6581d371a0 (patch) | |
tree | 5acf2746cf1551b36df57421f167a0e9b6100095 | |
parent | a4b9e40a15cefe8bf4c48bf994ac91ea5ee94ef6 (diff) | |
download | webui-vue-c05ff648da07d8e2221d67eac83b6c6581d371a0.tar.xz |
Add host boot settings to power operations page
Added BootSettingsStore and component to handle changing boot
source, boot override option and TPM required option.
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I885dd6008aceb34b319953a2e9b6416d848baf16
-rw-r--r-- | src/assets/styles/_form-components.scss | 10 | ||||
-rw-r--r-- | src/locales/en-US.json | 12 | ||||
-rw-r--r-- | src/store/index.js | 2 | ||||
-rw-r--r-- | src/store/modules/Control/BootSettingsStore.js | 136 | ||||
-rw-r--r-- | src/views/Control/ServerPowerOperations/BootSettings.vue | 148 | ||||
-rw-r--r-- | src/views/Control/ServerPowerOperations/ServerPowerOperations.vue | 20 |
6 files changed, 325 insertions, 3 deletions
diff --git a/src/assets/styles/_form-components.scss b/src/assets/styles/_form-components.scss index 89abfb3f..35274e70 100644 --- a/src/assets/styles/_form-components.scss +++ b/src/assets/styles/_form-components.scss @@ -25,3 +25,13 @@ border-bottom: 2px solid $danger !important; } } + +.custom-control { + .custom-control-input[disabled=disabled] { + & + .custom-control-label { + // Disabled label for checkbox, radio, + // switch bootstrap form components + color: $gray-700!important; + } + } +} diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 8a77b931..0bf40513 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -183,9 +183,11 @@ "pageServerPowerOperations": { "currentStatus": "Current status", "hostname": "Hostname", + "hostOsBootSettings": "Host OS boot settings", "hostStatus": "Host status", "immediateReboot": "Immediate – Server reboots without OS shutting down; may cause data corruption", "immediateShutdown": "Immediate - Server shuts down without OS shutting down; may cause data corruption", + "oneTimeBootWarning": "Pending one time boot. Next boot will be performed with the specified one time boot settings. Subsequent boots will be performed with the default settings.", "operationInProgress": "There are no options to display while a power operation is in progress. When complete, power operations will be displayed here.", "operations": "Operations", "orderlyReboot": "Orderly – OS shuts down, then server reboots", @@ -195,11 +197,21 @@ "rebootServer": "Reboot server", "shutDown": "Shut down", "shutdownServer": "Shutdown server", + "bootSettings": { + "bootSettingsOverride": "Boot settings override", + "enableOneTimeBoot": "Enable one time boot", + "tpmRequiredPolicy": "TPM required policy", + "tpmRequiredPolicyHelper": "Enable to ensure the system only boots when the TPM is functional." + }, "modal": { "confirmRebootMessage": "Are you sure you want to reboot?", "confirmRebootTitle": "Server reboot will cause outage", "confirmShutdownMessage": "Are you sure you want to shut down?", "confirmShutdownTitle": "Server shutdown will cause outage" + }, + "toast": { + "errorSaveSettings": "Error saving settings.", + "successSaveSettings": "Successfully saved settings." } } }
\ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index 6bad517c..27216990 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -6,6 +6,7 @@ import AuthenticationStore from './modules/Authentication/AuthenticanStore'; import LocalUserManagementStore from './modules/AccessControl/LocalUserMangementStore'; import OverviewStore from './modules/Overview/OverviewStore'; import FirmwareStore from './modules/Configuration/FirmwareStore'; +import BootSettingsStore from './modules/Control/BootSettingsStore'; import ControlStore from './modules/Control/ControlStore'; import PowerControlStore from './modules/Control/PowerControlStore'; import NetworkSettingStore from './modules/Configuration/NetworkSettingsStore'; @@ -25,6 +26,7 @@ export default new Vuex.Store({ localUsers: LocalUserManagementStore, overview: OverviewStore, firmware: FirmwareStore, + hostBootSettings: BootSettingsStore, controls: ControlStore, powerControl: PowerControlStore, networkSettings: NetworkSettingStore, diff --git a/src/store/modules/Control/BootSettingsStore.js b/src/store/modules/Control/BootSettingsStore.js new file mode 100644 index 00000000..8da586aa --- /dev/null +++ b/src/store/modules/Control/BootSettingsStore.js @@ -0,0 +1,136 @@ +import api from '../../api'; +import i18n from '../../../i18n'; + +const BootSettingsStore = { + namespaced: true, + state: { + bootSourceOptions: [], + bootSource: null, + overrideEnabled: null, + tpmEnabled: null + }, + getters: { + bootSourceOptions: state => state.bootSourceOptions, + bootSource: state => state.bootSource, + overrideEnabled: state => state.overrideEnabled, + tpmEnabled: state => state.tpmEnabled + }, + mutations: { + setBootSourceOptions: (state, bootSourceOptions) => + (state.bootSourceOptions = bootSourceOptions), + setBootSource: (state, bootSource) => (state.bootSource = bootSource), + setOverrideEnabled: (state, overrideEnabled) => { + if (overrideEnabled === 'Once') { + state.overrideEnabled = true; + } else { + // 'Continuous' or 'Disabled' + state.overrideEnabled = false; + } + }, + setTpmPolicy: (state, tpmEnabled) => (state.tpmEnabled = tpmEnabled) + }, + actions: { + getBootSettings({ commit }) { + api + .get('/redfish/v1/Systems/system/') + .then(({ data: { Boot } }) => { + commit( + 'setBootSourceOptions', + Boot['BootSourceOverrideTarget@Redfish.AllowableValues'] + ); + commit('setOverrideEnabled', Boot.BootSourceOverrideEnabled); + commit('setBootSource', Boot.BootSourceOverrideTarget); + }) + .catch(error => console.log(error)); + }, + saveBootSettings({ commit, dispatch }, { bootSource, overrideEnabled }) { + const data = { Boot: {} }; + data.Boot.BootSourceOverrideTarget = bootSource; + + if (overrideEnabled) { + data.Boot.BootSourceOverrideEnabled = 'Once'; + } else if (bootSource === 'None') { + data.Boot.BootSourceOverrideEnabled = 'Disabled'; + } else { + data.Boot.BootSourceOverrideEnabled = 'Continuous'; + } + + return api + .patch('/redfish/v1/Systems/system', data) + .then(response => { + // If request success, commit the values + commit('setBootSource', data.Boot.BootSourceOverrideTarget); + commit('setOverrideEnabled', data.Boot.BootSourceOverrideEnabled); + return response; + }) + .catch(error => { + console.log(error); + // If request error, GET saved options + dispatch('getBootSettings'); + return error; + }); + }, + getTpmPolicy({ commit }) { + // TODO: switch to Redfish when available + api + .get('/xyz/openbmc_project/control/host0/TPMEnable') + .then(({ data: { data: { TPMEnable } } }) => + commit('setTpmPolicy', TPMEnable) + ) + .catch(error => console.log(error)); + }, + saveTpmPolicy({ commit, dispatch }, tpmEnabled) { + // TODO: switch to Redfish when available + const data = { data: tpmEnabled }; + return api + .put( + '/xyz/openbmc_project/control/host0/TPMEnable/attr/TPMEnable', + data + ) + .then(response => { + // If request success, commit the values + commit('setTpmPolicy', tpmEnabled); + return response; + }) + .catch(error => { + console.log(error); + // If request error, GET saved policy + dispatch('getTpmPolicy'); + return error; + }); + }, + async saveSettings( + { dispatch }, + { bootSource, overrideEnabled, tpmEnabled } + ) { + const promises = []; + + if (bootSource !== null || overrideEnabled !== null) { + promises.push( + dispatch('saveBootSettings', { bootSource, overrideEnabled }) + ); + } + if (tpmEnabled !== null) { + promises.push(dispatch('saveTpmPolicy', tpmEnabled)); + } + + return await api.all(promises).then( + api.spread((...responses) => { + let message = i18n.t( + 'pageServerPowerOperations.toast.successSaveSettings' + ); + responses.forEach(response => { + if (response instanceof Error) { + throw new Error( + i18n.t('pageServerPowerOperations.toast.errorSaveSettings') + ); + } + }); + return message; + }) + ); + } + } +}; + +export default BootSettingsStore; diff --git a/src/views/Control/ServerPowerOperations/BootSettings.vue b/src/views/Control/ServerPowerOperations/BootSettings.vue new file mode 100644 index 00000000..c912749f --- /dev/null +++ b/src/views/Control/ServerPowerOperations/BootSettings.vue @@ -0,0 +1,148 @@ +<template> + <div class="boot-settings p-3"> + <b-form novalidate @submit.prevent="handleSubmit"> + <b-form-group + :label=" + $t('pageServerPowerOperations.bootSettings.bootSettingsOverride') + " + label-for="boot-option" + class="mb-3" + > + <b-form-select + id="boot-option" + v-model="form.bootOption" + :disabled="bootSourceOptions.length === 0" + :options="bootSourceOptions" + @change="onChangeSelect" + > + </b-form-select> + </b-form-group> + <b-form-checkbox + v-model="form.oneTimeBoot" + class="mb-4" + :disabled="form.bootOption === 'None'" + @change="$v.form.oneTimeBoot.$touch()" + > + {{ $t('pageServerPowerOperations.bootSettings.enableOneTimeBoot') }} + </b-form-checkbox> + <b-form-group + :label="$t('pageServerPowerOperations.bootSettings.tpmRequiredPolicy')" + > + <b-form-text id="tpm-required-policy-help-block"> + {{ + $t('pageServerPowerOperations.bootSettings.tpmRequiredPolicyHelper') + }} + </b-form-text> + <b-form-checkbox + id="tpm-required-policy" + v-model="form.tpmPolicyOn" + switch + aria-describedby="tpm-required-policy-help-block" + @change="$v.form.tpmPolicyOn.$touch()" + > + {{ + form.tpmPolicyOn ? $t('global.status.on') : $t('global.status.off') + }} + </b-form-checkbox> + </b-form-group> + <b-button + variant="primary" + type="submit" + class="mb-3" + :disabled="!$v.form.$anyDirty" + > + {{ $t('global.action.save') }} + </b-button> + </b-form> + </div> +</template> + +<script> +import { mapState } from 'vuex'; +import BVToastMixin from '../../../components/Mixins/BVToastMixin'; + +export default { + name: 'BootSettings', + mixins: [BVToastMixin], + data() { + return { + form: { + bootOption: this.$store.getters['hostBootSettings/bootSource'], + oneTimeBoot: this.$store.getters['hostBootSettings/overrideEnabled'], + tpmPolicyOn: this.$store.getters['hostBootSettings/tpmEnabled'] + } + }; + }, + computed: { + ...mapState('hostBootSettings', [ + 'bootSourceOptions', + 'bootSource', + 'overrideEnabled', + 'tpmEnabled' + ]) + }, + watch: { + bootSource: function(value) { + this.form.bootOption = value; + }, + overrideEnabled: function(value) { + this.form.oneTimeBoot = value; + }, + tpmEnabled: function(value) { + this.form.tpmPolicyOn = value; + } + }, + validations: { + // Empty validations to leverage vuelidate form states + // to check for changed values + form: { + bootOption: {}, + oneTimeBoot: {}, + tpmPolicyOn: {} + } + }, + created() { + this.$store.dispatch('hostBootSettings/getBootSettings'); + this.$store.dispatch('hostBootSettings/getTpmPolicy'); + }, + methods: { + handleSubmit() { + const bootSettingsChanged = + this.$v.form.bootOption.$dirty || this.$v.form.oneTimeBoot.$dirty; + const tpmPolicyChanged = this.$v.form.tpmPolicyOn.$dirty; + let settings; + let bootSource = null; + let overrideEnabled = null; + let tpmEnabled = null; + + if (bootSettingsChanged) { + // If bootSource or overrideEnabled changed get + // both current values to send with request + bootSource = this.form.bootOption; + overrideEnabled = this.form.oneTimeBoot; + } + if (tpmPolicyChanged) tpmEnabled = this.form.tpmPolicyOn; + settings = { bootSource, overrideEnabled, tpmEnabled }; + + this.$store + .dispatch('hostBootSettings/saveSettings', settings) + .then(message => this.successToast(message)) + .catch(({ message }) => this.errorToast(message)) + .finally(() => { + this.$v.form.$reset(); + }); + }, + onChangeSelect(selectedOption) { + this.$v.form.bootOption.$touch(); + // Disable one time boot if selected boot option is 'None' + if (selectedOption === 'None') this.form.oneTimeBoot = false; + } + } +}; +</script> + +<style lang="scss" scoped> +.boot-settings { + background-color: $gray-200; +} +</style> diff --git a/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue b/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue index c9b02b3e..e63d0732 100644 --- a/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue +++ b/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue @@ -2,7 +2,7 @@ <b-container fluid> <page-title /> <b-row> - <b-col md="8" lg="8" xl="6"> + <b-col md="8" xl="6"> <page-section :section-title="$t('pageServerPowerOperations.currentStatus')" > @@ -26,10 +26,20 @@ </b-col> </b-row> <b-row> - <b-col md="8" lg="7" xl="8"> + <b-col sm="8" md="6" xl="4"> + <page-section + :section-title="$t('pageServerPowerOperations.hostOsBootSettings')" + > + <boot-settings /> + </page-section> + </b-col> + <b-col sm="8" md="6" xl="7"> <page-section :section-title="$t('pageServerPowerOperations.operations')" > + <b-alert :show="oneTimeBootEnabled" variant="warning"> + {{ $t('pageServerPowerOperations.oneTimeBootWarning') }} + </b-alert> <template v-if="isOperationInProgress"> {{ $t('pageServerPowerOperations.operationInProgress') }} </template> @@ -101,10 +111,11 @@ import PageTitle from '../../../components/Global/PageTitle'; import PageSection from '../../../components/Global/PageSection'; import BVToastMixin from '../../../components/Mixins/BVToastMixin'; +import BootSettings from './BootSettings'; export default { name: 'ServerPowerOperations', - components: { PageTitle, PageSection }, + components: { PageTitle, PageSection, BootSettings }, mixins: [BVToastMixin], data() { return { @@ -123,6 +134,9 @@ export default { }, isOperationInProgress() { return this.$store.getters['controls/isOperationInProgress']; + }, + oneTimeBootEnabled() { + return this.$store.getters['hostBootSettings/overrideEnabled']; } }, created() { |