summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/assets/styles/_form-components.scss10
-rw-r--r--src/locales/en-US.json12
-rw-r--r--src/store/index.js2
-rw-r--r--src/store/modules/Control/BootSettingsStore.js136
-rw-r--r--src/views/Control/ServerPowerOperations/BootSettings.vue148
-rw-r--r--src/views/Control/ServerPowerOperations/ServerPowerOperations.vue20
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() {