Skip to content

Commit 512633e

Browse files
committed
NMS-19542: SNMP Config layout updates, search
1 parent c010d09 commit 512633e

13 files changed

+563
-311
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<FeatherDropdown class="actions-dropdown-button">
3+
<template v-slot:trigger="{ attrs, on }">
4+
<FeatherButton
5+
primary
6+
v-bind="attrs"
7+
v-on="on"
8+
>
9+
<template v-slot:icon>
10+
<FeatherIcon :icon="currentIcon" aria-hidden="true" focusable="false" class="actions-dropdown-icon" />
11+
{{ label }}
12+
</template>
13+
</FeatherButton>
14+
</template>
15+
<FeatherDropdownItem
16+
v-for="action in actions"
17+
:key="action.action"
18+
@click="itemClicked(action.action)">
19+
<span class="actions-dropdown-menu-item">{{ action.label }}</span>
20+
</FeatherDropdownItem>
21+
</FeatherDropdown>
22+
</template>
23+
24+
<script setup lang="ts">
25+
import { FeatherButton } from '@featherds/button'
26+
import { FeatherDropdown, FeatherDropdownItem } from '@featherds/dropdown'
27+
import { FeatherIcon } from '@featherds/icon'
28+
import IconDownload from '@featherds/icon/action/DownloadFile'
29+
import IconUpload from '@featherds/icon/action/UploadFile'
30+
import { markRaw, PropType } from 'vue'
31+
32+
const props = defineProps({
33+
label: {
34+
required: true,
35+
type: String
36+
},
37+
icon: {
38+
required: true,
39+
type: String as PropType<'download' | 'upload'>
40+
},
41+
42+
actions: {
43+
required: true,
44+
type: Object as PropType<{
45+
label: string,
46+
action: string
47+
}[]>
48+
}
49+
})
50+
51+
const emit = defineEmits(['action-click'])
52+
53+
const downloadIcon = markRaw(IconDownload)
54+
const uploadIcon = markRaw(IconUpload)
55+
56+
const currentIcon = computed(() => {
57+
if (props.icon === 'download') {
58+
return downloadIcon
59+
}
60+
61+
if (props.icon === 'upload') {
62+
return uploadIcon
63+
}
64+
65+
return null
66+
})
67+
68+
const itemClicked = (action: string) => {
69+
emit('action-click', action)
70+
}
71+
72+
</script>
73+
74+
<style lang="scss" scoped>
75+
.actions-dropdown-menu-item {
76+
padding: 1em;
77+
}
78+
79+
button.btn.btn-icon .actions-dropdown-icon {
80+
font-size: 1.1rem;
81+
}
82+
83+
.actions-dropdown-button {
84+
text-align: left;
85+
}
86+
</style>

ui/src/components/SnmpConfiguration/SnmpConfigDefaultsTab.vue renamed to ui/src/components/SnmpConfiguration/SnmpConfigAdvancedTab.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<template>
2-
<div class="snmp-config-defaults-tab">
2+
<div class="snmp-config-advanced-tab">
33
<div class="main-section">
44
<SnmpConfigDefaultsPanel />
55
</div>
66
</div>
77
</template>
88

9-
<script setup lang="ts">
9+
<script lang="ts" setup>
10+
1011
import SnmpConfigDefaultsPanel from './SnmpConfigDefaultsPanel.vue'
1112
1213
</script>
@@ -17,7 +18,14 @@ import SnmpConfigDefaultsPanel from './SnmpConfigDefaultsPanel.vue'
1718
@use '@featherds/table/scss/table';
1819
@use '@/styles/vars.scss';
1920
20-
.snmp-config-defaults-tab {
21+
.snmp-config-advanced-tab {
22+
background: var(variables.$surface);
23+
width: 100%;
24+
padding: 0;
25+
border-radius: 5px;
26+
margin-top: 0;
27+
border: 1px solid var(variables.$border-on-surface);
28+
2129
.main-section {
2230
padding: 16px;
2331
}

ui/src/components/SnmpConfiguration/SnmpConfigDefaultsPanel.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
<template>
22
<div class="snmp-config-defaults-panel">
33
<div class="main-section">
4-
<div class="title-container">
5-
<h3 class="section-title">SNMP Configuration Defaults</h3>
6-
</div>
74
<div>
8-
<span>View and set "global" default values for SNMP configuration parameters.</span>
5+
<h3>SNMP Configuration Default Overrides</h3>
6+
<span>View and set "global" default override values for SNMP configuration parameters.</span>
97
<FeatherIcon
108
:icon="InfoIcon"
119
class="info-icon"

ui/src/components/SnmpConfiguration/SnmpConfigDefinitionsTab.vue

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
<template>
22
<div class="snmp-config-definitions-tab">
33
<div class="main-section">
4+
<div v-if="displayTable" class="info-section">
5+
<div>
6+
<span>SNMP definitions display how IP addresses, ranges, or patterns are currently configured.</span>
7+
<FeatherIcon
8+
:icon="InfoIcon"
9+
class="info-icon"
10+
@click="isMessageDialogVisible = true"
11+
data-test="snmp-config-definitions-info-icon"
12+
/>
13+
</div>
14+
</div>
415
<SnmpConfigDefinitionsTable v-if="displayTable" />
516

617
<div
@@ -16,18 +27,36 @@
1627
/>
1728
</div>
1829
</div>
30+
<MessageDialog
31+
:visible="isMessageDialogVisible"
32+
title="SNMP Definitions"
33+
@close="isMessageDialogVisible = false"
34+
>
35+
<template #content>
36+
<div class="message-dialog-content-body">
37+
<p>SNMP definitions display how IP addresses, ranges, or patterns are currently configured.</p>
38+
<p>You may add new definitions or edit existing ones to customize SNMP settings for specific devices.</p>
39+
<p>You may also delete definitions, in which case the SNMP configuration for those devices will revert to the system defaults.</p>
40+
<p>Note that OpenNMS also modifies and optimizes these configurations automatically.</p>
41+
</div>
42+
</template>
43+
</MessageDialog>
1944
</div>
2045
</template>
2146

2247
<script setup lang="ts">
48+
import { FeatherIcon } from '@featherds/icon'
49+
import InfoIcon from '@featherds/icon/action/Info'
2350
import useSnackbar from '@/composables/useSnackbar'
2451
import { SnmpConfigEditMode, useSnmpConfigStore } from '@/stores/snmpConfigStore'
2552
import { SnmpConfigFormErrors, SnmpDefinition } from '@/types/snmpConfig'
2653
import SnmpConfigDefinitionsTable from './SnmpConfigDefinitionsTable.vue'
2754
import SnmpConfigDefinitionBasicInformation from './SnmpConfigDefinitionBasicInformation.vue'
55+
import MessageDialog from '../Common/MessageDialog.vue'
2856
2957
const snackbar = useSnackbar()
3058
const store = useSnmpConfigStore()
59+
const isMessageDialogVisible = ref(false)
3160
3261
const displayTable = computed(() => {
3362
return store.definitionCreateEditMode === SnmpConfigEditMode.Table
@@ -84,9 +113,29 @@ const onSave = async (definition: SnmpDefinition) => {
84113
.main-section {
85114
display: flex;
86115
flex-direction: column;
87-
gap: 20px;
116+
gap: 0;
88117
padding: 20px;
89118
119+
.info-section {
120+
margin-bottom: 1em;
121+
122+
.label {
123+
color: var(variables.$primary-text-on-surface);
124+
}
125+
126+
.info-icon {
127+
cursor: pointer;
128+
font-size: 1.5em;
129+
margin-left: 0.5em;
130+
vertical-align: middle;
131+
color: var(variables.$primary);
132+
133+
&:hover {
134+
opacity: 0.8;
135+
}
136+
}
137+
}
138+
90139
.snmp-config-details {
91140
border-radius: 5px;
92141
padding: 20px;

ui/src/components/SnmpConfiguration/SnmpConfigDefinitionsTable.vue

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
<template>
22
<TableCard class="snmp-config-definitions-table">
33
<div class="header">
4-
<div class="title-container">
5-
<!-- <span class="title"> SNMP Interfaces </span> -->
6-
</div>
74
<div class="action-container">
5+
<div class="search-container">
6+
<FeatherInput
7+
v-model="searchTerm"
8+
@update:modelValue="onSearchChange"
9+
label="Search IP addresses or location"
10+
>
11+
<template #pre>
12+
<FeatherIcon :icon="IconSearch" />
13+
</template>
14+
</FeatherInput>
15+
</div>
816
</div>
917
</div>
1018
<div class="container">
@@ -134,12 +142,14 @@
134142
</template>
135143

136144
<script lang="ts" setup>
137-
import { cloneDeep } from 'lodash'
145+
import { cloneDeep, debounce } from 'lodash'
138146
import { FeatherTextBadge, BadgeTypes } from '@featherds/badge'
139147
import { FeatherButton } from '@featherds/button'
140148
import { FeatherIcon } from '@featherds/icon'
141149
import IconDelete from '@featherds/icon/action/Delete'
142150
import IconEdit from '@featherds/icon/action/Edit'
151+
import IconSearch from '@featherds/icon/action/Search'
152+
import { FeatherInput } from '@featherds/input'
143153
import { FeatherPagination } from '@featherds/pagination'
144154
import { FeatherSortHeader, SORT } from '@featherds/table'
145155
@@ -157,6 +167,8 @@ const currentPage = ref(1)
157167
const pageSize = ref(50)
158168
const showDeleteConfirmation = ref(false)
159169
const definitionToDelete = ref<SnmpDefinition | null>(null)
170+
const searchTerm = ref('')
171+
const debouncedSearchTerm = ref('')
160172
161173
const emptyListContent = {
162174
msg: 'No results found.'
@@ -196,8 +208,48 @@ const createIpAddressLabel = (d: SnmpDefinition) => {
196208
return items
197209
}
198210
211+
const matchesSearchTerm = (def: SnmpDefinition, search: string) => {
212+
const lowerSearch = search.toLowerCase()
213+
214+
// Check location
215+
if ((def.location ?? 'default').toLowerCase().includes(lowerSearch)) {
216+
return true
217+
}
218+
219+
// Check range (begin and end)
220+
if (def.range?.some(r =>
221+
r.begin.toLowerCase().includes(lowerSearch) ||
222+
r.end.toLowerCase().includes(lowerSearch)
223+
)) {
224+
return true
225+
}
226+
227+
// Check specific IPs
228+
if (def.specific?.some(ip => ip.toLowerCase().includes(lowerSearch))) {
229+
return true
230+
}
231+
232+
// Check ipMatch
233+
if (def.ipMatch?.some(match => match.toLowerCase().includes(lowerSearch))) {
234+
return true
235+
}
236+
237+
return false
238+
}
239+
199240
const pageTotal = computed(() => {
200-
return store.config.definition?.length
241+
if (!store.config.definition) {
242+
return 0
243+
}
244+
245+
// Return filtered count if there's a search term
246+
if (debouncedSearchTerm.value) {
247+
return store.config.definition.filter(def =>
248+
matchesSearchTerm(def, debouncedSearchTerm.value)
249+
).length
250+
}
251+
252+
return store.config.definition.length
201253
})
202254
203255
const definitionsView = computed<SnmpDefinition[]>(() => {
@@ -208,6 +260,11 @@ const definitionsView = computed<SnmpDefinition[]>(() => {
208260
// Copy the definitions array
209261
let items: SnmpDefinition[] = [...store.config.definition]
210262
263+
// Filter by search term
264+
if (debouncedSearchTerm.value) {
265+
items = items.filter(def => matchesSearchTerm(def, debouncedSearchTerm.value))
266+
}
267+
211268
// Sort by the active sort property
212269
const sortProperty = Object.keys(sort).find(key => sort[key] !== SORT.NONE)
213270
@@ -302,6 +359,15 @@ const cancelDelete = () => {
302359
showDeleteConfirmation.value = false
303360
definitionToDelete.value = null
304361
}
362+
363+
const updateDebouncedSearchTerm = debounce((value: string) => {
364+
debouncedSearchTerm.value = value
365+
currentPage.value = 1 // Reset to first page when searching
366+
}, 200)
367+
368+
const onSearchChange = (value: string | number | undefined) => {
369+
updateDebouncedSearchTerm(String(value || ''))
370+
}
305371
</script>
306372

307373
<style lang="scss" scoped>
@@ -311,32 +377,24 @@ const cancelDelete = () => {
311377
@use '@/styles/_transitionDataTable';
312378
313379
.snmp-config-definitions-table {
314-
margin-top: 10px;
315-
padding: 25px;
380+
margin-top: 0;
381+
padding: 0;
316382
317383
.header {
318384
display: flex;
319385
justify-content: space-between;
320-
margin-bottom: 20px;
321-
322-
.title-container {
323-
display: flex;
324-
align-items: center;
325-
326-
.title {
327-
@include typography.headline3;
328-
}
329-
}
386+
margin-bottom: 0;
330387
331388
.action-container {
332389
display: flex;
333390
align-items: flex-start;
334-
justify-content: flex-end;
391+
justify-content: flex-start;
335392
gap: 5px;
336393
width: 30%;
337394
338395
.search-container {
339396
width: 80%;
397+
min-width: 30em;
340398
}
341399
}
342400
}

0 commit comments

Comments
 (0)