summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes4
-rw-r--r--OWNERS8
-rw-r--r--src/components/Global/FormFile.vue1
-rw-r--r--src/env/store/ibm.js3
-rw-r--r--src/locales/en-US.json15
-rw-r--r--src/locales/ru-RU.json3
-rw-r--r--src/store/api.js25
-rw-r--r--src/store/modules/Authentication/AuthenticanStore.js53
-rw-r--r--src/store/modules/GlobalStore.js26
-rw-r--r--src/store/modules/HardwareStatus/BmcStore.js2
-rw-r--r--src/store/modules/HardwareStatus/MemoryStore.js2
-rw-r--r--src/store/modules/HardwareStatus/ProcessorStore.js2
-rw-r--r--src/store/modules/HardwareStatus/ServerLedStore.js4
-rw-r--r--src/store/modules/HardwareStatus/SystemStore.js7
-rw-r--r--src/store/modules/Logs/DumpsStore.js14
-rw-r--r--src/store/modules/Logs/EventLogStore.js22
-rw-r--r--src/store/modules/Logs/PostCodeLogsStore.js6
-rw-r--r--src/store/modules/Operations/BootSettingsStore.js9
-rw-r--r--src/store/modules/Operations/ControlStore.js21
-rw-r--r--src/store/modules/Operations/FactoryResetStore.js13
-rw-r--r--src/store/modules/Operations/FirmwareStore.js61
-rw-r--r--src/store/modules/Operations/KeyClearStore.js2
-rw-r--r--src/store/modules/Operations/VirtualMediaStore.js6
-rw-r--r--src/store/modules/SecurityAndAccess/CertificatesStore.js110
-rw-r--r--src/store/modules/SecurityAndAccess/PoliciesStore.js18
-rw-r--r--src/store/modules/Settings/DateTimeStore.js14
-rw-r--r--src/store/modules/Settings/NetworkStore.js192
-rw-r--r--src/store/modules/Settings/PowerPolicyStore.js4
-rw-r--r--src/views/HardwareStatus/Inventory/InventoryTableFans.vue41
-rw-r--r--src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue39
-rw-r--r--src/views/Login/Login.vue5
-rw-r--r--src/views/Logs/Dumps/Dumps.vue2
-rw-r--r--src/views/Logs/EventLogs/EventLogs.vue20
-rw-r--r--src/views/Operations/Firmware/FirmwareFormUpdate.vue78
-rw-r--r--src/views/Overview/OverviewDumps.vue2
-rw-r--r--src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue24
-rw-r--r--src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue2
-rw-r--r--src/views/SecurityAndAccess/UserManagement/UserManagement.vue43
-rw-r--r--src/views/Settings/Network/ModalDefaultGateway.vue114
-rw-r--r--src/views/Settings/Network/ModalIpv6.vue133
-rw-r--r--src/views/Settings/Network/Network.vue30
-rw-r--r--src/views/Settings/Network/NetworkGlobalSettings.vue144
-rw-r--r--src/views/Settings/Network/TableIpv6.vue289
43 files changed, 1322 insertions, 291 deletions
diff --git a/.gitattributes b/.gitattributes
index 94f480de..f702d2d9 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,3 @@
-* text=auto eol=lf \ No newline at end of file
+* text=auto eol=lf
+*.png binary
+*.woff binary \ No newline at end of file
diff --git a/OWNERS b/OWNERS
index 8447b199..8398b5fe 100644
--- a/OWNERS
+++ b/OWNERS
@@ -37,7 +37,7 @@ owners:
reviewers:
- a.nikhil@ibm.com
-- Renuka.Sharanya.Pundla@ibm.com
+- jwestover@nvidia.com
- sivaprabug@ami.com
matchers:
@@ -46,15 +46,15 @@ openbmc:
- name: Gunnar Mills
email: gunnar@gmills.xyz
discord: GunnarM
+- name: Jason Westover
+ email: jwestover@nvidia.com
+ discord: jasonwestover
- name: Kirankumar Ballapalli
email: kirankumarb@ami.com
discord: kirankumarb
- name: Nikhil Ashoka
email: a.nikhil@ibm.com
discord: NikhilAshoka
-- name: Renuka Sharanya Pundla
- email: Renuka.Sharanya.Pundla@ibm.com
- discord: Renuka Sharanya
- name: Sivaprabu Ganesan
email: sivaprabug@ami.com
discord: sivaprabug
diff --git a/src/components/Global/FormFile.vue b/src/components/Global/FormFile.vue
index e18d6148..50ac9614 100644
--- a/src/components/Global/FormFile.vue
+++ b/src/components/Global/FormFile.vue
@@ -107,6 +107,7 @@ export default {
display: flex;
align-items: center;
background-color: theme-color('light');
+ word-break: break-all; // break long file name into multiple lines
.btn {
width: 36px;
height: 36px;
diff --git a/src/env/store/ibm.js b/src/env/store/ibm.js
index 86fc52d1..4383948f 100644
--- a/src/env/store/ibm.js
+++ b/src/env/store/ibm.js
@@ -1,11 +1,8 @@
import store from '@/store';
-import DumpsStore from '@/store/modules/Logs/DumpsStore';
import KeyClearStore from '@/store/modules/Operations/KeyClearStore';
store.unregisterModule('virtualMedia');
-store.registerModule('dumps', DumpsStore);
-
store.registerModule('key-clear', KeyClearStore);
export default store;
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 8ba7ac94..bd820666 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -282,6 +282,7 @@
"errorLogStatusUpdate": "Error updating log status.",
"errorResolveLogs": "Error resolving %{count} log. | Error resolving %{count} logs.",
"errorUnresolveLogs": "Error unresolving %{count} log. | Error unresolving %{count} logs.",
+ "errorDownloadEventEntry": "Error download event log entry.",
"successDelete": "Successfully deleted %{count} log. | Successfully deleted %{count} logs.",
"successResolveLogs": "Successfully resolved %{count} log. | Successfully resolved %{count} logs.",
"successUnresolveLogs": "Successfully unresolved %{count} log. | Successfully unresolved %{count} logs."
@@ -347,7 +348,6 @@
"fileSource": "File source",
"imageFile": "Image file",
"startUpdate": "Start update",
- "tftpServer": "TFTP server",
"workstation": "Workstation"
}
},
@@ -554,6 +554,7 @@
"accountPolicySettings": "Account policy settings",
"addUser": "Add user",
"deleteUser": "Delete user | Delete users",
+ "disableUser": "Disable user | Disable users",
"editUser": "Edit user",
"viewPrivilegeRoleDescriptions": "View privilege role descriptions",
"modal": {
@@ -561,6 +562,7 @@
"accountStatus": "Account status",
"automaticAfterTimeout": "Automatic after timeout",
"batchDeleteConfirmMessage": "Are you sure you want to delete %{count} user? This action cannot be undone. | Are you sure you want to delete %{count} users? This action cannot be undone.",
+ "batchDisableConfirmMessage": "Are you sure you want to disable %{count} user? | Are you sure you want to disable %{count} users?",
"cannotStartWithANumber": "Cannot start with a number",
"clickSaveToUnlockAccount": "Click \"Save\" to unlock account",
"confirmUserPassword": "Confirm user password",
@@ -703,16 +705,21 @@
},
"pageNetwork": {
"dhcp": "DHCP",
+ "dhcp6": "DHCPv6",
"domainName": "domain name",
"dns": "DNS server",
"fqdn": "FQDN",
"hostname": "Hostname",
+ "ipVersion": "Version of IP",
"interfaceSection": "Interface settings",
"ipv4": "IPv4",
"ipv4Addresses": "IPv4 addresses",
"ipv6": "IPv6",
+ "ipv6Addresses": "IPv6 addresses",
"linkStatus": "Link status",
"macAddress": "MAC address",
+ "gateway": "Gateway",
+ "ipv6DefaultGateway": "IPv6 Default Gateway",
"network": "network",
"networkSettings": "Network settings",
"ntp": "NTP server",
@@ -728,7 +735,9 @@
"dhcpConfirmTitle": "%{dhcpState} DHCP",
"editHostnameTitle": "Edit hostname",
"editMacAddressTitle": "Edit MAC address",
+ "editIPv6DefaultGatewayTitle": "Edit IPv6 Default Gateway",
"gateway": "Gateway",
+ "prefixLength": "Prefix Length",
"ipAddress": "IP address",
"staticDns": "Static DNS",
"subnetMask": "Subnet mask"
@@ -736,11 +745,15 @@
"table": {
"addDnsAddress": "Add IP address",
"addIpv4Address": "Add static IPv4 address",
+ "addIpv6Address": "Add static IPv6 address",
"addressOrigin": "Address origin",
+ "prefixLength": "Prefix Length",
"deleteDns": "Delete DNS address",
"deleteIpv4": "Delete IPv4 address",
+ "deleteIpv6": "Delete IPv6 address",
"editDns": "Edit DNS address",
"editIpv4": "Edit IPv4 address",
+ "editIpv6": "Edit IPv6 address",
"gateway": "Gateway",
"ipAddress": "IP address",
"subnet": "Subnet mask"
diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json
index dcf7c597..4a6106de 100644
--- a/src/locales/ru-RU.json
+++ b/src/locales/ru-RU.json
@@ -347,7 +347,6 @@
"fileSource": "Источник файла",
"imageFile": "Файл образа",
"startUpdate": "Начать обновление",
- "tftpServer": "TFTP сервер",
"workstation": "Рабочая станция"
}
},
@@ -552,6 +551,7 @@
"accountPolicySettings": "Настройки политики учётной записи",
"addUser": "Добавить пользователя",
"deleteUser": "Удалить пользователя | Удалить пользователей",
+ "disableUser": "Отключить пользователя | Отключить пользователей",
"editUser": "Редактировать пользователя",
"viewPrivilegeRoleDescriptions": "Просмотр описаний привилегий ролей",
"modal": {
@@ -559,6 +559,7 @@
"accountStatus": "Статус учётной записи",
"automaticAfterTimeout": "Автоматически после истечения таймаута",
"batchDeleteConfirmMessage": "Вы уверены, что хотите удалить %{count} пользователя? Это действие нельзя отменить. | Вы уверены, что хотите удалить %{count} пользователей? Это действие нельзя отменить.",
+ "batchDisableConfirmMessage": "Вы уверены, что хотите отключить пользователя %{count}? | Вы уверены, что хотите отключить пользователей %{count}?",
"cannotStartWithANumber": "Не может начинаться с цифры",
"clickSaveToUnlockAccount": "Нажмите \"Сохранить\" для разблокировки учётной записи",
"confirmUserPassword": "Подтвердите пароль пользователя",
diff --git a/src/store/api.js b/src/store/api.js
index 0bd84e62..664e2b76 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -1,4 +1,5 @@
import Axios from 'axios';
+import router from '../router';
import { setupCache, buildWebStorage } from 'axios-cache-interceptor';
//Do not change store import.
@@ -36,11 +37,14 @@ api.interceptors.response.use(undefined, (error) => {
}
}
+ // Check if action is unauthorized.
if (response.status == 403) {
- // Check if action is unauthorized.
- // Toast error message will appear on screen
- // when the action is unauthorized.
- store.commit('global/setUnauthorized');
+ if (isPasswordExpired(response)) {
+ router.push('/change-password');
+ } else {
+ // Toast error message will appear on screen.
+ store.commit('global/setUnauthorized');
+ }
}
return Promise.reject(error);
@@ -68,6 +72,9 @@ export default {
spread(callback) {
return Axios.spread(callback);
},
+ set_auth_token(token) {
+ axiosInstance.defaults.headers.common['X-Auth-Token'] = token;
+ },
};
export const getResponseCount = (responses) => {
@@ -84,3 +91,13 @@ export const getResponseCount = (responses) => {
errorCount,
};
};
+
+export const isPasswordExpired = (response) => {
+ let extInfoMsgs = response?.data?.['@Message.ExtendedInfo'];
+ return (
+ extInfoMsgs &&
+ extInfoMsgs.find(
+ (i) => i.MessageId.split('.')[4] === 'PasswordChangeRequired',
+ )
+ );
+};
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 2006661b..3122ab2f 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -1,4 +1,4 @@
-import api from '@/store/api';
+import api, { isPasswordExpired } from '@/store/api';
import Cookies from 'js-cookie';
import router from '@/router';
import { roles } from '@/router/routes';
@@ -10,21 +10,39 @@ const AuthenticationStore = {
authError: false,
xsrfCookie: Cookies.get('XSRF-TOKEN'),
isAuthenticatedCookie: Cookies.get('IsAuthenticated'),
+ sessionURI: localStorage.getItem('sessionURI'),
+ xAuthToken: null,
},
getters: {
consoleWindow: (state) => state.consoleWindow,
authError: (state) => state.authError,
isLoggedIn: (state) => {
+ // We might have gotten XSRF-TOKEN (and HttpOnly SESSION cookie) by Mutual TLS authentication,
+ // without going through explicit Session creation
return (
- state.xsrfCookie !== undefined || state.isAuthenticatedCookie == 'true'
+ state.xsrfCookie !== undefined ||
+ state.isAuthenticatedCookie == 'true' ||
+ state.xAuthToken !== null
);
},
+ // Used to authenticate WebSocket connections via subprotocol value
token: (state) => state.xsrfCookie,
},
mutations: {
- authSuccess(state) {
+ authSuccess(state, { session, token }) {
state.authError = false;
state.xsrfCookie = Cookies.get('XSRF-TOKEN');
+ // Preserve session data across page reloads and browser restarts
+ localStorage.setItem('sessionURI', session);
+ state.sessionURI = session;
+ // If we didn't get the XSRF cookie it means we are talking to a
+ // Redfish implementation that is not bmcweb. In this case get the token
+ // from headers and send it with the future requests, do not permanently
+ // save anywhere.
+ if (state.xsrfCookie === undefined) {
+ api.set_auth_token(token);
+ state.xAuthToken = token;
+ }
},
authError(state, authError = true) {
state.authError = authError;
@@ -32,33 +50,40 @@ const AuthenticationStore = {
logout(state) {
Cookies.remove('XSRF-TOKEN');
Cookies.remove('IsAuthenticated');
+ api.set_auth_token(undefined);
localStorage.removeItem('storedUsername');
state.xsrfCookie = undefined;
state.isAuthenticatedCookie = undefined;
+ localStorage.removeItem('sessionURI');
+ state.sessionURI = null;
+ state.xAuthToken = null;
+ state.consoleWindow = false;
},
- setConsoleWindow: (state, window) => (state.consoleWindow = window),
},
actions: {
login({ commit }, { username, password }) {
commit('authError', false);
return api
- .post('/login', {
- username: username,
- password: password,
+ .post('/redfish/v1/SessionService/Sessions', {
+ UserName: username,
+ Password: password,
+ })
+ .then((response) => {
+ commit('authSuccess', {
+ session: response.headers['location'],
+ token: response.headers['x-auth-token'],
+ });
+ return isPasswordExpired(response);
})
- .then(() => commit('authSuccess'))
.catch((error) => {
commit('authError');
throw new Error(error);
});
},
- logout({ commit }) {
+ logout({ commit, state }) {
api
- .post('/logout', { data: [] })
- .then(() => {
- commit('setConsoleWindow', false);
- commit('logout');
- })
+ .delete(state.sessionURI)
+ .then(() => commit('logout'))
.then(() => router.push('/login'))
.catch((error) => console.log(error));
},
diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js
index 036dc481..10d50b1a 100644
--- a/src/store/modules/GlobalStore.js
+++ b/src/store/modules/GlobalStore.js
@@ -77,9 +77,29 @@ const GlobalStore = {
},
},
actions: {
+ async getBmcPath() {
+ const serviceRoot = await api
+ .get('/redfish/v1')
+ .catch((error) => console.log(error));
+ let bmcPath = serviceRoot.data?.ManagerProvidingService?.['@odata.id'];
+ if (!bmcPath) {
+ const managers = await api
+ .get('/redfish/v1/Managers')
+ .catch((error) => console.log(error));
+ bmcPath = managers.data?.Members?.[0]?.['@odata.id'];
+ }
+ return bmcPath;
+ },
+ async getSystemPath() {
+ const systems = await api
+ .get('/redfish/v1/Systems')
+ .catch((error) => console.log(error));
+ let systemPath = systems.data?.Members?.[0]?.['@odata.id'];
+ return systemPath;
+ },
async getBmcTime({ commit }) {
return await api
- .get('/redfish/v1/Managers/bmc')
+ .get(`${await this.dispatch('global/getBmcPath')}`)
.then((response) => {
const bmcDateTime = response.data.DateTime;
const date = new Date(bmcDateTime);
@@ -87,9 +107,9 @@ const GlobalStore = {
})
.catch((error) => console.log(error));
},
- getSystemInfo({ commit }) {
+ async getSystemInfo({ commit }) {
api
- .get('/redfish/v1/Systems/system')
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then(
({
data: {
diff --git a/src/store/modules/HardwareStatus/BmcStore.js b/src/store/modules/HardwareStatus/BmcStore.js
index d96926ea..f0e4cf96 100644
--- a/src/store/modules/HardwareStatus/BmcStore.js
+++ b/src/store/modules/HardwareStatus/BmcStore.js
@@ -47,7 +47,7 @@ const BmcStore = {
actions: {
async getBmcInfo({ commit }) {
return await api
- .get('/redfish/v1/Managers/bmc')
+ .get(`${await this.dispatch('global/getBmcPath')}`)
.then(({ data }) => commit('setBmcInfo', data))
.catch((error) => console.log(error));
},
diff --git a/src/store/modules/HardwareStatus/MemoryStore.js b/src/store/modules/HardwareStatus/MemoryStore.js
index 787a0502..d9a107d3 100644
--- a/src/store/modules/HardwareStatus/MemoryStore.js
+++ b/src/store/modules/HardwareStatus/MemoryStore.js
@@ -60,7 +60,7 @@ const MemoryStore = {
actions: {
async getDimms({ commit }) {
return await api
- .get('/redfish/v1/Systems/system/Memory')
+ .get(`${await this.dispatch('global/getSystemPath')}/Memory`)
.then(({ data: { Members } }) => {
const promises = Members.map((item) => api.get(item['@odata.id']));
return api.all(promises);
diff --git a/src/store/modules/HardwareStatus/ProcessorStore.js b/src/store/modules/HardwareStatus/ProcessorStore.js
index 49f96208..446fdb9c 100644
--- a/src/store/modules/HardwareStatus/ProcessorStore.js
+++ b/src/store/modules/HardwareStatus/ProcessorStore.js
@@ -63,7 +63,7 @@ const ProcessorStore = {
actions: {
async getProcessorsInfo({ commit }) {
return await api
- .get('/redfish/v1/Systems/system/Processors')
+ .get(`${await this.dispatch('global/getSystemPath')}/Processors`)
.then(({ data: { Members = [] } }) =>
Members.map((member) => api.get(member['@odata.id'])),
)
diff --git a/src/store/modules/HardwareStatus/ServerLedStore.js b/src/store/modules/HardwareStatus/ServerLedStore.js
index af228022..d4af0648 100644
--- a/src/store/modules/HardwareStatus/ServerLedStore.js
+++ b/src/store/modules/HardwareStatus/ServerLedStore.js
@@ -17,7 +17,7 @@ const ServerLedStore = {
actions: {
async getIndicatorLedActiveState({ commit }) {
return await api
- .get('/redfish/v1/Systems/system')
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then((response) => {
commit(
'setIndicatorLedActiveState',
@@ -29,7 +29,7 @@ const ServerLedStore = {
async saveIndicatorLedActiveState({ commit }, payload) {
commit('setIndicatorLedActiveState', payload);
return await api
- .patch('/redfish/v1/Systems/system', {
+ .patch(`${await this.dispatch('global/getSystemPath')}`, {
LocationIndicatorActive: payload,
})
.catch((error) => {
diff --git a/src/store/modules/HardwareStatus/SystemStore.js b/src/store/modules/HardwareStatus/SystemStore.js
index ea519d73..87d2810b 100644
--- a/src/store/modules/HardwareStatus/SystemStore.js
+++ b/src/store/modules/HardwareStatus/SystemStore.js
@@ -37,16 +37,13 @@ const SystemStore = {
actions: {
async getSystem({ commit }) {
return await api
- .get('/redfish/v1')
- .then((response) =>
- api.get(`${response.data.Systems['@odata.id']}/system`),
- )
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then(({ data }) => commit('setSystemInfo', data))
.catch((error) => console.log(error));
},
async changeIdentifyLedState({ commit }, ledState) {
return await api
- .patch('/redfish/v1/Systems/system', {
+ .patch(`${await this.dispatch('global/getSystemPath')}`, {
LocationIndicatorActive: ledState,
})
.then(() => {
diff --git a/src/store/modules/Logs/DumpsStore.js b/src/store/modules/Logs/DumpsStore.js
index 328e3164..9391e571 100644
--- a/src/store/modules/Logs/DumpsStore.js
+++ b/src/store/modules/Logs/DumpsStore.js
@@ -24,9 +24,7 @@ const DumpsStore = {
actions: {
async getBmcDumpEntries() {
return api
- .get('/redfish/v1/')
- .then((response) => api.get(response.data.Managers['@odata.id']))
- .then((response) => api.get(`${response.data['@odata.id']}/bmc`))
+ .get(`${await this.dispatch('global/getBmcPath')}`)
.then((response) => api.get(response.data.LogServices['@odata.id']))
.then((response) => api.get(`${response.data['@odata.id']}/Dump`))
.then((response) => api.get(response.data.Entries['@odata.id']))
@@ -34,9 +32,7 @@ const DumpsStore = {
},
async getSystemDumpEntries() {
return api
- .get('/redfish/v1/')
- .then((response) => api.get(response.data.Systems['@odata.id']))
- .then((response) => api.get(`${response.data['@odata.id']}/system`))
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then((response) => api.get(response.data.LogServices['@odata.id']))
.then((response) => api.get(`${response.data['@odata.id']}/Dump`))
.then((response) => api.get(response.data.Entries['@odata.id']))
@@ -56,7 +52,7 @@ const DumpsStore = {
async createBmcDump() {
return await api
.post(
- '/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData',
+ `${await this.dispatch('global/getBmcPath')}/LogServices/Dump/Actions/LogService.CollectDiagnosticData`,
{
DiagnosticDataType: 'Manager',
OEMDiagnosticDataType: '',
@@ -70,7 +66,7 @@ const DumpsStore = {
async createSystemDump() {
return await api
.post(
- '/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData',
+ `${await this.dispatch('global/getSystemPath')}/LogServices/Dump/Actions/LogService.CollectDiagnosticData`,
{
DiagnosticDataType: 'OEM',
OEMDiagnosticDataType: 'System',
@@ -123,7 +119,7 @@ const DumpsStore = {
const totalDumpCount = state.allDumps.length;
return await api
.post(
- '/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog',
+ `${await this.dispatch('global/getBmcPath')}/LogServices/Dump/Actions/LogService.ClearLog`,
)
.then(() => {
commit('setAllDumps', []);
diff --git a/src/store/modules/Logs/EventLogStore.js b/src/store/modules/Logs/EventLogStore.js
index f7b2ead6..f302dffb 100644
--- a/src/store/modules/Logs/EventLogStore.js
+++ b/src/store/modules/Logs/EventLogStore.js
@@ -42,7 +42,9 @@ const EventLogStore = {
actions: {
async getEventLogData({ commit }) {
return await api
- .get('/redfish/v1/Systems/system/LogServices/EventLog/Entries')
+ .get(
+ `${await this.dispatch('global/getSystemPath')}/LogServices/EventLog/Entries`,
+ )
.then(({ data: { Members = [] } = {} }) => {
const eventLogs = Members.map((log) => {
const {
@@ -79,7 +81,7 @@ const EventLogStore = {
async deleteAllEventLogs({ dispatch }, data) {
return await api
.post(
- '/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog',
+ `${await this.dispatch('global/getSystemPath')}/LogServices/EventLog/Actions/LogService.ClearLog`,
)
.then(() => dispatch('getEventLogData'))
.then(() => i18n.tc('pageEventLogs.toast.successDelete', data.length))
@@ -218,6 +220,22 @@ const EventLogStore = {
throw new Error(i18n.t('pageEventLogs.toast.errorLogStatusUpdate'));
});
},
+ async downloadEntry(_, uri) {
+ return await api
+ .get(uri)
+ .then((response) => {
+ const blob = new Blob([response.data], {
+ type: response.headers['content-type'],
+ });
+ return blob;
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageEventLogs.toast.errorDownloadEventEntry'),
+ );
+ });
+ },
},
};
diff --git a/src/store/modules/Logs/PostCodeLogsStore.js b/src/store/modules/Logs/PostCodeLogsStore.js
index 7648b13c..7bd1410f 100644
--- a/src/store/modules/Logs/PostCodeLogsStore.js
+++ b/src/store/modules/Logs/PostCodeLogsStore.js
@@ -16,7 +16,9 @@ const PostCodeLogsStore = {
actions: {
async getPostCodesLogData({ commit }) {
return await api
- .get('/redfish/v1/Systems/system/LogServices/PostCodes/Entries')
+ .get(
+ `${await this.dispatch('global/getSystemPath')}/LogServices/PostCodes/Entries`,
+ )
.then(({ data: { Members = [] } = {} }) => {
const postCodeLogs = Members.map((log) => {
const { Created, MessageArgs, AdditionalDataURI } = log;
@@ -37,7 +39,7 @@ const PostCodeLogsStore = {
async deleteAllPostCodeLogs({ dispatch }, data) {
return await api
.post(
- '/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog',
+ `${await this.dispatch('global/getSystemPath')}/LogServices/PostCodes/Actions/LogService.ClearLog`,
)
.then(() => dispatch('getPostCodesLogData'))
.then(() =>
diff --git a/src/store/modules/Operations/BootSettingsStore.js b/src/store/modules/Operations/BootSettingsStore.js
index 1f5a628f..89598456 100644
--- a/src/store/modules/Operations/BootSettingsStore.js
+++ b/src/store/modules/Operations/BootSettingsStore.js
@@ -32,7 +32,7 @@ const BootSettingsStore = {
actions: {
async getBootSettings({ commit }) {
return await api
- .get('/redfish/v1/Systems/system')
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then(({ data: { Boot } }) => {
commit(
'setBootSourceOptions',
@@ -43,7 +43,10 @@ const BootSettingsStore = {
})
.catch((error) => console.log(error));
},
- saveBootSettings({ commit, dispatch }, { bootSource, overrideEnabled }) {
+ async saveBootSettings(
+ { commit, dispatch },
+ { bootSource, overrideEnabled },
+ ) {
const data = { Boot: {} };
data.Boot.BootSourceOverrideTarget = bootSource;
@@ -56,7 +59,7 @@ const BootSettingsStore = {
}
return api
- .patch('/redfish/v1/Systems/system', data)
+ .patch(`${await this.dispatch('global/getSystemPath')}`, data)
.then((response) => {
// If request success, commit the values
commit('setBootSource', data.Boot.BootSourceOverrideTarget);
diff --git a/src/store/modules/Operations/ControlStore.js b/src/store/modules/Operations/ControlStore.js
index e76063ba..320df6f9 100644
--- a/src/store/modules/Operations/ControlStore.js
+++ b/src/store/modules/Operations/ControlStore.js
@@ -51,7 +51,7 @@ const ControlStore = {
actions: {
async getLastPowerOperationTime({ commit }) {
return await api
- .get('/redfish/v1/Systems/system')
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then((response) => {
const lastReset = response.data.LastResetTime;
if (lastReset) {
@@ -61,9 +61,9 @@ const ControlStore = {
})
.catch((error) => console.log(error));
},
- getLastBmcRebootTime({ commit }) {
+ async getLastBmcRebootTime({ commit }) {
return api
- .get('/redfish/v1/Managers/bmc')
+ .get(`${await this.dispatch('global/getBmcPath')}`)
.then((response) => {
const lastBmcReset = response.data.LastResetTime;
const lastBmcRebootTime = new Date(lastBmcReset);
@@ -71,11 +71,13 @@ const ControlStore = {
})
.catch((error) => console.log(error));
},
- async rebootBmc({ dispatch }) {
+ async rebootBmc() {
const data = { ResetType: 'GracefulRestart' };
return await api
- .post('/redfish/v1/Managers/bmc/Actions/Manager.Reset', data)
- .then(() => dispatch('getLastBmcRebootTime'))
+ .post(
+ `${await this.dispatch('global/getBmcPath')}/Actions/Manager.Reset`,
+ data,
+ )
.then(() => i18n.t('pageRebootBmc.toast.successRebootStart'))
.catch((error) => {
console.log(error);
@@ -117,10 +119,13 @@ const ControlStore = {
commit('setOperationInProgress', false);
dispatch('getLastPowerOperationTime');
},
- serverPowerChange({ commit }, data) {
+ async serverPowerChange({ commit }, data) {
commit('setOperationInProgress', true);
api
- .post('/redfish/v1/Systems/system/Actions/ComputerSystem.Reset', data)
+ .post(
+ `${await this.dispatch('global/getSystemPath')}/Actions/ComputerSystem.Reset`,
+ data,
+ )
.catch((error) => {
console.log(error);
commit('setOperationInProgress', false);
diff --git a/src/store/modules/Operations/FactoryResetStore.js b/src/store/modules/Operations/FactoryResetStore.js
index 395cae19..84a8f08a 100644
--- a/src/store/modules/Operations/FactoryResetStore.js
+++ b/src/store/modules/Operations/FactoryResetStore.js
@@ -6,9 +6,12 @@ const FactoryResetStore = {
actions: {
async resetToDefaults() {
return await api
- .post('/redfish/v1/Managers/bmc/Actions/Manager.ResetToDefaults', {
- ResetType: 'ResetAll',
- })
+ .post(
+ `${await this.dispatch('global/getBmcPath')}/Actions/Manager.ResetToDefaults`,
+ {
+ ResetType: 'ResetAll',
+ },
+ )
.then(() => i18n.t('pageFactoryReset.toast.resetToDefaultsSuccess'))
.catch((error) => {
console.log('Factory Reset: ', error);
@@ -19,7 +22,9 @@ const FactoryResetStore = {
},
async resetBios() {
return await api
- .post('/redfish/v1/Systems/system/Bios/Actions/Bios.ResetBios')
+ .post(
+ `${await this.dispatch('global/getSystemPath')}/Bios/Actions/Bios.ResetBios`,
+ )
.then(() => i18n.t('pageFactoryReset.toast.resetBiosSuccess'))
.catch((error) => {
console.log('Factory Reset: ', error);
diff --git a/src/store/modules/Operations/FirmwareStore.js b/src/store/modules/Operations/FirmwareStore.js
index 7dce2316..64bd640f 100644
--- a/src/store/modules/Operations/FirmwareStore.js
+++ b/src/store/modules/Operations/FirmwareStore.js
@@ -9,11 +9,10 @@ const FirmwareStore = {
bmcActiveFirmwareId: null,
hostActiveFirmwareId: null,
applyTime: null,
+ multipartHttpPushUri: null,
httpPushUri: null,
- tftpAvailable: false,
},
getters: {
- isTftpUploadAvailable: (state) => state.tftpAvailable,
isSingleFileUploadEnabled: (state) => state.hostFirmware.length === 0,
activeBmcFirmware: (state) => {
return state.bmcFirmware.find(
@@ -43,8 +42,8 @@ const FirmwareStore = {
setHostFirmware: (state, firmware) => (state.hostFirmware = firmware),
setApplyTime: (state, applyTime) => (state.applyTime = applyTime),
setHttpPushUri: (state, httpPushUri) => (state.httpPushUri = httpPushUri),
- setTftpUploadAvailable: (state, tftpAvailable) =>
- (state.tftpAvailable = tftpAvailable),
+ setMultipartHttpPushUri: (state, multipartHttpPushUri) =>
+ (state.multipartHttpPushUri = multipartHttpPushUri),
},
actions: {
async getFirmwareInformation({ dispatch }) {
@@ -52,18 +51,18 @@ const FirmwareStore = {
dispatch('getActiveBmcFirmware');
return await dispatch('getFirmwareInventory');
},
- getActiveBmcFirmware({ commit }) {
+ async getActiveBmcFirmware({ commit }) {
return api
- .get('/redfish/v1/Managers/bmc')
+ .get(`${await this.dispatch('global/getBmcPath')}`)
.then(({ data: { Links } }) => {
const id = Links?.ActiveSoftwareImage['@odata.id'].split('/').pop();
commit('setActiveBmcFirmwareId', id);
})
.catch((error) => console.log(error));
},
- getActiveHostFirmware({ commit }) {
+ async getActiveHostFirmware({ commit }) {
return api
- .get('/redfish/v1/Systems/system/Bios')
+ .get(`${await this.dispatch('global/getSystemPath')}/Bios`)
.then(({ data: { Links } }) => {
const id = Links?.ActiveSoftwareImage['@odata.id'].split('/').pop();
commit('setActiveHostFirmwareId', id);
@@ -111,20 +110,24 @@ const FirmwareStore = {
.then(({ data }) => {
const applyTime =
data.HttpPushUriOptions.HttpPushUriApplyTime.ApplyTime;
- const allowableActions =
- data?.Actions?.['#UpdateService.SimpleUpdate']?.[
- 'TransferProtocol@Redfish.AllowableValues'
- ];
commit('setApplyTime', applyTime);
const httpPushUri = data.HttpPushUri;
commit('setHttpPushUri', httpPushUri);
- if (allowableActions?.includes('TFTP')) {
- commit('setTftpUploadAvailable', true);
- }
+ const multipartHttpPushUri = data.MultipartHttpPushUri;
+ commit('setMultipartHttpPushUri', multipartHttpPushUri);
})
.catch((error) => console.log(error));
},
- async uploadFirmware({ state }, image) {
+ async uploadFirmware({ state, dispatch }, params) {
+ if (state.multipartHttpPushUri != null) {
+ return dispatch('uploadFirmwareMultipartHttpPush', params);
+ } else if (state.httpPushUri != null) {
+ return dispatch('uploadFirmwareHttpPush', params);
+ } else {
+ console.log('Do not support firmware push update');
+ }
+ },
+ async uploadFirmwareHttpPush({ state }, { image }) {
return await api
.post(state.httpPushUri, image, {
headers: { 'Content-Type': 'application/octet-stream' },
@@ -134,16 +137,22 @@ const FirmwareStore = {
throw new Error(i18n.t('pageFirmware.toast.errorUpdateFirmware'));
});
},
- async uploadFirmwareTFTP(fileAddress) {
- const data = {
- TransferProtocol: 'TFTP',
- ImageURI: fileAddress,
- };
+ async uploadFirmwareMultipartHttpPush({ state }, { image, targets }) {
+ const formData = new FormData();
+ formData.append('UpdateFile', image);
+ let params = {};
+ if (targets != null && targets.length > 0) {
+ params.Targets = targets;
+ } else {
+ // TODO: Should be OK to leave Targets out, remove this clause
+ // when bmcweb is updated
+ params.Targets = [`${await this.dispatch('global/getBmcPath')}`];
+ }
+ formData.append('UpdateParameters', JSON.stringify(params));
return await api
- .post(
- '/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate',
- data,
- )
+ .post(state.multipartHttpPushUri, formData, {
+ headers: { 'Content-Type': 'multipart/form-data' },
+ })
.catch((error) => {
console.log(error);
throw new Error(i18n.t('pageFirmware.toast.errorUpdateFirmware'));
@@ -159,7 +168,7 @@ const FirmwareStore = {
},
};
return await api
- .patch('/redfish/v1/Managers/bmc', data)
+ .patch(`${await this.dispatch('global/getBmcPath')}`, data)
.catch((error) => {
console.log(error);
throw new Error(i18n.t('pageFirmware.toast.errorSwitchImages'));
diff --git a/src/store/modules/Operations/KeyClearStore.js b/src/store/modules/Operations/KeyClearStore.js
index 78804e75..9e5e875e 100644
--- a/src/store/modules/Operations/KeyClearStore.js
+++ b/src/store/modules/Operations/KeyClearStore.js
@@ -10,7 +10,7 @@ const KeyClearStore = {
};
return await api
.patch(
- '/redfish/v1/Systems/system/Bios/Settings',
+ `${await this.dispatch('global/getSystemPath')}/Bios/Settings`,
selectedKeyForClearing,
)
.then(() => i18n.t('pageKeyClear.toast.selectedKeyClearedSuccess'))
diff --git a/src/store/modules/Operations/VirtualMediaStore.js b/src/store/modules/Operations/VirtualMediaStore.js
index 1d27e215..9688d9c6 100644
--- a/src/store/modules/Operations/VirtualMediaStore.js
+++ b/src/store/modules/Operations/VirtualMediaStore.js
@@ -49,7 +49,7 @@ const VirtualMediaStore = {
}
return await api
- .get('/redfish/v1/Managers/bmc/VirtualMedia')
+ .get(`${await this.dispatch('global/getBmcPath')}/VirtualMedia`)
.then((response) =>
response.data.Members.map(
(virtualMedia) => virtualMedia['@odata.id'],
@@ -95,7 +95,7 @@ const VirtualMediaStore = {
async mountImage(_, { id, data }) {
return await api
.post(
- `/redfish/v1/Managers/bmc/VirtualMedia/${id}/Actions/VirtualMedia.InsertMedia`,
+ `${await this.dispatch('global/getBmcPath')}/VirtualMedia/${id}/Actions/VirtualMedia.InsertMedia`,
data,
)
.catch((error) => {
@@ -106,7 +106,7 @@ const VirtualMediaStore = {
async unmountImage(_, id) {
return await api
.post(
- `/redfish/v1/Managers/bmc/VirtualMedia/${id}/Actions/VirtualMedia.EjectMedia`,
+ `${await this.dispatch('global/getBmcPath')}/VirtualMedia/${id}/Actions/VirtualMedia.EjectMedia`,
)
.catch((error) => {
console.log('Unmount image:', error);
diff --git a/src/store/modules/SecurityAndAccess/CertificatesStore.js b/src/store/modules/SecurityAndAccess/CertificatesStore.js
index 666f5fd5..5c7c36d2 100644
--- a/src/store/modules/SecurityAndAccess/CertificatesStore.js
+++ b/src/store/modules/SecurityAndAccess/CertificatesStore.js
@@ -1,29 +1,8 @@
import api from '@/store/api';
import i18n from '@/i18n';
-export const CERTIFICATE_TYPES = [
- {
- type: 'HTTPS Certificate',
- location: '/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/',
- label: i18n.t('pageCertificates.httpsCertificate'),
- },
- {
- type: 'LDAP Certificate',
- location: '/redfish/v1/AccountService/LDAP/Certificates/',
- label: i18n.t('pageCertificates.ldapCertificate'),
- },
- {
- type: 'TrustStore Certificate',
- location: '/redfish/v1/Managers/bmc/Truststore/Certificates/',
- // Web UI will show 'CA Certificate' instead of
- // 'TrustStore Certificate' after user testing revealed
- // the term 'TrustStore Certificate' wasn't recognized/was unfamilar
- label: i18n.t('pageCertificates.caCertificate'),
- },
-];
-
-const getCertificateProp = (type, prop) => {
- const certificate = CERTIFICATE_TYPES.find(
+const getCertificateProp = (certificateTypes, type, prop) => {
+ const certificate = certificateTypes.find(
(certificate) => certificate.type === type,
);
return certificate ? certificate[prop] : null;
@@ -34,10 +13,12 @@ const CertificatesStore = {
state: {
allCertificates: [],
availableUploadTypes: [],
+ certificateTypes: [],
},
getters: {
allCertificates: (state) => state.allCertificates,
availableUploadTypes: (state) => state.availableUploadTypes,
+ certificateTypes: (state) => state.certificateTypes,
},
mutations: {
setCertificates(state, certificates) {
@@ -46,9 +27,40 @@ const CertificatesStore = {
setAvailableUploadTypes(state, availableUploadTypes) {
state.availableUploadTypes = availableUploadTypes;
},
+ setCertificateTypes(state, certificateTypes) {
+ state.certificateTypes = certificateTypes;
+ },
},
actions: {
- async getCertificates({ commit }) {
+ async getCertificateTypes({ commit }) {
+ const certificateTypes = [
+ {
+ type: 'HTTPS Certificate',
+ location: `${await this.dispatch(
+ 'global/getBmcPath',
+ )}/NetworkProtocol/HTTPS/Certificates/`,
+ label: i18n.t('pageCertificates.httpsCertificate'),
+ },
+ {
+ type: 'LDAP Certificate',
+ location: '/redfish/v1/AccountService/LDAP/Certificates/',
+ label: i18n.t('pageCertificates.ldapCertificate'),
+ },
+ {
+ type: 'TrustStore Certificate',
+ location: `${await this.dispatch(
+ 'global/getBmcPath',
+ )}/Truststore/Certificates/`,
+ // Web UI will show 'CA Certificate' instead of
+ // 'TrustStore Certificate' after user testing revealed
+ // the term 'TrustStore Certificate' wasn't recognized/was unfamilar
+ label: i18n.t('pageCertificates.caCertificate'),
+ },
+ ];
+ await commit('setCertificateTypes', certificateTypes);
+ },
+ async getCertificates({ dispatch, getters, commit }) {
+ await dispatch('getCertificateTypes');
return await api
.get('/redfish/v1/CertificateService/CertificateLocations')
.then(
@@ -75,14 +87,18 @@ const CertificatesStore = {
return {
type: Name,
location: data['@odata.id'],
- certificate: getCertificateProp(Name, 'label'),
+ certificate: getCertificateProp(
+ getters['certificateTypes'],
+ Name,
+ 'label',
+ ),
issuedBy: Issuer.CommonName,
issuedTo: Subject.CommonName,
validFrom: new Date(ValidNotBefore),
validUntil: new Date(ValidNotAfter),
};
});
- const availableUploadTypes = CERTIFICATE_TYPES.filter(
+ const availableUploadTypes = getters['certificateTypes'].filter(
({ type }) =>
!certificates
.map((certificate) => certificate.type)
@@ -95,15 +111,23 @@ const CertificatesStore = {
);
});
},
- async addNewCertificate({ dispatch }, { file, type }) {
+ async addNewCertificate({ dispatch, getters }, { file, type }) {
return await api
- .post(getCertificateProp(type, 'location'), file, {
- headers: { 'Content-Type': 'application/x-pem-file' },
- })
+ .post(
+ getCertificateProp(getters['certificateTypes'], type, 'location'),
+ file,
+ {
+ headers: { 'Content-Type': 'application/x-pem-file' },
+ },
+ )
.then(() => dispatch('getCertificates'))
.then(() =>
i18n.t('pageCertificates.toast.successAddCertificate', {
- certificate: getCertificateProp(type, 'label'),
+ certificate: getCertificateProp(
+ getters['certificateTypes'],
+ type,
+ 'label',
+ ),
}),
)
.catch((error) => {
@@ -112,7 +136,7 @@ const CertificatesStore = {
});
},
async replaceCertificate(
- { dispatch },
+ { dispatch, getters },
{ certificateString, location, type },
) {
const data = {};
@@ -128,7 +152,11 @@ const CertificatesStore = {
.then(() => dispatch('getCertificates'))
.then(() =>
i18n.t('pageCertificates.toast.successReplaceCertificate', {
- certificate: getCertificateProp(type, 'label'),
+ certificate: getCertificateProp(
+ getters['certificateTypes'],
+ type,
+ 'label',
+ ),
}),
)
.catch((error) => {
@@ -138,13 +166,17 @@ const CertificatesStore = {
);
});
},
- async deleteCertificate({ dispatch }, { type, location }) {
+ async deleteCertificate({ dispatch, getters }, { type, location }) {
return await api
.delete(location)
.then(() => dispatch('getCertificates'))
.then(() =>
i18n.t('pageCertificates.toast.successDeleteCertificate', {
- certificate: getCertificateProp(type, 'label'),
+ certificate: getCertificateProp(
+ getters['certificateTypes'],
+ type,
+ 'label',
+ ),
}),
)
.catch((error) => {
@@ -154,7 +186,7 @@ const CertificatesStore = {
);
});
},
- async generateCsr(_, userData) {
+ async generateCsr({ getters }, userData) {
const {
certificateType,
country,
@@ -173,7 +205,11 @@ const CertificatesStore = {
const data = {};
data.CertificateCollection = {
- '@odata.id': getCertificateProp(certificateType, 'location'),
+ '@odata.id': getCertificateProp(
+ getters['certificateTypes'],
+ certificateType,
+ 'location',
+ ),
};
data.Country = country;
data.State = state;
diff --git a/src/store/modules/SecurityAndAccess/PoliciesStore.js b/src/store/modules/SecurityAndAccess/PoliciesStore.js
index e6bcfb96..f1e98b27 100644
--- a/src/store/modules/SecurityAndAccess/PoliciesStore.js
+++ b/src/store/modules/SecurityAndAccess/PoliciesStore.js
@@ -31,7 +31,7 @@ const PoliciesStore = {
actions: {
async getNetworkProtocolStatus({ commit }) {
return await api
- .get('/redfish/v1/Managers/bmc/NetworkProtocol')
+ .get(`${await this.dispatch('global/getBmcPath')}/NetworkProtocol`)
.then((response) => {
const sshProtocol = response.data.SSH.ProtocolEnabled;
const ipmiProtocol = response.data.IPMI.ProtocolEnabled;
@@ -42,7 +42,7 @@ const PoliciesStore = {
},
async getBiosStatus({ commit }) {
return await api
- .get('/redfish/v1/Systems/system/Bios')
+ .get(`${await this.dispatch('global/getSystemPath')}/Bios`)
.then((response) => {
commit('setRtadEnabled', response.data.Attributes.pvm_rtad);
commit('setVtpmEnabled', response.data.Attributes.pvm_vtpm);
@@ -66,7 +66,10 @@ const PoliciesStore = {
},
};
return await api
- .patch('/redfish/v1/Managers/bmc/NetworkProtocol', ipmi)
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/NetworkProtocol`,
+ ipmi,
+ )
.then(() => {
if (protocolEnabled) {
return i18n.t('pagePolicies.toast.successIpmiEnabled');
@@ -92,7 +95,10 @@ const PoliciesStore = {
},
};
return await api
- .patch('/redfish/v1/Managers/bmc/NetworkProtocol', ssh)
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/NetworkProtocol`,
+ ssh,
+ )
.then(() => {
if (protocolEnabled) {
return i18n.t('pagePolicies.toast.successSshEnabled');
@@ -113,7 +119,7 @@ const PoliciesStore = {
async saveRtadState({ commit }, updatedRtad) {
commit('setRtadEnabled', updatedRtad);
return await api
- .patch('/redfish/v1/Systems/system/Bios/Settings', {
+ .patch(`${await this.dispatch('global/getSystemPath')}/Bios/Settings`, {
Attributes: {
pvm_rtad: updatedRtad,
},
@@ -137,7 +143,7 @@ const PoliciesStore = {
async saveVtpmState({ commit }, updatedVtpm) {
commit('setVtpmEnabled', updatedVtpm);
return await api
- .patch('/redfish/v1/Systems/system/Bios/Settings', {
+ .patch(`${await this.dispatch('global/getSystemPath')}/Bios/Settings`, {
Attributes: {
pvm_vtpm: updatedVtpm,
},
diff --git a/src/store/modules/Settings/DateTimeStore.js b/src/store/modules/Settings/DateTimeStore.js
index 51b722a8..9d804a7e 100644
--- a/src/store/modules/Settings/DateTimeStore.js
+++ b/src/store/modules/Settings/DateTimeStore.js
@@ -19,7 +19,7 @@ const DateTimeStore = {
actions: {
async getNtpData({ commit }) {
return await api
- .get('/redfish/v1/Managers/bmc/NetworkProtocol')
+ .get(`${await this.dispatch('global/getBmcPath')}/NetworkProtocol`)
.then((response) => {
const ntpServers = response.data.NTP.NTPServers;
const isNtpProtocolEnabled = response.data.NTP.ProtocolEnabled;
@@ -40,7 +40,10 @@ const DateTimeStore = {
ntpData.NTP.NTPServers = dateTimeForm.ntpServersArray;
}
return await api
- .patch(`/redfish/v1/Managers/bmc/NetworkProtocol`, ntpData)
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/NetworkProtocol`,
+ ntpData,
+ )
.then(async () => {
if (!dateTimeForm.ntpProtocolEnabled) {
const dateTimeData = {
@@ -58,9 +61,12 @@ const DateTimeStore = {
*/
const timeoutVal = state.isNtpProtocolEnabled ? 20000 : 0;
return await new Promise((resolve, reject) => {
- setTimeout(() => {
+ setTimeout(async () => {
return api
- .patch(`/redfish/v1/Managers/bmc`, dateTimeData)
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}`,
+ dateTimeData,
+ )
.then(() => resolve())
.catch(() => reject());
}, timeoutVal);
diff --git a/src/store/modules/Settings/NetworkStore.js b/src/store/modules/Settings/NetworkStore.js
index 9b016030..a249d22b 100644
--- a/src/store/modules/Settings/NetworkStore.js
+++ b/src/store/modules/Settings/NetworkStore.js
@@ -29,29 +29,47 @@ const NetworkStore = {
state.globalNetworkSettings = data.map(({ data }) => {
const {
DHCPv4,
+ DHCPv6,
HostName,
IPv4Addresses,
IPv4StaticAddresses,
+ IPv6Addresses,
+ IPv6StaticAddresses,
LinkStatus,
MACAddress,
+ IPv6DefaultGateway,
} = data;
return {
defaultGateway: IPv4StaticAddresses[0]?.Gateway, //First static gateway is the default gateway
+ ipv6DefaultGateway: IPv6DefaultGateway,
dhcpAddress: IPv4Addresses.filter(
(ipv4) => ipv4.AddressOrigin === 'DHCP',
),
+ dhcpv6Address: IPv6Addresses.filter(
+ (ipv6) =>
+ ipv6.AddressOrigin === 'SLAAC' || ipv6.AddressOrigin === 'DHCPv6',
+ ),
dhcpEnabled: DHCPv4.DHCPEnabled,
+ dhcp6Enabled: DHCPv6.OperatingMode,
hostname: HostName,
macAddress: MACAddress,
linkStatus: LinkStatus,
staticAddress: IPv4StaticAddresses[0]?.Address, // Display first static address on overview page
+ ipv6StaticAddress: IPv6StaticAddresses[0]?.Address,
useDnsEnabled: DHCPv4.UseDNSServers,
useDomainNameEnabled: DHCPv4.UseDomainName,
useNtpEnabled: DHCPv4.UseNTPServers,
+ useDnsEnabledIpv6: DHCPv6.UseDNSServers,
+ useDomainNameEnabledIpv6: DHCPv6.UseDomainName,
+ useNtpEnabledIpv6: DHCPv6.UseNTPServers,
};
});
},
setNtpState: (state, ntpState) => (state.ntpState = ntpState),
+ setDomainNameStateIpv6: (state, domainState) =>
+ (state.domainStateIpv6 = domainState),
+ setDnsStateIpv6: (state, dnsState) => (state.dnsStateIpv6 = dnsState),
+ setNtpStateIpv6: (state, ntpState) => (state.ntpStateIpv6 = ntpState),
setSelectedInterfaceId: (state, selectedInterfaceId) =>
(state.selectedInterfaceId = selectedInterfaceId),
setSelectedInterfaceIndex: (state, selectedInterfaceIndex) =>
@@ -60,7 +78,7 @@ const NetworkStore = {
actions: {
async getEthernetData({ commit }) {
return await api
- .get('/redfish/v1/Managers/bmc/EthernetInterfaces')
+ .get(`${await this.dispatch('global/getBmcPath')}/EthernetInterfaces`)
.then((response) =>
response.data.Members.map(
(ethernetInterface) => ethernetInterface['@odata.id'],
@@ -96,7 +114,7 @@ const NetworkStore = {
};
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
data,
)
.then(dispatch('getEthernetData'))
@@ -114,18 +132,54 @@ const NetworkStore = {
);
});
},
- async saveDomainNameState({ commit, state }, domainState) {
- commit('setDomainNameState', domainState);
+ async saveDhcp6EnabledState({ state, dispatch }, dhcpState) {
const data = {
- DHCPv4: {
- UseDomainName: domainState,
+ DHCPv6: {
+ OperatingMode: dhcpState ? 'Enabled' : 'Disabled',
},
};
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ data,
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dhcp6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.dhcp6'),
+ }),
+ );
+ });
+ },
+ async saveDomainNameState({ commit, state }, { domainState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setDomainNameState', domainState);
+ data = {
+ DHCPv4: {
+ UseDomainName: domainState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setDomainNameStateIpv6', domainState);
+ data = {
+ DHCPv6: {
+ UseDomainName: domainState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.firstInterfaceId}`,
data,
)
.then(() => {
@@ -135,7 +189,9 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setDomainNameState', !domainState);
+ if (ipVersion === 'IPv4') commit('setDomainNameState', !domainState);
+ else if (ipVersion === 'IPv6')
+ commit('setDomainNameStateIpv6', !domainState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.domainName'),
@@ -143,18 +199,28 @@ const NetworkStore = {
);
});
},
- async saveDnsState({ commit, state }, dnsState) {
- commit('setDnsState', dnsState);
- const data = {
- DHCPv4: {
- UseDNSServers: dnsState,
- },
- };
+ async saveDnsState({ commit, state }, { dnsState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setDnsState', dnsState);
+ data = {
+ DHCPv4: {
+ UseDNSServers: dnsState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setDnsStateIpv6', dnsState);
+ data = {
+ DHCPv6: {
+ UseDNSServers: dnsState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.firstInterfaceId}`,
data,
)
.then(() => {
@@ -164,7 +230,8 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setDnsState', !dnsState);
+ if (ipVersion === 'IPv4') commit('setDnsState', !dnsState);
+ else if (ipVersion === 'IPv6') commit('setDnsStateIpv6', !dnsState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.dns'),
@@ -172,18 +239,28 @@ const NetworkStore = {
);
});
},
- async saveNtpState({ commit, state }, ntpState) {
- commit('setNtpState', ntpState);
- const data = {
- DHCPv4: {
- UseNTPServers: ntpState,
- },
- };
+ async saveNtpState({ commit, state }, { ntpState, ipVersion }) {
+ var data;
+ if (ipVersion === 'IPv4') {
+ commit('setNtpState', ntpState);
+ data = {
+ DHCPv4: {
+ UseNTPServers: ntpState,
+ },
+ };
+ } else if (ipVersion === 'IPv6') {
+ commit('setNtpStateIpv6', ntpState);
+ data = {
+ DHCPv6: {
+ UseNTPServers: ntpState,
+ },
+ };
+ }
// Saving to the first interface automatically updates DHCPv4 and DHCPv6
// on all interfaces
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.firstInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.firstInterfaceId}`,
data,
)
.then(() => {
@@ -193,7 +270,8 @@ const NetworkStore = {
})
.catch((error) => {
console.log(error);
- commit('setNtpState', !ntpState);
+ if (ipVersion === 'IPv4') commit('setNtpState', !ntpState);
+ else if (ipVersion === 'IPv6') commit('setNtpStateIpv6', !ntpState);
throw new Error(
i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
setting: i18n.t('pageNetwork.ntp'),
@@ -221,7 +299,7 @@ const NetworkStore = {
const newAddress = [ipv4Form];
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
{ IPv4StaticAddresses: originalAddresses.concat(newAddress) },
)
.then(dispatch('getEthernetData'))
@@ -239,10 +317,41 @@ const NetworkStore = {
);
});
},
+ async saveIpv6Address({ dispatch, state }, ipv6Form) {
+ const originalAddresses = state.ethernetData[
+ state.selectedInterfaceIndex
+ ].IPv6StaticAddresses.map((ipv6) => {
+ const { Address, PrefixLength } = ipv6;
+ return {
+ Address,
+ PrefixLength,
+ };
+ });
+ const newAddress = [ipv6Form];
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ { IPv6StaticAddresses: originalAddresses.concat(newAddress) },
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ }),
+ );
+ });
+ },
async editIpv4Address({ dispatch, state }, ipv4TableData) {
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
{ IPv4StaticAddresses: ipv4TableData },
)
.then(dispatch('getEthernetData'))
@@ -260,10 +369,31 @@ const NetworkStore = {
);
});
},
+ async editIpv6Address({ dispatch, state }, ipv6TableData) {
+ return api
+ .patch(
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
+ { IPv6StaticAddresses: ipv6TableData },
+ )
+ .then(dispatch('getEthernetData'))
+ .then(() => {
+ return i18n.t('pageNetwork.toast.successSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ throw new Error(
+ i18n.t('pageNetwork.toast.errorSaveNetworkSettings', {
+ setting: i18n.t('pageNetwork.ipv6'),
+ }),
+ );
+ });
+ },
async saveSettings({ state, dispatch }, interfaceSettingsForm) {
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
interfaceSettingsForm,
)
.then(dispatch('getEthernetData'))
@@ -288,7 +418,7 @@ const NetworkStore = {
const newDnsArray = originalAddresses.concat(newAddress);
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
{ StaticNameServers: newDnsArray },
)
.then(dispatch('getEthernetData'))
@@ -309,7 +439,7 @@ const NetworkStore = {
async editDnsAddress({ dispatch, state }, dnsTableData) {
return api
.patch(
- `/redfish/v1/Managers/bmc/EthernetInterfaces/${state.selectedInterfaceId}`,
+ `${await this.dispatch('global/getBmcPath')}/EthernetInterfaces/${state.selectedInterfaceId}`,
{ StaticNameServers: dnsTableData },
)
.then(dispatch('getEthernetData'))
diff --git a/src/store/modules/Settings/PowerPolicyStore.js b/src/store/modules/Settings/PowerPolicyStore.js
index 3adaec8d..fc65381e 100644
--- a/src/store/modules/Settings/PowerPolicyStore.js
+++ b/src/store/modules/Settings/PowerPolicyStore.js
@@ -44,7 +44,7 @@ const PowerPolicyStore = {
},
async getPowerRestoreCurrentPolicy({ commit }) {
return await api
- .get('/redfish/v1/Systems/system')
+ .get(`${await this.dispatch('global/getSystemPath')}`)
.then(({ data: { PowerRestorePolicy } }) => {
commit('setPowerRestoreCurrentPolicy', PowerRestorePolicy);
})
@@ -54,7 +54,7 @@ const PowerPolicyStore = {
const data = { PowerRestorePolicy: powerPolicy };
return await api
- .patch('/redfish/v1/Systems/system', data)
+ .patch(`${await this.dispatch('global/getSystemPath')}`, data)
.then(() => {
dispatch('getPowerRestoreCurrentPolicy');
return i18n.t('pagePowerRestorePolicy.toast.successSaveSettings');
diff --git a/src/views/HardwareStatus/Inventory/InventoryTableFans.vue b/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
index 62f0b76b..af4b461e 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTableFans.vue
@@ -51,6 +51,12 @@
{{ value }}
</template>
+ <!-- StatusState -->
+ <template #cell(statusState)="{ value }">
+ <status-icon :status="statusStateIcon(value)" />
+ {{ value }}
+ </template>
+
<template #row-details="{ item }">
<b-container fluid>
<b-row>
@@ -146,6 +152,12 @@ export default {
tdClass: 'text-nowrap',
},
{
+ key: 'statusState',
+ label: this.$t('pageInventory.table.state'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
key: 'partNumber',
label: this.$t('pageInventory.table.partNumber'),
formatter: this.dataFormatter,
@@ -183,11 +195,40 @@ export default {
sortCompare(a, b, key) {
if (key === 'health') {
return this.sortStatus(a, b, key);
+ } else if (key === 'statusState') {
+ return this.sortStatusState(a, b, key);
}
},
onFiltered(filteredItems) {
this.searchTotalFilteredRows = filteredItems.length;
},
+ /**
+ * Returns the appropriate icon based on the given status.
+ *
+ * @param {string} status - The status to determine the icon for.
+ * @return {string} The icon corresponding to the given status.
+ */
+ statusStateIcon(status) {
+ switch (status) {
+ case 'Enabled':
+ return 'success';
+ case 'Absent':
+ return 'warning';
+ default:
+ return '';
+ }
+ },
+ /**
+ * Sorts the status state of two objects based on the provided key.
+ *
+ * @param {Object} a - The first object to compare.
+ * @param {Object} b - The second object to compare.
+ * @param {string} key - The key to use for comparison.
+ */
+ sortStatusState(a, b, key) {
+ const statusState = ['Enabled', 'Absent'];
+ return statusState.indexOf(a[key]) - statusState.indexOf(b[key]);
+ },
},
};
</script>
diff --git a/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue b/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
index df03fdf2..0ce8c823 100644
--- a/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
+++ b/src/views/HardwareStatus/Inventory/InventoryTablePowerSupplies.vue
@@ -51,6 +51,12 @@
{{ value }}
</template>
+ <!-- StatusState -->
+ <template #cell(statusState)="{ value }">
+ <status-icon :status="statusStateIcon(value)" />
+ {{ value }}
+ </template>
+
<template #row-details="{ item }">
<b-container fluid>
<b-row>
@@ -167,6 +173,12 @@ export default {
tdClass: 'text-nowrap',
},
{
+ key: 'statusState',
+ label: this.$t('pageInventory.table.state'),
+ formatter: this.dataFormatter,
+ tdClass: 'text-nowrap',
+ },
+ {
key: 'locationNumber',
label: this.$t('pageInventory.table.locationNumber'),
formatter: this.dataFormatter,
@@ -204,11 +216,38 @@ export default {
sortCompare(a, b, key) {
if (key === 'health') {
return this.sortStatus(a, b, key);
+ } else if (key === 'statusState') {
+ return this.sortStatusState(a, b, key);
}
},
onFiltered(filteredItems) {
this.searchTotalFilteredRows = filteredItems.length;
},
+ /**
+ * Returns the icon to use for status state based on the given status.
+ * @param {string} status The status to determine the icon for.
+ * @return {string} The icon for the given status.
+ */
+ statusStateIcon(status) {
+ switch (status) {
+ case 'Enabled':
+ return 'success';
+ case 'Absent':
+ return 'warning';
+ default:
+ return '';
+ }
+ },
+ /**
+ * Sorts the status state of two objects based on the provided key.
+ * @param {Object} a The first object to compare.
+ * @param {Object} b The second object to compare.
+ * @param {string} key The key to use for comparison.
+ */
+ sortStatusState(a, b, key) {
+ const statusState = ['Enabled', 'Absent'];
+ return statusState.indexOf(a[key]) - statusState.indexOf(b[key]);
+ },
},
};
</script>
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
index db475c56..5e1e6ddd 100644
--- a/src/views/Login/Login.vue
+++ b/src/views/Login/Login.vue
@@ -117,14 +117,11 @@ export default {
const password = this.userInfo.password;
this.$store
.dispatch('authentication/login', { username, password })
- .then(() => {
+ .then((PasswordChangeRequired) => {
localStorage.setItem('storedLanguage', i18n.locale);
localStorage.setItem('storedUsername', username);
this.$store.commit('global/setUsername', username);
this.$store.commit('global/setLanguagePreference', i18n.locale);
- return this.$store.dispatch('authentication/getUserInfo', username);
- })
- .then(({ PasswordChangeRequired }) => {
if (PasswordChangeRequired) {
this.$router.push('/change-password');
} else {
diff --git a/src/views/Logs/Dumps/Dumps.vue b/src/views/Logs/Dumps/Dumps.vue
index 75873a8c..e89acd93 100644
--- a/src/views/Logs/Dumps/Dumps.vue
+++ b/src/views/Logs/Dumps/Dumps.vue
@@ -306,7 +306,7 @@ export default {
},
created() {
this.startLoader();
- this.$store.dispatch('dumps/getBmcDumpEntries').finally(() => {
+ this.$store.dispatch('dumps/getAllDumps').finally(() => {
this.endLoader();
this.isBusy = false;
});
diff --git a/src/views/Logs/EventLogs/EventLogs.vue b/src/views/Logs/EventLogs/EventLogs.vue
index 0e7c494e..b48bd441 100644
--- a/src/views/Logs/EventLogs/EventLogs.vue
+++ b/src/views/Logs/EventLogs/EventLogs.vue
@@ -151,11 +151,7 @@
</dl>
</b-col>
<b-col class="text-nowrap">
- <b-button
- class="btn btn-secondary float-right"
- :href="item.additionalDataUri"
- target="_blank"
- >
+ <b-button @click="downloadEntry(item.additionalDataUri)">
<icon-download />{{ $t('pageEventLogs.additionalDataUri') }}
</b-button>
</b-col>
@@ -471,6 +467,20 @@ export default {
});
},
methods: {
+ downloadEntry(uri) {
+ let filename = uri?.split('LogServices/')?.[1];
+ filename.replace(RegExp('/', 'g'), '_');
+ this.$store
+ .dispatch('eventLog/downloadEntry', uri)
+ .then((blob) => {
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = filename;
+ link.click();
+ URL.revokeObjectURL(link.href);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
changelogStatus(row) {
this.$store
.dispatch('eventLog/updateEventLogStatus', {
diff --git a/src/views/Operations/Firmware/FirmwareFormUpdate.vue b/src/views/Operations/Firmware/FirmwareFormUpdate.vue
index ac4b23fc..28d1104d 100644
--- a/src/views/Operations/Firmware/FirmwareFormUpdate.vue
+++ b/src/views/Operations/Firmware/FirmwareFormUpdate.vue
@@ -2,21 +2,8 @@
<div>
<div class="form-background p-3">
<b-form @submit.prevent="onSubmitUpload">
- <b-form-group
- v-if="isTftpUploadAvailable"
- :label="$t('pageFirmware.form.updateFirmware.fileSource')"
- :disabled="isPageDisabled"
- >
- <b-form-radio v-model="isWorkstationSelected" :value="true">
- {{ $t('pageFirmware.form.updateFirmware.workstation') }}
- </b-form-radio>
- <b-form-radio v-model="isWorkstationSelected" :value="false">
- {{ $t('pageFirmware.form.updateFirmware.tftpServer') }}
- </b-form-radio>
- </b-form-group>
-
<!-- Workstation Upload -->
- <template v-if="isWorkstationSelected">
+ <template>
<b-form-group
:label="$t('pageFirmware.form.updateFirmware.imageFile')"
label-for="image-file"
@@ -37,25 +24,6 @@
</b-form-group>
</template>
- <!-- TFTP Server Upload -->
- <template v-else>
- <b-form-group
- :label="$t('pageFirmware.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"
@@ -73,7 +41,7 @@
</template>
<script>
-import { requiredIf } from 'vuelidate/lib/validators';
+import { required } from 'vuelidate/lib/validators';
import BVToastMixin from '@/components/Mixins/BVToastMixin';
import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
@@ -99,36 +67,15 @@ export default {
data() {
return {
loading,
- isWorkstationSelected: true,
file: null,
- tftpFileAddress: null,
isServerPowerOffRequired:
process.env.VUE_APP_SERVER_OFF_REQUIRED === 'true',
};
},
- computed: {
- isTftpUploadAvailable() {
- return this.$store.getters['firmware/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;
- }),
+ required,
},
};
},
@@ -149,24 +96,13 @@ export default {
title: this.$t('pageFirmware.toast.updateStarted'),
timestamp: true,
});
- if (this.isWorkstationSelected) {
- this.dispatchWorkstationUpload(timerId);
- } else {
- this.dispatchTftpUpload(timerId);
- }
+ this.dispatchWorkstationUpload(timerId);
},
dispatchWorkstationUpload(timerId) {
this.$store
- .dispatch('firmware/uploadFirmware', this.file)
- .catch(({ message }) => {
- this.endLoader();
- this.errorToast(message);
- clearTimeout(timerId);
- });
- },
- dispatchTftpUpload(timerId) {
- this.$store
- .dispatch('firmware/uploadFirmwareTFTP', this.tftpFileAddress)
+ .dispatch('firmware/uploadFirmware', {
+ image: this.file,
+ })
.catch(({ message }) => {
this.endLoader();
this.errorToast(message);
diff --git a/src/views/Overview/OverviewDumps.vue b/src/views/Overview/OverviewDumps.vue
index a2ae4e4e..6db5d287 100644
--- a/src/views/Overview/OverviewDumps.vue
+++ b/src/views/Overview/OverviewDumps.vue
@@ -34,7 +34,7 @@ export default {
},
},
created() {
- this.$store.dispatch('dumps/getBmcDumpEntries').finally(() => {
+ this.$store.dispatch('dumps/getAllDumps').finally(() => {
this.$root.$emit('overview-dumps-complete');
});
},
diff --git a/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue b/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
index d9d65912..9f60d2ba 100644
--- a/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
+++ b/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue
@@ -366,7 +366,6 @@ import IconCheckmark from '@carbon/icons-vue/es/checkmark/20';
import { required, requiredIf } from 'vuelidate/lib/validators';
import { COUNTRY_LIST } from './CsrCountryCodes';
-import { CERTIFICATE_TYPES } from '@/store/modules/SecurityAndAccess/CertificatesStore';
import BVToastMixin from '@/components/Mixins/BVToastMixin';
import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
@@ -391,14 +390,6 @@ export default {
keyCurveId: null,
keyBitLength: null,
},
- certificateOptions: CERTIFICATE_TYPES.reduce((arr, cert) => {
- if (cert.type === 'TrustStore Certificate') return arr;
- arr.push({
- text: cert.label,
- value: cert.type,
- });
- return arr;
- }, []),
countryOptions: COUNTRY_LIST.map((country) => ({
text: country.label,
value: country.code,
@@ -410,6 +401,21 @@ export default {
csrStringCopied: false,
};
},
+ computed: {
+ certificateTypes() {
+ return this.$store.getters['certificates/certificateTypes'];
+ },
+ certificateOptions() {
+ return this.certificateTypes.reduce((arr, cert) => {
+ if (cert.type === 'TrustStore Certificate') return arr;
+ arr.push({
+ text: cert.label,
+ value: cert.type,
+ });
+ return arr;
+ }, []);
+ },
+ },
validations: {
form: {
certificateType: { required },
diff --git a/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue b/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
index 6ea2561a..beacf575 100644
--- a/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
+++ b/src/views/SecurityAndAccess/Ldap/ModalAddRoleGroup.vue
@@ -16,7 +16,7 @@
<template v-if="roleGroup !== null">
<dl class="mb-4">
<dt>{{ $t('pageLdap.modal.groupName') }}</dt>
- <dd>{{ form.groupName }}</dd>
+ <dd style="word-break: break-all">{{ form.groupName }}</dd>
</dl>
</template>
diff --git a/src/views/SecurityAndAccess/UserManagement/UserManagement.vue b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
index ab316688..944ea258 100644
--- a/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
+++ b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
@@ -361,16 +361,39 @@ export default {
.finally(() => this.endLoader());
break;
case 'disable':
- this.startLoader();
- this.$store
- .dispatch('userManagement/disableUsers', this.selectedRows)
- .then((messages) => {
- messages.forEach(({ type, message }) => {
- if (type === 'success') this.successToast(message);
- if (type === 'error') this.errorToast(message);
- });
- })
- .finally(() => this.endLoader());
+ this.$bvModal
+ .msgBoxConfirm(
+ this.$tc(
+ 'pageUserManagement.modal.batchDisableConfirmMessage',
+ this.selectedRows.length,
+ ),
+ {
+ title: this.$tc(
+ 'pageUserManagement.disableUser',
+ this.selectedRows.length,
+ ),
+ okTitle: this.$tc(
+ 'pageUserManagement.disableUser',
+ this.selectedRows.length,
+ ),
+ cancelTitle: this.$t('global.action.cancel'),
+ autoFocusButton: 'ok',
+ },
+ )
+ .then((disableConfirmed) => {
+ if (disableConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/disableUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ }
+ });
break;
}
},
diff --git a/src/views/Settings/Network/ModalDefaultGateway.vue b/src/views/Settings/Network/ModalDefaultGateway.vue
new file mode 100644
index 00000000..48c05c1d
--- /dev/null
+++ b/src/views/Settings/Network/ModalDefaultGateway.vue
@@ -0,0 +1,114 @@
+<template>
+ <b-modal
+ id="modal-default-gateway"
+ ref="modal"
+ :title="$t('pageNetwork.modal.editIPv6DefaultGatewayTitle')"
+ @hidden="resetForm"
+ >
+ <b-form id="gateway-settings" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.gateway')"
+ label-for="defaultGateway"
+ >
+ <b-form-input
+ id="defaultGateway"
+ v-model.trim="form.defaultGateway"
+ data-test-id="network-input-gateway"
+ type="text"
+ :state="getValidationState($v.form.defaultGateway)"
+ @change="$v.form.defaultGateway.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <div v-if="!$v.form.defaultGateway.required">
+ {{ $t('global.form.fieldRequired') }}
+ </div>
+ <div v-if="!$v.form.defaultGateway.validateGateway">
+ {{ $t('global.form.invalidFormat') }}
+ </div>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button
+ form="gateway-settings"
+ type="submit"
+ variant="primary"
+ @click="onOk"
+ >
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+import { required, helpers } from 'vuelidate/lib/validators';
+
+const validateGateway = helpers.regex(
+ 'validateGateway',
+ /^((?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}|:(?::[a-fA-F0-9]{1,4}){1,7}|::|(?:[a-fA-F0-9]{1,4}:){6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[a-fA-F0-9]{1,4}:){0,5}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,5}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,2}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|[a-fA-F0-9]{1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[0-9]{1,3}\.){3}[0-9]{1,3})$/,
+);
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ defaultGateway: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ form: {
+ defaultGateway: '',
+ },
+ };
+ },
+ watch: {
+ defaultGateway() {
+ this.form.defaultGateway = this.defaultGateway;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ defaultGateway: {
+ required,
+ validateGateway,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', { IPv6DefaultGateway: this.form.defaultGateway });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.defaultGateway = this.defaultGateway;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/ModalIpv6.vue b/src/views/Settings/Network/ModalIpv6.vue
new file mode 100644
index 00000000..f707a774
--- /dev/null
+++ b/src/views/Settings/Network/ModalIpv6.vue
@@ -0,0 +1,133 @@
+<template>
+ <b-modal
+ id="modal-add-ipv6"
+ ref="modal"
+ :title="$t('pageNetwork.table.addIpv6Address')"
+ @hidden="resetForm"
+ >
+ <b-form id="form-ipv6" @submit.prevent="handleSubmit">
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.ipAddress')"
+ label-for="ipAddress"
+ >
+ <b-form-input
+ id="ipAddress"
+ v-model="form.ipAddress"
+ type="text"
+ :state="getValidationState($v.form.ipAddress)"
+ @input="$v.form.ipAddress.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.ipAddress.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.ipAddress.validateIpv6">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ <b-col sm="6">
+ <b-form-group
+ :label="$t('pageNetwork.modal.prefixLength')"
+ label-for="prefixLength"
+ >
+ <b-form-input
+ id="prefixLength"
+ v-model="form.prefixLength"
+ type="text"
+ :state="getValidationState($v.form.prefixLength)"
+ @input="$v.form.prefixLength.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.prefixLength.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-if="!$v.form.prefixLength.validatePrefixLength">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button variant="secondary" @click="cancel()">
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button form="form-ipv6" type="submit" variant="primary" @click="onOk">
+ {{ $t('global.action.add') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+import { required, helpers } from 'vuelidate/lib/validators';
+
+const validateIpv6 = helpers.regex(
+ 'validateIpv6',
+ /^((?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:|(?:[a-fA-F0-9]{1,4}:){1,6}:[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,5}(?::[a-fA-F0-9]{1,4}){1,2}|(?:[a-fA-F0-9]{1,4}:){1,4}(?::[a-fA-F0-9]{1,4}){1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}(?::[a-fA-F0-9]{1,4}){1,4}|(?:[a-fA-F0-9]{1,4}:){1,2}(?::[a-fA-F0-9]{1,4}){1,5}|[a-fA-F0-9]{1,4}:(?::[a-fA-F0-9]{1,4}){1,6}|:(?::[a-fA-F0-9]{1,4}){1,7}|::|(?:[a-fA-F0-9]{1,4}:){6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[a-fA-F0-9]{1,4}:){0,5}(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,5}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,3}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|(?:[a-fA-F0-9]{1,4}:){1,2}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|[a-fA-F0-9]{1,4}:(?:[0-9]{1,3}\.){3}[0-9]{1,3}|::(?:[0-9]{1,3}\.){3}[0-9]{1,3})$/,
+);
+
+const validatePrefixLength = helpers.regex(
+ 'validatePrefixLength',
+ /^(12[0-8]|1[0-9]|[1-9][0-9]|[0-9])$/,
+);
+
+export default {
+ mixins: [VuelidateMixin],
+ data() {
+ return {
+ form: {
+ ipAddress: '',
+ prefixLength: '',
+ },
+ };
+ },
+ validations() {
+ return {
+ form: {
+ ipAddress: {
+ required,
+ validateIpv6,
+ },
+ prefixLength: {
+ required,
+ validatePrefixLength,
+ },
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ this.$emit('ok', {
+ Address: this.form.ipAddress,
+ PrefixLength: parseInt(this.form.prefixLength),
+ });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.ipAddress = null;
+ this.form.prefixLength = null;
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/Settings/Network/Network.vue b/src/views/Settings/Network/Network.vue
index f731c25f..0279cbe6 100644
--- a/src/views/Settings/Network/Network.vue
+++ b/src/views/Settings/Network/Network.vue
@@ -23,6 +23,8 @@
<network-interface-settings :tab-index="tabIndex" />
<!-- IPV4 table -->
<table-ipv-4 :tab-index="tabIndex" />
+ <!-- IPV6 table -->
+ <table-ipv-6 :tab-index="tabIndex" />
<!-- Static DNS table -->
<table-dns :tab-index="tabIndex" />
</b-tab>
@@ -33,9 +35,14 @@
</page-section>
<!-- Modals -->
<modal-ipv4 :default-gateway="defaultGateway" @ok="saveIpv4Address" />
+ <modal-ipv6 @ok="saveIpv6Address" />
<modal-dns @ok="saveDnsAddress" />
<modal-hostname :hostname="currentHostname" @ok="saveSettings" />
<modal-mac-address :mac-address="currentMacAddress" @ok="saveSettings" />
+ <modal-default-gateway
+ :default-gateway="ipv6DefaultGateway"
+ @ok="saveSettings"
+ />
</b-container>
</template>
@@ -44,14 +51,17 @@ import BVToastMixin from '@/components/Mixins/BVToastMixin';
import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
import LoadingBarMixin, { loading } from '@/components/Mixins/LoadingBarMixin';
import ModalMacAddress from './ModalMacAddress.vue';
+import ModalDefaultGateway from './ModalDefaultGateway.vue';
import ModalHostname from './ModalHostname.vue';
import ModalIpv4 from './ModalIpv4.vue';
+import ModalIpv6 from './ModalIpv6.vue';
import ModalDns from './ModalDns.vue';
import NetworkGlobalSettings from './NetworkGlobalSettings.vue';
import NetworkInterfaceSettings from './NetworkInterfaceSettings.vue';
import PageSection from '@/components/Global/PageSection';
import PageTitle from '@/components/Global/PageTitle';
import TableIpv4 from './TableIpv4.vue';
+import TableIpv6 from './TableIpv6.vue';
import TableDns from './TableDns.vue';
import { mapState } from 'vuex';
@@ -60,7 +70,9 @@ export default {
components: {
ModalHostname,
ModalMacAddress,
+ ModalDefaultGateway,
ModalIpv4,
+ ModalIpv6,
ModalDns,
NetworkGlobalSettings,
NetworkInterfaceSettings,
@@ -68,6 +80,7 @@ export default {
PageTitle,
TableDns,
TableIpv4,
+ TableIpv6,
},
mixins: [BVToastMixin, DataFormatterMixin, LoadingBarMixin],
beforeRouteLeave(to, from, next) {
@@ -79,6 +92,7 @@ export default {
currentHostname: '',
currentMacAddress: '',
defaultGateway: '',
+ ipv6DefaultGateway: '',
loading,
tabIndex: 0,
};
@@ -105,6 +119,9 @@ export default {
const networkTableIpv4 = new Promise((resolve) => {
this.$root.$on('network-table-ipv4-complete', () => resolve());
});
+ const networkTableIpv6 = new Promise((resolve) => {
+ this.$root.$on('network-table-ipv6-complete', () => resolve());
+ });
// Combine all child component Promises to indicate
// when page data load complete
Promise.all([
@@ -113,6 +130,7 @@ export default {
interfaceSettings,
networkTableDns,
networkTableIpv4,
+ networkTableIpv6,
]).finally(() => this.endLoader());
},
methods: {
@@ -131,6 +149,10 @@ export default {
this.$store.getters['network/globalNetworkSettings'][
this.tabIndex
].macAddress;
+ this.ipv6DefaultGateway =
+ this.$store.getters['network/globalNetworkSettings'][
+ this.tabIndex
+ ].ipv6DefaultGateway;
},
getTabIndex(selectedIndex) {
this.tabIndex = selectedIndex;
@@ -149,6 +171,14 @@ export default {
.catch(({ message }) => this.errorToast(message))
.finally(() => this.endLoader());
},
+ saveIpv6Address(modalFormData) {
+ this.startLoader();
+ this.$store
+ .dispatch('network/saveIpv6Address', modalFormData)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
saveDnsAddress(modalFormData) {
this.startLoader();
this.$store
diff --git a/src/views/Settings/Network/NetworkGlobalSettings.vue b/src/views/Settings/Network/NetworkGlobalSettings.vue
index 30287673..0c062ea2 100644
--- a/src/views/Settings/Network/NetworkGlobalSettings.vue
+++ b/src/views/Settings/Network/NetworkGlobalSettings.vue
@@ -4,7 +4,7 @@
:section-title="$t('pageNetwork.networkSettings')"
>
<b-row>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>
{{ $t('pageNetwork.hostname') }}
@@ -12,10 +12,19 @@
<icon-edit :title="$t('pageNetwork.modal.editHostnameTitle')" />
</b-button>
</dt>
- <dd>{{ dataFormatter(firstInterface.hostname) }}</dd>
+ <dd style="word-break: break-all">
+ {{ dataFormatter(firstInterface.hostname) }}
+ </dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
+ <dl>
+ <dt>{{ $t('pageNetwork.ipVersion') }}</dt>
+ <dd>{{ $t('pageNetwork.ipv4') }}</dd>
+ <dd>{{ $t('pageNetwork.ipv6') }}</dd>
+ </dl>
+ </b-col>
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useDomainName') }}</dt>
<dd>
@@ -32,9 +41,23 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useDomainNameSwitchIpv6"
+ v-model="useDomainNameStateIpv6"
+ data-test-id="networkSettings-switch-useDomainNameIpv6"
+ switch
+ @change="changeDomainNameStateIpv6"
+ >
+ <span v-if="useDomainNameStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useDns') }}</dt>
<dd>
@@ -51,9 +74,23 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useDnsSwitchIpv6"
+ v-model="useDnsStateIpv6"
+ data-test-id="networkSettings-switch-useDnsIpv6"
+ switch
+ @change="changeDnsStateIpv6"
+ >
+ <span v-if="useDnsStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
- <b-col md="3">
+ <b-col md="2">
<dl>
<dt>{{ $t('pageNetwork.useNtp') }}</dt>
<dd>
@@ -70,6 +107,20 @@
<span v-else>{{ $t('global.status.disabled') }}</span>
</b-form-checkbox>
</dd>
+ <dd>
+ <b-form-checkbox
+ id="useNtpSwitchIpv6"
+ v-model="useNtpStateIpv6"
+ data-test-id="networkSettings-switch-useNtpIpv6"
+ switch
+ @change="changeNtpStateIpv6"
+ >
+ <span v-if="useNtpStateIpv6">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
</dl>
</b-col>
</b-row>
@@ -125,6 +176,33 @@ export default {
return newValue;
},
},
+ useDomainNameStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDomainNameEnabledIpv6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useDnsStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useDnsEnabledIpv6v6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ useNtpStateIpv6: {
+ get() {
+ return this.$store.getters['network/globalNetworkSettings'][0]
+ .useNtpEnabledIpv6;
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
},
created() {
this.$store.dispatch('network/getEthernetData').finally(() => {
@@ -135,7 +213,10 @@ export default {
methods: {
changeDomainNameState(state) {
this.$store
- .dispatch('network/saveDomainNameState', state)
+ .dispatch('network/saveDomainNameState', {
+ domainState: state,
+ ipVersion: 'IPv4',
+ })
.then((success) => {
this.successToast(success);
})
@@ -143,14 +224,57 @@ export default {
},
changeDnsState(state) {
this.$store
- .dispatch('network/saveDnsState', state)
- .then((message) => this.successToast(message))
+ .dispatch('network/saveDnsState', {
+ dnsState: state,
+ ipVersion: 'IPv4',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
.catch(({ message }) => this.errorToast(message));
},
changeNtpState(state) {
this.$store
- .dispatch('network/saveNtpState', state)
- .then((message) => this.successToast(message))
+ .dispatch('network/saveNtpState', {
+ ntpState: state,
+ ipVersion: 'IPv4',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDomainNameStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveDomainNameState', {
+ domainState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((success) => {
+ this.successToast(success);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeDnsStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveDnsState', {
+ dnsState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ changeNtpStateIpv6(state) {
+ this.$store
+ .dispatch('network/saveNtpState', {
+ ntpState: state,
+ ipVersion: 'IPv6',
+ })
+ .then((message) => {
+ this.successToast(message);
+ })
.catch(({ message }) => this.errorToast(message));
},
initSettingsModal() {
diff --git a/src/views/Settings/Network/TableIpv6.vue b/src/views/Settings/Network/TableIpv6.vue
new file mode 100644
index 00000000..5a16e9dc
--- /dev/null
+++ b/src/views/Settings/Network/TableIpv6.vue
@@ -0,0 +1,289 @@
+<template>
+ <page-section :section-title="$t('pageNetwork.ipv6')">
+ <b-row class="mb-4">
+ <b-col lg="2" md="6">
+ <dl>
+ <dt>{{ $t('pageNetwork.dhcp6') }}</dt>
+ <dd>
+ <b-form-checkbox
+ id="dhcp6Switch"
+ v-model="dhcp6EnabledState"
+ data-test-id="networkSettings-switch-dhcp6Enabled"
+ switch
+ @change="changeDhcp6EnabledState"
+ >
+ <span v-if="dhcp6EnabledState">
+ {{ $t('global.status.enabled') }}
+ </span>
+ <span v-else>{{ $t('global.status.disabled') }}</span>
+ </b-form-checkbox>
+ </dd>
+ </dl>
+ </b-col>
+ <b-col lg="2" md="6">
+ <dl class="text-nowrap">
+ <dt>
+ {{ $t('pageNetwork.ipv6DefaultGateway') }}
+ <b-button
+ v-if="defaultGatewayEditable"
+ variant="link"
+ class="p-1"
+ @click="initDefaultGatewayModal()"
+ >
+ <icon-edit
+ :title="$t('pageNetwork.modal.editIPv6DefaultGatewayTitle')"
+ />
+ </b-button>
+ </dt>
+ <dd>
+ {{ dataFormatter(defaultGateway) }}
+ </dd>
+ </dl>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col>
+ <h3 class="h5">
+ {{ $t('pageNetwork.ipv6Addresses') }}
+ </h3>
+ </b-col>
+ <b-col class="text-right">
+ <b-button variant="primary" @click="initAddIpv6Address()">
+ <icon-add />
+ {{ $t('pageNetwork.table.addIpv6Address') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-table
+ responsive="md"
+ hover
+ :fields="ipv6TableFields"
+ :items="form.ipv6TableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ class="mb-0"
+ show-empty
+ >
+ <template #cell(actions)="{ item, index }">
+ <table-row-action
+ v-for="(action, actionIndex) in filteredActions(item)"
+ :key="actionIndex"
+ :value="action.value"
+ :title="action.title"
+ :enabled="action.enabled"
+ @click-table-action="onIpv6TableAction(action, $event, index)"
+ >
+ <template #icon>
+ <icon-edit v-if="action.value === 'edit'" />
+ <icon-trashcan v-if="action.value === 'delete'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </page-section>
+</template>
+
+<script>
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import IconAdd from '@carbon/icons-vue/es/add--alt/20';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import PageSection from '@/components/Global/PageSection';
+import TableRowAction from '@/components/Global/TableRowAction';
+import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'Ipv6Table',
+ components: {
+ IconAdd,
+ IconEdit,
+ IconTrashcan,
+ PageSection,
+ TableRowAction,
+ },
+ mixins: [BVToastMixin, LoadingBarMixin, DataFormatterMixin],
+ props: {
+ tabIndex: {
+ type: Number,
+ default: 0,
+ },
+ },
+ data() {
+ return {
+ form: {
+ ipv6TableItems: [],
+ },
+ actions: [
+ {
+ value: 'edit',
+ title: this.$t('global.action.edit'),
+ },
+ {
+ value: 'delete',
+ title: this.$t('global.action.delete'),
+ },
+ ],
+ ipv6TableFields: [
+ {
+ key: 'Address',
+ label: this.$t('pageNetwork.table.ipAddress'),
+ },
+ {
+ key: 'PrefixLength',
+ label: this.$t('pageNetwork.table.prefixLength'),
+ },
+ {
+ key: 'AddressOrigin',
+ label: this.$t('pageNetwork.table.addressOrigin'),
+ },
+ { key: 'actions', label: '', tdClass: 'text-right' },
+ ],
+ defaultGateway: '',
+ defaultGatewayEditable:
+ process.env.VUE_APP_ENV_NAME !== 'nvidia-bluefield',
+ };
+ },
+ computed: {
+ ...mapState('network', ['ethernetData']),
+ selectedInterface() {
+ return this.$store.getters['network/selectedInterfaceIndex'];
+ },
+ dhcp6EnabledState: {
+ get() {
+ return (
+ this.$store.getters['network/globalNetworkSettings'][
+ this.selectedInterface
+ ].dhcp6Enabled === 'Enabled'
+ );
+ },
+ set(newValue) {
+ return newValue;
+ },
+ },
+ filteredActions() {
+ return (item) => {
+ if (item.AddressOrigin === 'DHCPv6' || item.AddressOrigin === 'SLAAC') {
+ return item.actions.filter((action) => action.value !== 'delete');
+ } else {
+ return item.actions;
+ }
+ };
+ },
+ },
+ watch: {
+ // Watch for change in tab index
+ tabIndex() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ },
+ ethernetData() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ },
+ },
+ created() {
+ this.getIpv6TableItems();
+ this.getDefaultGateway();
+ this.$store.dispatch('network/getEthernetData').finally(() => {
+ // Emit initial data fetch complete to parent component
+ this.$root.$emit('network-table-ipv6-complete');
+ });
+ },
+ methods: {
+ getDefaultGateway() {
+ this.defaultGateway = this.ethernetData[this.tabIndex].IPv6DefaultGateway;
+ },
+ getIpv6TableItems() {
+ const index = this.tabIndex;
+ const addresses =
+ this.ethernetData[index].IPv6Addresses.filter(
+ (ipv6) =>
+ ipv6.AddressOrigin === 'LinkLocal' ||
+ ipv6.AddressOrigin === 'Static' ||
+ ipv6.AddressOrigin === 'SLAAC' ||
+ ipv6.AddressOrigin === 'DHCPv6',
+ ) || [];
+ this.form.ipv6TableItems = addresses.map((ipv6) => {
+ return {
+ Address: ipv6.Address,
+ PrefixLength: ipv6.PrefixLength,
+ AddressOrigin: ipv6.AddressOrigin,
+ actions: [
+ {
+ value: 'delete',
+ title: this.$t('pageNetwork.table.deleteIpv6'),
+ },
+ ],
+ };
+ });
+ },
+ onIpv6TableAction(action, $event, index) {
+ if ($event === 'delete') {
+ this.deleteIpv6TableRow(index);
+ }
+ },
+ deleteIpv6TableRow(index) {
+ const AddressOrigin = this.form.ipv6TableItems[index].AddressOrigin;
+ this.form.ipv6TableItems.splice(index, 1);
+ const newIpv6Array = this.form.ipv6TableItems.map((ipv6) => {
+ const { Address, PrefixLength } = ipv6;
+ return {
+ Address,
+ PrefixLength,
+ };
+ });
+ if (
+ newIpv6Array.length == 0 &&
+ (AddressOrigin === 'Static' || AddressOrigin === 'LinkLocal')
+ ) {
+ this.$store
+ .dispatch('network/saveDhcp6EnabledState', true)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
+ this.$store
+ .dispatch('network/editIpv6Address', newIpv6Array)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ },
+ initAddIpv6Address() {
+ this.$bvModal.show('modal-add-ipv6');
+ },
+ changeDhcp6EnabledState(state) {
+ this.$bvModal
+ .msgBoxConfirm(
+ state
+ ? this.$t('pageNetwork.modal.confirmEnableDhcp')
+ : this.$t('pageNetwork.modal.confirmDisableDhcp'),
+ {
+ title: this.$t('pageNetwork.modal.dhcpConfirmTitle', {
+ dhcpState: state
+ ? this.$t('global.action.enable')
+ : this.$t('global.action.disable'),
+ }),
+ okTitle: state
+ ? this.$t('global.action.enable')
+ : this.$t('global.action.disable'),
+ okVariant: 'danger',
+ cancelTitle: this.$t('global.action.cancel'),
+ },
+ )
+ .then((dhcpEnableConfirmed) => {
+ if (dhcpEnableConfirmed) {
+ this.$store
+ .dispatch('network/saveDhcp6EnabledState', state)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ let onDhcpCancel = document.getElementById('dhcp6Switch');
+ onDhcpCancel.checked = !state;
+ }
+ });
+ },
+ initDefaultGatewayModal() {
+ this.$bvModal.show('modal-default-gateway');
+ },
+ },
+};
+</script>