]>
Commit | Line | Data |
---|---|---|
03a54ebd | 1 | Ext.define('Proxmox.panel.NotificationMatcherGeneralPanel', { |
5d1b587f | 2 | extend: 'Proxmox.panel.InputPanel', |
03a54ebd | 3 | xtype: 'pmxNotificationMatcherGeneralPanel', |
5d1b587f LW |
4 | mixins: ['Proxmox.Mixin.CBind'], |
5 | ||
6 | items: [ | |
7 | { | |
8 | xtype: 'pmxDisplayEditField', | |
9 | name: 'name', | |
10 | cbind: { | |
11 | value: '{name}', | |
12 | editable: '{isCreate}', | |
13 | }, | |
e8f1954c | 14 | fieldLabel: gettext('Matcher Name'), |
5d1b587f LW |
15 | allowBlank: false, |
16 | }, | |
5f7b28cb LW |
17 | { |
18 | xtype: 'proxmoxcheckbox', | |
19 | name: 'enable', | |
20 | fieldLabel: gettext('Enable'), | |
21 | allowBlank: false, | |
22 | checked: true, | |
23 | }, | |
5d1b587f | 24 | { |
03a54ebd LW |
25 | xtype: 'proxmoxtextfield', |
26 | name: 'comment', | |
27 | fieldLabel: gettext('Comment'), | |
5d1b587f LW |
28 | cbind: { |
29 | deleteEmpty: '{!isCreate}', | |
30 | }, | |
5d1b587f | 31 | }, |
03a54ebd | 32 | ], |
5f7b28cb LW |
33 | |
34 | ||
35 | onSetValues: function(values) { | |
36 | values.enable = !values.disable; | |
37 | ||
38 | delete values.disable; | |
39 | return values; | |
40 | }, | |
41 | ||
42 | onGetValues: function(values) { | |
43 | let me = this; | |
44 | ||
45 | if (values.enable) { | |
46 | if (!me.isCreate) { | |
47 | Proxmox.Utils.assemble_field_data(values, { 'delete': 'disable' }); | |
48 | } | |
49 | } else { | |
50 | values.disable = 1; | |
51 | } | |
52 | delete values.enable; | |
53 | ||
54 | return values; | |
55 | }, | |
03a54ebd LW |
56 | }); |
57 | ||
58 | Ext.define('Proxmox.panel.NotificationMatcherTargetPanel', { | |
59 | extend: 'Proxmox.panel.InputPanel', | |
60 | xtype: 'pmxNotificationMatcherTargetPanel', | |
61 | mixins: ['Proxmox.Mixin.CBind'], | |
62 | ||
63 | items: [ | |
78d21b71 LW |
64 | { |
65 | xtype: 'pmxNotificationTargetSelector', | |
66 | name: 'target', | |
67 | allowBlank: false, | |
68 | }, | |
5d1b587f LW |
69 | ], |
70 | }); | |
71 | ||
e8f1954c | 72 | Ext.define('Proxmox.window.NotificationMatcherEdit', { |
5d1b587f LW |
73 | extend: 'Proxmox.window.Edit', |
74 | ||
75 | isAdd: true, | |
c972de23 | 76 | onlineHelp: 'notification_matchers', |
5d1b587f LW |
77 | |
78 | fieldDefaults: { | |
79 | labelWidth: 120, | |
80 | }, | |
81 | ||
03a54ebd | 82 | width: 700, |
5d1b587f LW |
83 | |
84 | initComponent: function() { | |
85 | let me = this; | |
86 | ||
87 | me.isCreate = !me.name; | |
88 | ||
89 | if (!me.baseUrl) { | |
90 | throw "baseUrl not set"; | |
91 | } | |
92 | ||
e8f1954c | 93 | me.url = `/api2/extjs${me.baseUrl}/matchers`; |
5d1b587f LW |
94 | |
95 | if (me.isCreate) { | |
96 | me.method = 'POST'; | |
97 | } else { | |
98 | me.url += `/${me.name}`; | |
99 | me.method = 'PUT'; | |
100 | } | |
101 | ||
e8f1954c | 102 | me.subject = gettext('Notification Matcher'); |
5d1b587f LW |
103 | |
104 | Ext.apply(me, { | |
03a54ebd LW |
105 | bodyPadding: 0, |
106 | items: [ | |
107 | { | |
108 | xtype: 'tabpanel', | |
109 | region: 'center', | |
110 | layout: 'fit', | |
111 | bodyPadding: 10, | |
112 | items: [ | |
113 | { | |
114 | name: me.name, | |
115 | title: gettext('General'), | |
116 | xtype: 'pmxNotificationMatcherGeneralPanel', | |
117 | isCreate: me.isCreate, | |
118 | baseUrl: me.baseUrl, | |
119 | }, | |
120 | { | |
121 | name: me.name, | |
122 | title: gettext('Match Rules'), | |
123 | xtype: 'pmxNotificationMatchRulesEditPanel', | |
124 | isCreate: me.isCreate, | |
125 | baseUrl: me.baseUrl, | |
126 | }, | |
127 | { | |
128 | name: me.name, | |
129 | title: gettext('Targets to notify'), | |
130 | xtype: 'pmxNotificationMatcherTargetPanel', | |
131 | isCreate: me.isCreate, | |
132 | baseUrl: me.baseUrl, | |
133 | }, | |
134 | ], | |
135 | }, | |
136 | ], | |
5d1b587f LW |
137 | }); |
138 | ||
139 | me.callParent(); | |
140 | ||
141 | if (!me.isCreate) { | |
142 | me.load(); | |
143 | } | |
144 | }, | |
145 | }); | |
78d21b71 LW |
146 | |
147 | Ext.define('Proxmox.form.NotificationTargetSelector', { | |
148 | extend: 'Ext.grid.Panel', | |
149 | alias: 'widget.pmxNotificationTargetSelector', | |
150 | ||
151 | mixins: { | |
152 | field: 'Ext.form.field.Field', | |
153 | }, | |
154 | ||
155 | padding: '0 0 10 0', | |
156 | ||
157 | allowBlank: true, | |
158 | selectAll: false, | |
159 | isFormField: true, | |
160 | ||
161 | store: { | |
162 | autoLoad: true, | |
163 | model: 'proxmox-notification-endpoints', | |
164 | sorters: 'name', | |
165 | }, | |
166 | ||
167 | columns: [ | |
168 | { | |
169 | header: gettext('Target Name'), | |
170 | dataIndex: 'name', | |
171 | flex: 1, | |
172 | }, | |
173 | { | |
174 | header: gettext('Type'), | |
175 | dataIndex: 'type', | |
176 | flex: 1, | |
177 | }, | |
178 | { | |
179 | header: gettext('Comment'), | |
180 | dataIndex: 'comment', | |
181 | flex: 3, | |
182 | }, | |
183 | ], | |
184 | ||
185 | selModel: { | |
186 | selType: 'checkboxmodel', | |
187 | mode: 'SIMPLE', | |
188 | }, | |
189 | ||
190 | checkChangeEvents: [ | |
191 | 'selectionchange', | |
192 | 'change', | |
193 | ], | |
194 | ||
195 | listeners: { | |
196 | selectionchange: function() { | |
197 | // to trigger validity and error checks | |
198 | this.checkChange(); | |
199 | }, | |
200 | }, | |
201 | ||
202 | getSubmitData: function() { | |
203 | let me = this; | |
204 | let res = {}; | |
205 | res[me.name] = me.getValue(); | |
206 | return res; | |
207 | }, | |
208 | ||
209 | getValue: function() { | |
210 | let me = this; | |
211 | if (me.savedValue !== undefined) { | |
212 | return me.savedValue; | |
213 | } | |
214 | let sm = me.getSelectionModel(); | |
215 | return (sm.getSelection() ?? []).map(item => item.data.name); | |
216 | }, | |
217 | ||
218 | setValueSelection: function(value) { | |
219 | let me = this; | |
220 | ||
221 | let store = me.getStore(); | |
222 | ||
223 | let notFound = []; | |
224 | let selection = value.map(item => { | |
225 | let found = store.findRecord('name', item, 0, false, true, true); | |
226 | if (!found) { | |
227 | notFound.push(item); | |
228 | } | |
229 | return found; | |
230 | }).filter(r => r); | |
231 | ||
232 | for (const name of notFound) { | |
233 | let rec = store.add({ | |
234 | name, | |
235 | type: '-', | |
236 | comment: gettext('Included target does not exist!'), | |
237 | }); | |
238 | selection.push(rec[0]); | |
239 | } | |
240 | ||
241 | let sm = me.getSelectionModel(); | |
242 | if (selection.length) { | |
243 | sm.select(selection); | |
244 | } else { | |
245 | sm.deselectAll(); | |
246 | } | |
247 | // to correctly trigger invalid class | |
248 | me.getErrors(); | |
249 | }, | |
250 | ||
251 | setValue: function(value) { | |
252 | let me = this; | |
253 | ||
254 | let store = me.getStore(); | |
255 | if (!store.isLoaded()) { | |
256 | me.savedValue = value; | |
257 | store.on('load', function() { | |
258 | me.setValueSelection(value); | |
259 | delete me.savedValue; | |
260 | }, { single: true }); | |
261 | } else { | |
262 | me.setValueSelection(value); | |
263 | } | |
264 | return me.mixins.field.setValue.call(me, value); | |
265 | }, | |
266 | ||
267 | getErrors: function(value) { | |
268 | let me = this; | |
269 | if (!me.isDisabled() && me.allowBlank === false && | |
270 | me.getSelectionModel().getCount() === 0) { | |
271 | me.addBodyCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']); | |
272 | return [gettext('No target selected')]; | |
273 | } | |
274 | ||
275 | me.removeBodyCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']); | |
276 | return []; | |
277 | }, | |
278 | ||
279 | initComponent: function() { | |
280 | let me = this; | |
281 | me.callParent(); | |
282 | me.initField(); | |
283 | }, | |
284 | ||
285 | }); | |
03a54ebd LW |
286 | |
287 | Ext.define('Proxmox.panel.NotificationRulesEditPanel', { | |
288 | extend: 'Proxmox.panel.InputPanel', | |
289 | xtype: 'pmxNotificationMatchRulesEditPanel', | |
290 | mixins: ['Proxmox.Mixin.CBind'], | |
291 | ||
be532951 DC |
292 | controller: { |
293 | xclass: 'Ext.app.ViewController', | |
294 | ||
295 | // we want to also set the empty value, but 'bind' does not do that so | |
296 | // we have to set it then (and only then) to get the correct value in | |
297 | // the tree | |
298 | control: { | |
299 | 'field': { | |
300 | change: function(cmp) { | |
301 | let me = this; | |
302 | let vm = me.getViewModel(); | |
303 | if (cmp.field) { | |
304 | let record = vm.get('selectedRecord'); | |
305 | if (!record) { | |
306 | return; | |
307 | } | |
308 | let data = Ext.apply({}, record.get('data')); | |
309 | let value = cmp.getValue(); | |
310 | // only update if the value is empty (or empty array) | |
311 | if (!value || !value.length) { | |
312 | data[cmp.field] = value; | |
313 | record.set({ data }); | |
314 | } | |
315 | } | |
316 | }, | |
317 | }, | |
318 | }, | |
319 | }, | |
320 | ||
03a54ebd LW |
321 | viewModel: { |
322 | data: { | |
323 | selectedRecord: null, | |
324 | matchFieldType: 'exact', | |
325 | matchFieldField: '', | |
326 | matchFieldValue: '', | |
327 | rootMode: 'all', | |
328 | }, | |
329 | ||
330 | formulas: { | |
331 | nodeType: { | |
332 | get: function(get) { | |
333 | let record = get('selectedRecord'); | |
334 | return record?.get('type'); | |
335 | }, | |
336 | set: function(value) { | |
337 | let me = this; | |
338 | let record = me.get('selectedRecord'); | |
339 | ||
340 | let data; | |
341 | ||
342 | switch (value) { | |
343 | case 'match-severity': | |
344 | data = { | |
6ed92b73 | 345 | value: ['info', 'notice', 'warning', 'error', 'unknown'], |
03a54ebd LW |
346 | }; |
347 | break; | |
348 | case 'match-field': | |
349 | data = { | |
350 | type: 'exact', | |
351 | field: '', | |
352 | value: '', | |
353 | }; | |
354 | break; | |
355 | case 'match-calendar': | |
356 | data = { | |
357 | value: '', | |
358 | }; | |
359 | break; | |
360 | } | |
361 | ||
362 | let node = { | |
363 | type: value, | |
364 | data, | |
365 | }; | |
366 | record.set(node); | |
367 | }, | |
368 | }, | |
369 | showMatchingMode: function(get) { | |
370 | let record = get('selectedRecord'); | |
371 | if (!record) { | |
372 | return false; | |
373 | } | |
374 | return record.isRoot(); | |
375 | }, | |
376 | showMatcherType: function(get) { | |
377 | let record = get('selectedRecord'); | |
378 | if (!record) { | |
379 | return false; | |
380 | } | |
381 | return !record.isRoot(); | |
382 | }, | |
383 | typeIsMatchField: { | |
384 | bind: { | |
385 | bindTo: '{selectedRecord}', | |
386 | deep: true, | |
387 | }, | |
388 | get: function(record) { | |
389 | return record?.get('type') === 'match-field'; | |
390 | }, | |
391 | }, | |
392 | typeIsMatchSeverity: { | |
393 | bind: { | |
394 | bindTo: '{selectedRecord}', | |
395 | deep: true, | |
396 | }, | |
397 | get: function(record) { | |
398 | return record?.get('type') === 'match-severity'; | |
399 | }, | |
400 | }, | |
401 | typeIsMatchCalendar: { | |
402 | bind: { | |
403 | bindTo: '{selectedRecord}', | |
404 | deep: true, | |
405 | }, | |
406 | get: function(record) { | |
407 | return record?.get('type') === 'match-calendar'; | |
408 | }, | |
409 | }, | |
410 | matchFieldType: { | |
411 | bind: { | |
412 | bindTo: '{selectedRecord}', | |
413 | deep: true, | |
414 | }, | |
415 | set: function(value) { | |
416 | let me = this; | |
417 | let record = me.get('selectedRecord'); | |
418 | let currentData = record.get('data'); | |
419 | record.set({ | |
420 | data: { | |
421 | ...currentData, | |
422 | type: value, | |
423 | }, | |
424 | }); | |
425 | }, | |
426 | get: function(record) { | |
427 | return record?.get('data')?.type; | |
428 | }, | |
429 | }, | |
430 | matchFieldField: { | |
431 | bind: { | |
432 | bindTo: '{selectedRecord}', | |
433 | deep: true, | |
434 | }, | |
435 | set: function(value) { | |
436 | let me = this; | |
437 | let record = me.get('selectedRecord'); | |
438 | let currentData = record.get('data'); | |
439 | ||
440 | record.set({ | |
441 | data: { | |
442 | ...currentData, | |
443 | field: value, | |
444 | }, | |
445 | }); | |
446 | }, | |
447 | get: function(record) { | |
448 | return record?.get('data')?.field; | |
449 | }, | |
450 | }, | |
451 | matchFieldValue: { | |
452 | bind: { | |
453 | bindTo: '{selectedRecord}', | |
454 | deep: true, | |
455 | }, | |
456 | set: function(value) { | |
457 | let me = this; | |
458 | let record = me.get('selectedRecord'); | |
459 | let currentData = record.get('data'); | |
460 | record.set({ | |
461 | data: { | |
462 | ...currentData, | |
463 | value: value, | |
464 | }, | |
465 | }); | |
466 | }, | |
467 | get: function(record) { | |
468 | return record?.get('data')?.value; | |
469 | }, | |
470 | }, | |
471 | matchSeverityValue: { | |
472 | bind: { | |
473 | bindTo: '{selectedRecord}', | |
474 | deep: true, | |
475 | }, | |
476 | set: function(value) { | |
477 | let me = this; | |
478 | let record = me.get('selectedRecord'); | |
479 | let currentData = record.get('data'); | |
480 | record.set({ | |
481 | data: { | |
482 | ...currentData, | |
483 | value: value, | |
484 | }, | |
485 | }); | |
486 | }, | |
487 | get: function(record) { | |
488 | return record?.get('data')?.value; | |
489 | }, | |
490 | }, | |
491 | matchCalendarValue: { | |
492 | bind: { | |
493 | bindTo: '{selectedRecord}', | |
494 | deep: true, | |
495 | }, | |
496 | set: function(value) { | |
497 | let me = this; | |
498 | let record = me.get('selectedRecord'); | |
499 | let currentData = record.get('data'); | |
500 | record.set({ | |
501 | data: { | |
502 | ...currentData, | |
503 | value: value, | |
504 | }, | |
505 | }); | |
506 | }, | |
507 | get: function(record) { | |
508 | return record?.get('data')?.value; | |
509 | }, | |
510 | }, | |
511 | rootMode: { | |
512 | bind: { | |
513 | bindTo: '{selectedRecord}', | |
514 | deep: true, | |
515 | }, | |
516 | set: function(value) { | |
517 | let me = this; | |
518 | let record = me.get('selectedRecord'); | |
519 | let currentData = record.get('data'); | |
1f8bfa3b DC |
520 | let invert = false; |
521 | if (value.startsWith('not')) { | |
522 | value = value.substring(3); | |
523 | invert = true; | |
524 | } | |
03a54ebd LW |
525 | record.set({ |
526 | data: { | |
527 | ...currentData, | |
528 | value, | |
1f8bfa3b | 529 | invert, |
03a54ebd LW |
530 | }, |
531 | }); | |
532 | }, | |
533 | get: function(record) { | |
1f8bfa3b DC |
534 | let prefix = record?.get('data').invert ? 'not' : ''; |
535 | return prefix + record?.get('data')?.value; | |
03a54ebd LW |
536 | }, |
537 | }, | |
538 | }, | |
539 | }, | |
540 | ||
541 | column1: [ | |
542 | { | |
543 | xtype: 'pmxNotificationMatchRuleTree', | |
544 | cbind: { | |
545 | isCreate: '{isCreate}', | |
546 | }, | |
547 | }, | |
548 | ], | |
549 | column2: [ | |
550 | { | |
551 | xtype: 'pmxNotificationMatchRuleSettings', | |
552 | }, | |
553 | ||
554 | ], | |
555 | ||
556 | onGetValues: function(values) { | |
557 | let me = this; | |
558 | ||
559 | let deleteArrayIfEmtpy = (field) => { | |
560 | if (Ext.isArray(values[field])) { | |
561 | if (values[field].length === 0) { | |
562 | delete values[field]; | |
563 | if (!me.isCreate) { | |
564 | Proxmox.Utils.assemble_field_data(values, { 'delete': field }); | |
565 | } | |
566 | } | |
567 | } | |
568 | }; | |
569 | deleteArrayIfEmtpy('match-field'); | |
570 | deleteArrayIfEmtpy('match-severity'); | |
571 | deleteArrayIfEmtpy('match-calendar'); | |
572 | ||
573 | return values; | |
574 | }, | |
575 | }); | |
576 | ||
577 | Ext.define('Proxmox.panel.NotificationMatchRuleTree', { | |
578 | extend: 'Ext.panel.Panel', | |
579 | xtype: 'pmxNotificationMatchRuleTree', | |
580 | mixins: ['Proxmox.Mixin.CBind'], | |
581 | border: false, | |
582 | ||
583 | getNodeTextAndIcon: function(type, data) { | |
584 | let text; | |
585 | let iconCls; | |
586 | ||
587 | switch (type) { | |
588 | case 'match-severity': { | |
be532951 DC |
589 | let v = data.value; |
590 | if (Ext.isArray(data.value)) { | |
591 | v = data.value.join(', '); | |
592 | } | |
03a54ebd LW |
593 | text = Ext.String.format(gettext("Match severity: {0}"), v); |
594 | iconCls = 'fa fa-exclamation'; | |
be532951 DC |
595 | if (!v) { |
596 | iconCls += ' critical'; | |
597 | } | |
03a54ebd LW |
598 | } break; |
599 | case 'match-field': { | |
600 | let field = data.field; | |
601 | let value = data.value; | |
602 | text = Ext.String.format(gettext("Match field: {0}={1}"), field, value); | |
603 | iconCls = 'fa fa-cube'; | |
be532951 DC |
604 | if (!field || !value) { |
605 | iconCls += ' critical'; | |
606 | } | |
03a54ebd LW |
607 | } break; |
608 | case 'match-calendar': { | |
609 | let v = data.value; | |
610 | text = Ext.String.format(gettext("Match calendar: {0}"), v); | |
611 | iconCls = 'fa fa-calendar-o'; | |
be532951 DC |
612 | if (!v || !v.length) { |
613 | iconCls += ' critical'; | |
614 | } | |
03a54ebd LW |
615 | } break; |
616 | case 'mode': | |
617 | if (data.value === 'all') { | |
618 | text = gettext("All"); | |
619 | } else if (data.value === 'any') { | |
620 | text = gettext("Any"); | |
621 | } | |
622 | if (data.invert) { | |
623 | text = `!${text}`; | |
624 | } | |
625 | iconCls = 'fa fa-filter'; | |
626 | ||
627 | break; | |
628 | } | |
629 | ||
630 | return [text, iconCls]; | |
631 | }, | |
632 | ||
633 | initComponent: function() { | |
634 | let me = this; | |
635 | ||
636 | let treeStore = Ext.create('Ext.data.TreeStore', { | |
637 | root: { | |
638 | expanded: true, | |
639 | expandable: false, | |
640 | text: '', | |
641 | type: 'mode', | |
642 | data: { | |
643 | value: 'all', | |
644 | invert: false, | |
645 | }, | |
646 | children: [], | |
647 | iconCls: 'fa fa-filter', | |
648 | }, | |
649 | }); | |
650 | ||
651 | let realMatchFields = Ext.create({ | |
652 | xtype: 'hiddenfield', | |
653 | setValue: function(value) { | |
654 | this.value = value; | |
655 | this.checkChange(); | |
656 | }, | |
657 | getValue: function() { | |
658 | return this.value; | |
659 | }, | |
be532951 DC |
660 | getErrors: function() { |
661 | for (const matcher of this.value ?? []) { | |
662 | let matches = matcher.match(/^([^:]+):([^=]+)=(.+)$/); | |
663 | if (!matches) { | |
664 | return [""]; // fake error for validation | |
665 | } | |
666 | } | |
667 | return []; | |
668 | }, | |
03a54ebd LW |
669 | getSubmitValue: function() { |
670 | let value = this.value; | |
671 | if (!value) { | |
672 | value = []; | |
673 | } | |
674 | return value; | |
675 | }, | |
676 | name: 'match-field', | |
677 | }); | |
678 | ||
679 | let realMatchSeverity = Ext.create({ | |
680 | xtype: 'hiddenfield', | |
681 | setValue: function(value) { | |
682 | this.value = value; | |
683 | this.checkChange(); | |
684 | }, | |
685 | getValue: function() { | |
686 | return this.value; | |
687 | }, | |
be532951 DC |
688 | getErrors: function() { |
689 | for (const severities of this.value ?? []) { | |
690 | if (!severities) { | |
691 | return [""]; // fake error for validation | |
692 | } | |
693 | } | |
694 | return []; | |
695 | }, | |
03a54ebd LW |
696 | getSubmitValue: function() { |
697 | let value = this.value; | |
698 | if (!value) { | |
699 | value = []; | |
700 | } | |
701 | return value; | |
702 | }, | |
703 | name: 'match-severity', | |
704 | }); | |
705 | ||
706 | let realMode = Ext.create({ | |
707 | xtype: 'hiddenfield', | |
708 | name: 'mode', | |
709 | setValue: function(value) { | |
710 | this.value = value; | |
711 | this.checkChange(); | |
712 | }, | |
713 | getValue: function() { | |
714 | return this.value; | |
715 | }, | |
716 | getSubmitValue: function() { | |
717 | let value = this.value; | |
718 | return value; | |
719 | }, | |
720 | }); | |
721 | ||
722 | let realMatchCalendar = Ext.create({ | |
723 | xtype: 'hiddenfield', | |
724 | name: 'match-calendar', | |
725 | ||
726 | setValue: function(value) { | |
727 | this.value = value; | |
728 | this.checkChange(); | |
729 | }, | |
730 | getValue: function() { | |
731 | return this.value; | |
732 | }, | |
be532951 DC |
733 | getErrors: function() { |
734 | for (const timespan of this.value ?? []) { | |
735 | if (!timespan) { | |
736 | return [""]; // fake error for validation | |
737 | } | |
738 | } | |
739 | return []; | |
740 | }, | |
03a54ebd LW |
741 | getSubmitValue: function() { |
742 | let value = this.value; | |
743 | return value; | |
744 | }, | |
745 | }); | |
746 | ||
747 | let realInvertMatch = Ext.create({ | |
748 | xtype: 'proxmoxcheckbox', | |
749 | name: 'invert-match', | |
750 | hidden: true, | |
751 | deleteEmpty: !me.isCreate, | |
752 | }); | |
753 | ||
754 | let storeChanged = function(store) { | |
755 | store.suspendEvent('datachanged'); | |
756 | ||
757 | let matchFieldStmts = []; | |
758 | let matchSeverityStmts = []; | |
759 | let matchCalendarStmts = []; | |
760 | let modeStmt = 'all'; | |
761 | let invertMatchStmt = false; | |
762 | ||
763 | store.each(function(model) { | |
764 | let type = model.get('type'); | |
765 | let data = model.get('data'); | |
766 | ||
767 | switch (type) { | |
768 | case 'match-field': | |
be532951 | 769 | matchFieldStmts.push(`${data.type}:${data.field ?? ''}=${data.value ?? ''}`); |
03a54ebd LW |
770 | break; |
771 | case 'match-severity': | |
be532951 DC |
772 | if (Ext.isArray(data.value)) { |
773 | matchSeverityStmts.push(data.value.join(',')); | |
774 | } else { | |
775 | matchSeverityStmts.push(data.value); | |
776 | } | |
03a54ebd LW |
777 | break; |
778 | case 'match-calendar': | |
779 | matchCalendarStmts.push(data.value); | |
780 | break; | |
781 | case 'mode': | |
edd98f94 FE |
782 | modeStmt = data.value; |
783 | invertMatchStmt = data.invert; | |
03a54ebd LW |
784 | break; |
785 | } | |
786 | ||
787 | let [text, iconCls] = me.getNodeTextAndIcon(type, data); | |
788 | model.set({ | |
789 | text, | |
790 | iconCls, | |
791 | }); | |
792 | }); | |
793 | ||
794 | realMatchFields.suspendEvent('change'); | |
795 | realMatchFields.setValue(matchFieldStmts); | |
796 | realMatchFields.resumeEvent('change'); | |
797 | ||
798 | realMatchCalendar.suspendEvent('change'); | |
799 | realMatchCalendar.setValue(matchCalendarStmts); | |
800 | realMatchCalendar.resumeEvent('change'); | |
801 | ||
802 | realMode.suspendEvent('change'); | |
803 | realMode.setValue(modeStmt); | |
804 | realMode.resumeEvent('change'); | |
805 | ||
806 | realInvertMatch.suspendEvent('change'); | |
807 | realInvertMatch.setValue(invertMatchStmt); | |
808 | realInvertMatch.resumeEvent('change'); | |
809 | ||
810 | realMatchSeverity.suspendEvent('change'); | |
811 | realMatchSeverity.setValue(matchSeverityStmts); | |
812 | realMatchSeverity.resumeEvent('change'); | |
813 | ||
814 | store.resumeEvent('datachanged'); | |
815 | }; | |
816 | ||
817 | realMatchFields.addListener('change', function(field, value) { | |
818 | let parseMatchField = function(filter) { | |
819 | let [, type, matchedField, matchedValue] = | |
820 | filter.match(/^(?:(regex|exact):)?([A-Za-z0-9_][A-Za-z0-9._-]*)=(.+)$/); | |
821 | if (type === undefined) { | |
822 | type = "exact"; | |
823 | } | |
824 | return { | |
825 | type: 'match-field', | |
826 | data: { | |
827 | type, | |
828 | field: matchedField, | |
829 | value: matchedValue, | |
830 | }, | |
831 | leaf: true, | |
832 | }; | |
833 | }; | |
834 | ||
835 | for (let node of treeStore.queryBy( | |
836 | record => record.get('type') === 'match-field', | |
837 | ).getRange()) { | |
838 | node.remove(true); | |
839 | } | |
840 | ||
be532951 DC |
841 | if (!value) { |
842 | return; | |
843 | } | |
03a54ebd LW |
844 | let records = value.map(parseMatchField); |
845 | ||
846 | let rootNode = treeStore.getRootNode(); | |
847 | ||
848 | for (let record of records) { | |
849 | rootNode.appendChild(record); | |
850 | } | |
851 | }); | |
852 | ||
853 | realMatchSeverity.addListener('change', function(field, value) { | |
854 | let parseSeverity = function(severities) { | |
855 | return { | |
856 | type: 'match-severity', | |
857 | data: { | |
858 | value: severities.split(','), | |
859 | }, | |
860 | leaf: true, | |
861 | }; | |
862 | }; | |
863 | ||
864 | for (let node of treeStore.queryBy( | |
865 | record => record.get('type') === 'match-severity').getRange()) { | |
866 | node.remove(true); | |
867 | } | |
868 | ||
869 | let records = value.map(parseSeverity); | |
870 | let rootNode = treeStore.getRootNode(); | |
871 | ||
872 | for (let record of records) { | |
873 | rootNode.appendChild(record); | |
874 | } | |
875 | }); | |
876 | ||
877 | realMatchCalendar.addListener('change', function(field, value) { | |
878 | let parseCalendar = function(timespan) { | |
879 | return { | |
880 | type: 'match-calendar', | |
881 | data: { | |
882 | value: timespan, | |
883 | }, | |
884 | leaf: true, | |
885 | }; | |
886 | }; | |
887 | ||
888 | for (let node of treeStore.queryBy( | |
889 | record => record.get('type') === 'match-calendar').getRange()) { | |
890 | node.remove(true); | |
891 | } | |
892 | ||
893 | let records = value.map(parseCalendar); | |
894 | let rootNode = treeStore.getRootNode(); | |
895 | ||
896 | for (let record of records) { | |
897 | rootNode.appendChild(record); | |
898 | } | |
899 | }); | |
900 | ||
901 | realMode.addListener('change', function(field, value) { | |
902 | let data = treeStore.getRootNode().get('data'); | |
903 | treeStore.getRootNode().set('data', { | |
904 | ...data, | |
905 | value, | |
906 | }); | |
907 | }); | |
908 | ||
909 | realInvertMatch.addListener('change', function(field, value) { | |
910 | let data = treeStore.getRootNode().get('data'); | |
911 | treeStore.getRootNode().set('data', { | |
912 | ...data, | |
913 | invert: value, | |
914 | }); | |
915 | }); | |
916 | ||
917 | treeStore.addListener('datachanged', storeChanged); | |
918 | ||
919 | let treePanel = Ext.create({ | |
920 | xtype: 'treepanel', | |
921 | store: treeStore, | |
922 | minHeight: 300, | |
923 | maxHeight: 300, | |
924 | scrollable: true, | |
925 | ||
926 | bind: { | |
927 | selection: '{selectedRecord}', | |
928 | }, | |
929 | }); | |
930 | ||
931 | let addNode = function() { | |
932 | let node = { | |
933 | type: 'match-field', | |
934 | data: { | |
935 | type: 'exact', | |
936 | field: '', | |
937 | value: '', | |
938 | }, | |
939 | leaf: true, | |
940 | }; | |
941 | treeStore.getRootNode().appendChild(node); | |
942 | treePanel.setSelection(treeStore.getRootNode().lastChild); | |
943 | }; | |
944 | ||
945 | let deleteNode = function() { | |
946 | let selection = treePanel.getSelection(); | |
947 | for (let selected of selection) { | |
948 | if (!selected.isRoot()) { | |
949 | selected.remove(true); | |
950 | } | |
951 | } | |
952 | }; | |
953 | ||
954 | Ext.apply(me, { | |
955 | items: [ | |
956 | realMatchFields, | |
957 | realMode, | |
958 | realMatchSeverity, | |
959 | realInvertMatch, | |
960 | realMatchCalendar, | |
961 | treePanel, | |
962 | { | |
963 | xtype: 'button', | |
964 | margin: '5 5 5 0', | |
965 | text: gettext('Add'), | |
966 | iconCls: 'fa fa-plus-circle', | |
967 | handler: addNode, | |
968 | }, | |
969 | { | |
970 | xtype: 'button', | |
971 | margin: '5 5 5 0', | |
972 | text: gettext('Remove'), | |
973 | iconCls: 'fa fa-minus-circle', | |
974 | handler: deleteNode, | |
975 | }, | |
976 | ], | |
977 | }); | |
978 | me.callParent(); | |
979 | }, | |
980 | }); | |
981 | ||
982 | Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { | |
983 | extend: 'Ext.panel.Panel', | |
984 | xtype: 'pmxNotificationMatchRuleSettings', | |
985 | border: false, | |
986 | ||
987 | items: [ | |
988 | { | |
989 | xtype: 'proxmoxKVComboBox', | |
990 | name: 'mode', | |
991 | fieldLabel: gettext('Match if'), | |
992 | allowBlank: false, | |
993 | isFormField: false, | |
994 | ||
1f8bfa3b DC |
995 | matchFieldWidth: false, |
996 | ||
03a54ebd LW |
997 | comboItems: [ |
998 | ['all', gettext('All rules match')], | |
999 | ['any', gettext('Any rule matches')], | |
1f8bfa3b DC |
1000 | ['notall', gettext('At least one rule does not match')], |
1001 | ['notany', gettext('No rule matches')], | |
03a54ebd LW |
1002 | ], |
1003 | bind: { | |
1004 | hidden: '{!showMatchingMode}', | |
1005 | disabled: '{!showMatchingMode}', | |
1006 | value: '{rootMode}', | |
1007 | }, | |
1008 | }, | |
03a54ebd LW |
1009 | { |
1010 | xtype: 'proxmoxKVComboBox', | |
1011 | fieldLabel: gettext('Node type'), | |
1012 | isFormField: false, | |
1013 | allowBlank: false, | |
1014 | ||
1015 | bind: { | |
1016 | value: '{nodeType}', | |
1017 | hidden: '{!showMatcherType}', | |
1018 | disabled: '{!showMatcherType}', | |
1019 | }, | |
1020 | ||
1021 | comboItems: [ | |
1022 | ['match-field', gettext('Match Field')], | |
1023 | ['match-severity', gettext('Match Severity')], | |
1024 | ['match-calendar', gettext('Match Calendar')], | |
1025 | ], | |
1026 | }, | |
1027 | { | |
1028 | fieldLabel: 'Match Type', | |
1029 | xtype: 'proxmoxKVComboBox', | |
1030 | reference: 'type', | |
1031 | isFormField: false, | |
1032 | allowBlank: false, | |
1033 | submitValue: false, | |
be532951 | 1034 | field: 'type', |
03a54ebd LW |
1035 | |
1036 | bind: { | |
1037 | hidden: '{!typeIsMatchField}', | |
1038 | disabled: '{!typeIsMatchField}', | |
1039 | value: '{matchFieldType}', | |
1040 | }, | |
1041 | ||
1042 | comboItems: [ | |
1043 | ['exact', gettext('Exact')], | |
1044 | ['regex', gettext('Regex')], | |
1045 | ], | |
1046 | }, | |
1047 | { | |
1048 | fieldLabel: gettext('Field'), | |
de0cec40 | 1049 | xtype: 'proxmoxKVComboBox', |
03a54ebd LW |
1050 | isFormField: false, |
1051 | submitValue: false, | |
de0cec40 LW |
1052 | allowBlank: false, |
1053 | editable: true, | |
1054 | displayField: 'key', | |
be532951 | 1055 | field: 'field', |
03a54ebd LW |
1056 | bind: { |
1057 | hidden: '{!typeIsMatchField}', | |
1058 | disabled: '{!typeIsMatchField}', | |
1059 | value: '{matchFieldField}', | |
1060 | }, | |
de0cec40 LW |
1061 | // TODO: Once we have a 'notification registry', we should |
1062 | // retrive those via an API call. | |
1063 | comboItems: [ | |
1064 | ['type', ''], | |
1065 | ['hostname', ''], | |
1066 | ], | |
03a54ebd LW |
1067 | }, |
1068 | { | |
1069 | fieldLabel: gettext('Value'), | |
1070 | xtype: 'textfield', | |
1071 | isFormField: false, | |
1072 | submitValue: false, | |
1073 | allowBlank: false, | |
be532951 | 1074 | field: 'value', |
03a54ebd LW |
1075 | bind: { |
1076 | hidden: '{!typeIsMatchField}', | |
1077 | disabled: '{!typeIsMatchField}', | |
1078 | value: '{matchFieldValue}', | |
1079 | }, | |
1080 | }, | |
1081 | { | |
1082 | xtype: 'proxmoxKVComboBox', | |
1083 | fieldLabel: gettext('Severities to match'), | |
1084 | isFormField: false, | |
1085 | allowBlank: true, | |
1086 | multiSelect: true, | |
be532951 | 1087 | field: 'value', |
03a54ebd LW |
1088 | |
1089 | bind: { | |
1090 | value: '{matchSeverityValue}', | |
1091 | hidden: '{!typeIsMatchSeverity}', | |
1092 | disabled: '{!typeIsMatchSeverity}', | |
1093 | }, | |
1094 | ||
1095 | comboItems: [ | |
1096 | ['info', gettext('Info')], | |
1097 | ['notice', gettext('Notice')], | |
1098 | ['warning', gettext('Warning')], | |
1099 | ['error', gettext('Error')], | |
6ed92b73 | 1100 | ['unknown', gettext('Unknown')], |
03a54ebd LW |
1101 | ], |
1102 | }, | |
1103 | { | |
1104 | xtype: 'proxmoxKVComboBox', | |
1105 | fieldLabel: gettext('Timespan to match'), | |
1106 | isFormField: false, | |
1107 | allowBlank: false, | |
1108 | editable: true, | |
1109 | displayField: 'key', | |
be532951 | 1110 | field: 'value', |
03a54ebd LW |
1111 | |
1112 | bind: { | |
1113 | value: '{matchCalendarValue}', | |
1114 | hidden: '{!typeIsMatchCalendar}', | |
1115 | disabled: '{!typeIsMatchCalender}', | |
1116 | }, | |
1117 | ||
1118 | comboItems: [ | |
1119 | ['mon 8-12', ''], | |
1120 | ['tue..fri,sun 0:00-23:59', ''], | |
1121 | ], | |
1122 | }, | |
1123 | ], | |
1124 | }); |