Skip to content

Commit

Permalink
Add SNMP alerts page and test hooks (#8)
Browse files Browse the repository at this point in the history
* Add SNMP alerts page and test hooks

This page will be included in the Settings section of the primary
navigation. The user will be able to delete and add alert destination.

Signed-off-by: Sandeepa Singh <[email protected]>
  • Loading branch information
Sandeepa Singh authored and rfrandse committed Jun 30, 2022
1 parent e05a88e commit d45ebe1
Show file tree
Hide file tree
Showing 10 changed files with 594 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/components/AppNavigation/AppNavigationMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ const AppNavigationMixin = {
label: this.$t('appNavigation.powerRestorePolicy'),
route: '/settings/power-restore-policy',
},
{
id: 'snmp-alerts',
label: this.$t('appNavigation.snmpAlerts'),
route: '/settings/snmp-alerts',
},
],
},
{
Expand Down
5 changes: 5 additions & 0 deletions src/env/components/AppNavigation/ibm.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ const AppNavigationMixin = {
label: this.$t('appNavigation.powerRestorePolicy'),
route: '/settings/power-restore-policy',
},
{
id: 'snmp-alerts',
label: this.$t('appNavigation.snmpAlerts'),
route: '/settings/snmp-alerts',
},
],
},
{
Expand Down
9 changes: 9 additions & 0 deletions src/env/router/ibm.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import SerialOverLanConsole from '@/views/Operations/SerialOverLan/SerialOverLan
import ServerPowerOperations from '@/views/Operations/ServerPowerOperations';
import Certificates from '@/views/SecurityAndAccess/Certificates';
import Power from '@/views/ResourceManagement/Power';
import SnmpAlerts from '@/views/Settings/SnmpAlerts';
import i18n from '@/i18n';

// Custom components
Expand Down Expand Up @@ -207,6 +208,14 @@ const routes = [
title: i18n.t('appPageTitle.powerRestorePolicy'),
},
},
{
path: '/settings/snmp-alerts',
name: 'snmp-alerts',
component: SnmpAlerts,
meta: {
title: i18n.t('appPageTitle.snmpAlerts'),
},
},
{
path: '/resource-management/power',
name: 'power',
Expand Down
9 changes: 9 additions & 0 deletions src/router/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import ServerPowerOperations from '@/views/Operations/ServerPowerOperations';
import Certificates from '@/views/SecurityAndAccess/Certificates';
import VirtualMedia from '@/views/Operations/VirtualMedia';
import Power from '@/views/ResourceManagement/Power';
import SnmpAlerts from '@/views/Settings/SnmpAlerts';
import i18n from '@/i18n';

const routes = [
Expand Down Expand Up @@ -183,6 +184,14 @@ const routes = [
title: i18n.t('appPageTitle.dateTime'),
},
},
{
path: '/settings/snmp-alerts',
name: 'snmp-alerts',
component: SnmpAlerts,
meta: {
title: i18n.t('appPageTitle.snmpAlerts'),
},
},
{
path: '/operations/factory-reset',
name: 'factory-reset',
Expand Down
16 changes: 16 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,22 @@ import ChassisStore from './modules/HardwareStatus/ChassisStore';
import BmcStore from './modules/HardwareStatus/BmcStore';
import ProcessorStore from './modules/HardwareStatus/ProcessorStore';
import AssemblyStore from './modules/HardwareStatus/AssemblyStore';
import PcieTopologyStore from './modules/HardwareStatus/PcieTopologyStore';
import PostCodeLogsStore from './modules/Logs/PostCodeLogsStore';
import PoliciesStore from './modules/SecurityAndAccess/PoliciesStore';
import FactoryResetStore from './modules/Operations/FactoryResetStore';
import HardwareDeconfigurationStore from './modules/Settings/HardwareDeconfigurationStore';
import KeyClearStore from './modules/Operations/KeyClearStore';

import SnmpAlertsStore from './modules/Settings/SnmpAlertsStore';
import WebSocketPlugin from './plugins/WebSocketPlugin';
import DateTimeStore from './modules/Settings/DateTimeStore';
import VirtualMediaStore from './modules/Operations/VirtualMediaStore';
import ResourceMemoryStore from './modules/ResourceManagement/ResourceMemoryStore';
import LateralCastOutControlStore from './modules/ResourceManagement/LateralCastOutControlStore';
import DeconfigurationRecordsStore from './modules/Logs/DeconfigurationRecordsStore';
import ConcurrentMaintenanceStore from './modules/HardwareStatus/ConcurrentMaintenanceStore';
import PcieSlotsStore from './modules/HardwareStatus/PcieSlotsStore';

Vue.use(Vuex);

Expand All @@ -42,20 +50,23 @@ export default new Vuex.Store({
modules: {
global: GlobalStore,
authentication: AuthenticationStore,
concurrent: ConcurrentMaintenanceStore,
sessions: SessionsStore,
dateTime: DateTimeStore,
ldap: LdapStore,
userManagement: UserManagementStore,
firmware: FirmwareStore,
serverBootSettings: BootSettingsStore,
controls: ControlStore,
pcieTopology: PcieTopologyStore,
powerControl: PowerControlStore,
powerPolicy: PowerPolicyStore,
powerSupply: PowerSupplyStore,
network: NetworkStore,
eventLog: EventLogStore,
sensors: SensorsStore,
serverLed: ServerLedStore,
snmpAlerts: SnmpAlertsStore,
certificates: CertificatesStore,
system: SystemStore,
memory: MemoryStore,
Expand All @@ -64,11 +75,16 @@ export default new Vuex.Store({
bmc: BmcStore,
processors: ProcessorStore,
assemblies: AssemblyStore,
pcieSlots: PcieSlotsStore,
postCodeLogs: PostCodeLogsStore,
virtualMedia: VirtualMediaStore,
policies: PoliciesStore,
factoryReset: FactoryResetStore,
keyClear: KeyClearStore,
resourceMemory: ResourceMemoryStore,
hardwareDeconfiguration: HardwareDeconfigurationStore,
deconfigurationRecords: DeconfigurationRecordsStore,
lateralCastOutControl: LateralCastOutControlStore,
},
plugins: [WebSocketPlugin],
});
116 changes: 116 additions & 0 deletions src/store/modules/Settings/SnmpAlertsStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import api, { getResponseCount } from '@/store/api';
import i18n from '@/i18n';
const SnmpAlertsStore = {
namespaced: true,
state: {
allSnmpDetails: [],
},
getters: {
allSnmpDetails(state) {
return state.allSnmpDetails;
},
},
mutations: {
setSnmpDetails(state, allSnmpDetails) {
state.allSnmpDetails = allSnmpDetails;
},
},
actions: {
async getSnmpAlertUrl() {
return await api
.get('/redfish/v1/')
.then((response) => api.get(response.data.EventService['@odata.id']))
.then((response) => api.get(response.data.Subscriptions['@odata.id']))
.then((response) => response.data['@odata.id'])
.catch((error) => console.log('Error', error));
},
async getSnmpDetails({ commit, dispatch }) {
const snmpAlertUrl = await dispatch('getSnmpAlertUrl');
return await api
.get(snmpAlertUrl)
.then((response) =>
response.data.Members.map((user) => user['@odata.id'])
)
.then((userIds) => api.all(userIds.map((user) => api.get(user))))
.then((users) => {
const snmpDetailsData = users.map((user) => user.data);
commit('setSnmpDetails', snmpDetailsData);
})
.catch((error) => {
console.log(error);
const message = i18n.t('pageSnmpAlerts.toast.errorLoadSnmpDetails');
throw new Error(message);
});
},
async deleteDestination({ dispatch }, id) {
const snmpAlertUrl = await dispatch('getSnmpAlertUrl');
return await api
.delete(`${snmpAlertUrl}/${id}`)
.then(() => dispatch('getSnmpDetails'))
.then(() =>
i18n.t('pageSnmpAlerts.toast.successDeleteDestination', {
id,
})
)
.catch((error) => {
console.log(error);
const message = i18n.t(
'pageSnmpAlerts.toast.errorDeleteDestination',
{
id,
}
);
throw new Error(message);
});
},
async deleteMultipleDestinations({ dispatch }, destination) {
const snmpAlertUrl = await dispatch('getSnmpAlertUrl');
const promises = destination.map(({ id }) => {
return api.delete(`${snmpAlertUrl}/${id}`).catch((error) => {
console.log(error);
return error;
});
});
return await api
.all(promises)
.then((response) => {
dispatch('getSnmpDetails');
return response;
})
.then(
api.spread((...responses) => {
const { successCount, errorCount } = getResponseCount(responses);
let toastMessages = [];
if (successCount) {
const message = i18n.tc(
'pageSnmpAlerts.toast.successBatchDelete',
successCount
);
toastMessages.push({ type: 'success', message });
}
if (errorCount) {
const message = i18n.tc(
'pageSnmpAlerts.toast.errorBatchDelete',
errorCount
);
toastMessages.push({ type: 'error', message });
}
return toastMessages;
})
);
},
async addDestination({ dispatch }, { data }) {
const snmpAlertUrl = await dispatch('getSnmpAlertUrl');
return await api
.post(snmpAlertUrl, data)
.then(() => dispatch('getSnmpDetails'))
.then(() => i18n.t('pageSnmpAlerts.toast.successAddDestination'))
.catch((error) => {
console.log(error);
const message = i18n.t('pageSnmpAlerts.toast.errorAddDestination');
throw new Error(message);
});
},
},
};
export default SnmpAlertsStore;
142 changes: 142 additions & 0 deletions src/views/Settings/SnmpAlerts/ModalAddDestination.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<template>
<b-modal id="add-destination" ref="modal" @ok="onOk" @hidden="resetForm">
<template #modal-title>
{{ $t('pageSnmpAlerts.modal.addSnmpDestinationTitle') }}
</template>
<b-form id="form-destination">
<b-container>
<b-row>
<b-col sm="6">
<!-- Add new SNMP alert destination type -->
<b-form-group
:label="$t('pageSnmpAlerts.modal.ipaddress')"
label-for="ip-address"
>
<b-form-input
id="ip-Address"
v-model="form.ipAddress"
:state="getValidationState($v.form.ipAddress)"
data-test-id="snmpAlerts-input-ipAddress"
type="text"
@blur="$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.ipAddress">
{{ $t('global.form.invalidFormat') }}
</template>
</b-form-invalid-feedback>
</b-form-group>
</b-col>
<b-col>
<b-form-group label-for="port">
<template #label>
{{ $t('pageSnmpAlerts.modal.port') }} -
<span class="form-text d-inline">
{{ $t('global.form.optional') }}
</span>
</template>
<b-form-input
id="port"
v-model="form.port"
type="text"
:state="getValidationState($v.form.port)"
data-test-id="snmpAlerts-input-port"
@blur="$v.form.port.$touch()"
/>
<b-form-invalid-feedback role="alert">
<template
v-if="!$v.form.port.minLength || !$v.form.port.maxLength"
>
{{
$t('global.form.valueMustBeBetween', {
min: 0,
max: 65535,
})
}}
</template>
</b-form-invalid-feedback>
</b-form-group>
</b-col>
</b-row>
</b-container>
</b-form>
<template #modal-footer="{ cancel }">
<b-button variant="secondary" @click="cancel()">
{{ $t('global.action.cancel') }}
</b-button>
<b-button
form="form-user"
type="submit"
variant="primary"
data-test-id="snmpAlerts-button-ok"
@click="onOk"
>
{{ $t('pageSnmpAlerts.addDestination') }}
</b-button>
</template>
</b-modal>
</template>
<script>
import {
required,
ipAddress,
minValue,
maxValue,
} from 'vuelidate/lib/validators';
import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
export default {
mixins: [VuelidateMixin],
data() {
return {
form: {
ipaddress: null,
port: null,
},
};
},
validations() {
return {
form: {
ipAddress: {
required,
ipAddress,
},
port: {
minValue: minValue(0),
maxValue: maxValue(65535),
},
},
};
},
methods: {
handleSubmit() {
this.$v.$touch();
if (this.$v.$invalid) return;
this.$emit('ok', {
ipAddress: this.form.ipAddress,
port: this.form.port,
});
this.closeModal();
},
closeModal() {
this.$nextTick(() => {
this.$refs.modal.hide();
});
},
resetForm() {
this.form.ipAddress = '';
this.form.port = '';
this.$v.$reset();
this.$emit('hidden');
},
onOk(bvModalEvt) {
// prevent modal close
bvModalEvt.preventDefault();
this.handleSubmit();
},
},
};
</script>
Loading

0 comments on commit d45ebe1

Please sign in to comment.