]> git.proxmox.com Git - pve-manager.git/blame - www/manager/grid/FirewallRules.js
add htmlEncode to various comment/description fields
[pve-manager.git] / www / manager / grid / FirewallRules.js
CommitLineData
434f2466
DM
1Ext.define('PVE.form.FWMacroSelector', {
2 extend: 'PVE.form.ComboGrid',
3 alias: 'widget.pveFWMacroSelector',
4
5 initComponent: function() {
6 var me = this;
7
8 var store = Ext.create('Ext.data.Store', {
9 autoLoad: true,
10 fields: [ 'macro', 'descr' ],
11 idProperty: 'macro',
12 proxy: {
13 type: 'pve',
14 url: "/api2/json/cluster/firewall/macros"
15 },
16 sorters: {
17 property: 'macro',
18 order: 'DESC'
19 }
20 });
21
22 Ext.apply(me, {
23 store: store,
24 allowBlank: true,
25 autoSelect: false,
26 valueField: 'macro',
27 displayField: 'macro',
28 listConfig: {
29 columns: [
30 {
31 header: gettext('Macro'),
32 dataIndex: 'macro',
33 hideable: false,
34 width: 100
35 },
36 {
37 header: gettext('Description'),
38 flex: 1,
5171a299 39 renderer: Ext.String.htmlEncode,
434f2466
DM
40 dataIndex: 'descr'
41 }
42 ]
43 }
44 });
45
46 me.callParent();
47 }
48});
49
50Ext.define('PVE.FirewallRulePanel', {
51 extend: 'PVE.panel.InputPanel',
52
53 allow_iface: false,
54
02a7f63b 55 list_refs_url: undefined,
3e6177d9 56
060b19b6
DM
57 onGetValues: function(values) {
58 var me = this;
59
60 // hack: editable ComboGrid returns nothing when empty, so we need to set ''
0757b9c2 61 // Also, disabled text fields return nothing, so we need to set ''
060b19b6 62
0757b9c2
DM
63 Ext.Array.each(['source', 'dest', 'proto', 'sport', 'dport'], function(key) {
64 if (values[key] === undefined) {
65 values[key] = '';
66 }
67 });
060b19b6 68
1200d656
DM
69 delete values.modified_marker;
70
060b19b6
DM
71 return values;
72 },
73
434f2466
DM
74 initComponent : function() {
75 var me = this;
76
02a7f63b
DM
77 if (!me.list_refs_url) {
78 throw "no list_refs_url specified";
3e6177d9
DM
79 }
80
434f2466 81 me.column1 = [
1200d656
DM
82 {
83 // hack: we use this field to mark the form 'dirty' when the
84 // record has errors- so that the user can safe the unmodified
85 // form again.
86 xtype: 'hiddenfield',
87 name: 'modified_marker',
88 value: '',
89 },
434f2466
DM
90 {
91 xtype: 'pveKVComboBox',
92 name: 'type',
93 value: 'in',
94 data: [['in', 'in'], ['out', 'out']],
95 fieldLabel: gettext('Direction'),
96 allowBlank: false
97 },
98 {
99 xtype: 'pveKVComboBox',
100 name: 'action',
101 value: 'ACCEPT',
102 data: [['ACCEPT', 'ACCEPT'], ['DROP', 'DROP'], ['REJECT', 'REJECT']],
103 fieldLabel: gettext('Action'),
104 allowBlank: false
f6f18eed
AD
105 }
106 ];
107
108 if (me.allow_iface) {
109 me.column1.push({
110 xtype: 'pvetextfield',
111 name: 'iface',
434f2466 112 deleteEmpty: !me.create,
f6f18eed
AD
113 value: '',
114 fieldLabel: gettext('Interface')
115 });
116 } else {
117 me.column1.push({
118 xtype: 'displayfield',
119 fieldLabel: '',
120 height: 22, // hack: set same height as text fields
121 value: ''
122 });
123 }
124
125 me.column1.push([
434f2466
DM
126 {
127 xtype: 'displayfield',
128 fieldLabel: '',
129 height: 7,
130 value: ''
131 },
132 {
02a7f63b 133 xtype: 'pveIPRefSelector',
434f2466
DM
134 name: 'source',
135 autoSelect: false,
136 editable: true,
02a7f63b 137 base_url: me.list_refs_url,
434f2466 138 value: '',
5a0a1cc4
DM
139 fieldLabel: gettext('Source')
140
434f2466
DM
141 },
142 {
02a7f63b 143 xtype: 'pveIPRefSelector',
434f2466
DM
144 name: 'dest',
145 autoSelect: false,
434f2466 146 editable: true,
02a7f63b 147 base_url: me.list_refs_url,
434f2466 148 value: '',
5a0a1cc4 149 fieldLabel: gettext('Destination')
434f2466 150 }
f6f18eed 151 ]);
434f2466 152
0757b9c2 153
434f2466
DM
154 me.column2 = [
155 {
156 xtype: 'pvecheckbox',
157 name: 'enable',
158 checked: false,
159 height: 22, // hack: set same height as text fields
160 uncheckedValue: 0,
161 fieldLabel: gettext('Enable')
162 },
163 {
164 xtype: 'pveFWMacroSelector',
165 name: 'macro',
166 value: '',
434f2466 167 fieldLabel: gettext('Macro'),
d86d9aab
AD
168 allowBlank: true,
169 listeners: {
170 change: function(f, value) {
171 if (value === '') {
172 me.down('field[name=proto]').setDisabled(false);
173 me.down('field[name=sport]').setDisabled(false);
174 me.down('field[name=dport]').setDisabled(false);
175 } else {
176 me.down('field[name=proto]').setDisabled(true);
0757b9c2 177 me.down('field[name=proto]').setValue('');
d86d9aab 178 me.down('field[name=sport]').setDisabled(true);
0757b9c2 179 me.down('field[name=sport]').setValue('');
d86d9aab 180 me.down('field[name=dport]').setDisabled(true);
0757b9c2
DM
181 me.down('field[name=dport]').setValue('');
182 }
d86d9aab
AD
183 }
184 }
f6f18eed
AD
185 },
186 {
5491c98f 187 xtype: 'pveIPProtocolSelector',
f6f18eed 188 name: 'proto',
060b19b6 189 autoSelect: false,
060b19b6 190 editable: true,
434f2466 191 value: '',
060b19b6 192 fieldLabel: gettext('Protocol')
f6f18eed 193 },
434f2466
DM
194 {
195 xtype: 'displayfield',
196 fieldLabel: '',
197 height: 7,
198 value: ''
199 },
200 {
5491c98f 201 xtype: 'textfield',
434f2466
DM
202 name: 'sport',
203 value: '',
204 fieldLabel: gettext('Source port')
205 },
206 {
5491c98f 207 xtype: 'textfield',
434f2466
DM
208 name: 'dport',
209 height: 22, // hack: set same height as text fields
210 value: '',
211 fieldLabel: gettext('Dest. port')
212 }
f6f18eed 213 ];
434f2466
DM
214
215 me.columnB = [
216 {
217 xtype: 'textfield',
218 name: 'comment',
219 value: '',
220 fieldLabel: gettext('Comment')
221 }
222 ];
223
224 me.callParent();
225 }
226});
227
228Ext.define('PVE.FirewallRuleEdit', {
229 extend: 'PVE.window.Edit',
230
231 base_url: undefined,
02a7f63b 232 list_refs_url: undefined,
434f2466
DM
233
234 allow_iface: false,
235
236 initComponent : function() {
237 /*jslint confusion: true */
238 var me = this;
239
3e6177d9
DM
240 if (!me.base_url) {
241 throw "no base_url specified";
242 }
02a7f63b
DM
243 if (!me.list_refs_url) {
244 throw "no list_refs_url specified";
3e6177d9
DM
245 }
246
434f2466
DM
247 me.create = (me.rule_pos === undefined);
248
249 if (me.create) {
250 me.url = '/api2/extjs' + me.base_url;
251 me.method = 'POST';
252 } else {
253 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
254 me.method = 'PUT';
255 }
256
257 var ipanel = Ext.create('PVE.FirewallRulePanel', {
258 create: me.create,
02a7f63b 259 list_refs_url: me.list_refs_url,
434f2466
DM
260 allow_iface: me.allow_iface,
261 rule_pos: me.rule_pos
262 });
263
264 Ext.apply(me, {
265 subject: gettext('Rule'),
266 isAdd: true,
267 items: [ ipanel ]
268 });
269
270 me.callParent();
271
272 if (!me.create) {
273 me.load({
5a0a1cc4 274 success: function(response, options) {
434f2466
DM
275 var values = response.result.data;
276 ipanel.setValues(values);
1200d656 277 if (values.errors) {
5a0a1cc4
DM
278 var field = me.query('[isFormField][name=modified_marker]')[0];
279 field.setValue(1);
1200d656
DM
280 Ext.Function.defer(function() {
281 var form = ipanel.up('form').getForm();
282 form.markInvalid(values.errors)
283 }, 100);
284 }
434f2466
DM
285 }
286 });
287 }
288 }
289});
290
291Ext.define('PVE.FirewallGroupRuleEdit', {
292 extend: 'PVE.window.Edit',
293
294 base_url: undefined,
295
296 allow_iface: false,
297
298 initComponent : function() {
299 /*jslint confusion: true */
300 var me = this;
301
302 me.create = (me.rule_pos === undefined);
303
304 if (me.create) {
305 me.url = '/api2/extjs' + me.base_url;
306 me.method = 'POST';
307 } else {
308 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
309 me.method = 'PUT';
310 }
311
312 var column1 = [
313 {
314 xtype: 'hiddenfield',
315 name: 'type',
316 value: 'group'
317 },
318 {
319 xtype: 'pveSecurityGroupsSelector',
320 name: 'action',
321 value: '',
322 fieldLabel: gettext('Security Group'),
323 allowBlank: false
324 }
325 ];
326
327 if (me.allow_iface) {
328 column1.push({
329 xtype: 'pvetextfield',
330 name: 'iface',
331 deleteEmpty: !me.create,
332 value: '',
333 fieldLabel: gettext('Interface')
334 });
335 }
336
337 var ipanel = Ext.create('PVE.panel.InputPanel', {
338 create: me.create,
339 column1: column1,
340 column2: [
341 {
342 xtype: 'pvecheckbox',
343 name: 'enable',
344 checked: false,
345 height: 22, // hack: set same height as text fields
346 uncheckedValue: 0,
347 fieldLabel: gettext('Enable')
348 }
349 ],
350 columnB: [
351 {
352 xtype: 'textfield',
353 name: 'comment',
354 value: '',
355 fieldLabel: gettext('Comment')
356 }
357 ]
358 });
359
360 Ext.apply(me, {
361 subject: gettext('Rule'),
362 isAdd: true,
363 items: [ ipanel ]
364 });
365
366 me.callParent();
367
368 if (!me.create) {
369 me.load({
370 success: function(response, options) {
371 var values = response.result.data;
372 ipanel.setValues(values);
373 }
374 });
375 }
376 }
377});
378
379Ext.define('PVE.FirewallRules', {
380 extend: 'Ext.grid.Panel',
381 alias: 'widget.pveFirewallRules',
382
383 base_url: undefined,
02a7f63b 384 list_refs_url: undefined,
434f2466
DM
385
386 addBtn: undefined,
387 removeBtn: undefined,
388 editBtn: undefined,
389 groupBtn: undefined,
390
391 tbar_prefix: undefined,
392
393 allow_groups: true,
394 allow_iface: false,
395
396 setBaseUrl: function(url) {
397 var me = this;
398
399 me.base_url = url;
400
401 if (url === undefined) {
402 me.addBtn.setDisabled(true);
403 if (me.groupBtn) {
404 me.groupBtn.setDisabled(true);
405 }
406 me.store.removeAll();
407 } else {
408 me.addBtn.setDisabled(false);
409 if (me.groupBtn) {
410 me.groupBtn.setDisabled(false);
411 }
412 me.store.setProxy({
413 type: 'pve',
414 url: '/api2/json' + url
415 });
416
417 me.store.load();
418 }
419 },
420
421 moveRule: function(from, to) {
422 var me = this;
423
424 if (!me.base_url) {
425 return;
426 }
427
428 PVE.Utils.API2Request({
429 url: me.base_url + "/" + from,
430 method: 'PUT',
431 params: { moveto: to },
432 waitMsgTarget: me,
433 failure: function(response, options) {
434 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
435 },
436 callback: function() {
437 me.store.load();
438 }
439 });
440 },
441
5491c98f 442 updateRule: function(rule) {
434f2466
DM
443 var me = this;
444
445 if (!me.base_url) {
446 return;
447 }
448
449 rule.enable = rule.enable ? 1 : 0;
450
451 var pos = rule.pos;
452 delete rule.pos;
b63d3c41 453 delete rule.errors;
434f2466
DM
454
455 PVE.Utils.API2Request({
456 url: me.base_url + '/' + pos.toString(),
457 method: 'PUT',
458 params: rule,
459 waitMsgTarget: me,
460 failure: function(response, options) {
b63d3c41 461 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
434f2466
DM
462 },
463 callback: function() {
464 me.store.load();
465 }
466 });
467 },
468
469 deleteRule: function(rule) {
470 var me = this;
471
472 if (!me.base_url) {
473 return;
474 }
475
476 PVE.Utils.API2Request({
477 url: me.base_url + '/' + rule.pos.toString() +
478 '?digest=' + encodeURIComponent(rule.digest),
479 method: 'DELETE',
480 waitMsgTarget: me,
481 failure: function(response, options) {
482 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
483 },
484 callback: function() {
485 me.store.load();
486 }
487 });
488 },
489
490 initComponent: function() {
491 /*jslint confusion: true */
492 var me = this;
493
02a7f63b
DM
494 if (!me.list_refs_url) {
495 throw "no list_refs_url specified";
3e6177d9
DM
496 }
497
434f2466
DM
498 var store = new Ext.data.Store({
499 model: 'pve-fw-rule'
500 });
501
502 var reload = function() {
503 store.load();
504 };
505
506 var sm = Ext.create('Ext.selection.RowModel', {});
507
508 var run_editor = function() {
509 var rec = sm.getSelection()[0];
510 if (!rec) {
511 return;
512 }
513 var type = rec.data.type;
514
515 var editor;
516 if (type === 'in' || type === 'out') {
517 editor = 'PVE.FirewallRuleEdit';
518 } else if (type === 'group') {
519 editor = 'PVE.FirewallGroupRuleEdit';
520 } else {
521 return;
522 }
523
524 var win = Ext.create(editor, {
525 digest: rec.data.digest,
526 allow_iface: me.allow_iface,
527 base_url: me.base_url,
02a7f63b 528 list_refs_url: me.list_refs_url,
434f2466
DM
529 rule_pos: rec.data.pos
530 });
531
532 win.show();
533 win.on('destroy', reload);
534 };
535
536 me.editBtn = new PVE.button.Button({
537 text: gettext('Edit'),
538 disabled: true,
539 selModel: sm,
540 handler: run_editor
541 });
542
543 me.addBtn = Ext.create('Ext.Button', {
544 text: gettext('Add'),
545 disabled: true,
546 handler: function() {
547 var win = Ext.create('PVE.FirewallRuleEdit', {
548 allow_iface: me.allow_iface,
3e6177d9 549 base_url: me.base_url,
02a7f63b 550 list_refs_url: me.list_refs_url
434f2466
DM
551 });
552 win.on('destroy', reload);
553 win.show();
554 }
555 });
556
557 if (me.allow_groups) {
558 me.groupBtn = Ext.create('Ext.Button', {
5cc4d7de
DM
559 text: gettext('Insert') + ': ' +
560 gettext('Security Group'),
434f2466
DM
561 disabled: true,
562 handler: function() {
563 var win = Ext.create('PVE.FirewallGroupRuleEdit', {
564 allow_iface: me.allow_iface,
565 base_url: me.base_url
566 });
567 win.on('destroy', reload);
568 win.show();
569 }
570 });
571 }
572
573 me.removeBtn = new PVE.button.Button({
574 text: gettext('Remove'),
575 selModel: sm,
576 disabled: true,
577 handler: function() {
578 var rec = sm.getSelection()[0];
579 if (!rec) {
580 return;
581 }
582 me.deleteRule(rec.data);
583 }
584 });
585
586 var tbar = me.tbar_prefix ? [ me.tbar_prefix ] : [];
587 tbar.push(me.addBtn);
588 if (me.groupBtn) {
589 tbar.push(me.groupBtn);
590 }
591 tbar.push([ me.removeBtn, me.editBtn ]);
592
8f417489
DM
593 var render_errors = function(name, value, metaData, record) {
594 var errors = record.data.errors;
595 if (errors && errors[name]) {
596 metaData.tdCls = 'x-form-invalid-field';
597 var html = '<p>' + Ext.htmlEncode(errors[name]) + '</p>';
598 metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' +
599 html.replace(/\"/g,'&quot;') + '"';
600 }
601 return value;
602 };
603
434f2466
DM
604 var columns = [
605 {
606 // similar to xtype: 'rownumberer',
607 dataIndex: 'pos',
608 resizable: false,
609 width: 23,
610 sortable: false,
611 align: 'right',
612 hideable: false,
613 menuDisabled: true,
614 renderer: function(value, metaData, record, rowIdx, colIdx, store) {
615 metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
616 if (value >= 0) {
617 return value;
618 }
619 return '';
620 }
621 },
622 {
623 xtype: 'checkcolumn',
624 header: gettext('Enable'),
625 dataIndex: 'enable',
626 listeners: {
627 checkchange: function(column, record, checked) {
628 record.commit();
629 var data = {};
630 record.fields.each(function(field) {
631 data[field.name] = record.get(field.name);
632 });
633 if (!me.allow_iface || !data.iface) {
634 delete data.iface;
635 }
5491c98f 636 me.updateRule(data);
434f2466
DM
637 }
638 },
639 width: 50
640 },
641 {
642 header: gettext('Type'),
643 dataIndex: 'type',
8f417489
DM
644 renderer: function(value, metaData, record) {
645 return render_errors('type', value, metaData, record);
646 },
434f2466
DM
647 width: 50
648 },
649 {
650 header: gettext('Action'),
651 dataIndex: 'action',
8f417489
DM
652 renderer: function(value, metaData, record) {
653 return render_errors('action', value, metaData, record);
654 },
434f2466
DM
655 width: 80
656 },
657 {
658 header: gettext('Macro'),
659 dataIndex: 'macro',
8f417489
DM
660 renderer: function(value, metaData, record) {
661 return render_errors('macro', value, metaData, record);
662 },
434f2466
DM
663 width: 80
664 }
665 ];
666
667 if (me.allow_iface) {
668 columns.push({
669 header: gettext('Interface'),
670 dataIndex: 'iface',
8f417489
DM
671 renderer: function(value, metaData, record) {
672 return render_errors('iface', value, metaData, record);
673 },
434f2466
DM
674 width: 80
675 });
676 }
677
678 columns.push([
679 {
680 header: gettext('Source'),
681 dataIndex: 'source',
8f417489
DM
682 renderer: function(value, metaData, record) {
683 return render_errors('source', value, metaData, record);
684 },
434f2466
DM
685 width: 100
686 },
687 {
688 header: gettext('Destination'),
689 dataIndex: 'dest',
8f417489
DM
690 renderer: function(value, metaData, record) {
691 return render_errors('dest', value, metaData, record);
692 },
434f2466
DM
693 width: 100
694 },
695 {
696 header: gettext('Protocol'),
697 dataIndex: 'proto',
8f417489
DM
698 renderer: function(value, metaData, record) {
699 return render_errors('proto', value, metaData, record);
700 },
434f2466
DM
701 width: 100
702 },
703 {
704 header: gettext('Dest. port'),
705 dataIndex: 'dport',
8f417489
DM
706 renderer: function(value, metaData, record) {
707 return render_errors('dport', value, metaData, record);
708 },
434f2466
DM
709 width: 100
710 },
711 {
712 header: gettext('Source port'),
713 dataIndex: 'sport',
8f417489
DM
714 renderer: function(value, metaData, record) {
715 return render_errors('sport', value, metaData, record);
716 },
434f2466
DM
717 width: 100
718 },
719 {
720 header: gettext('Comment'),
721 dataIndex: 'comment',
722 flex: 1,
8f417489
DM
723 renderer: function(value, metaData, record) {
724 return render_errors('comment', Ext.util.Format.htmlEncode(value), metaData, record);
434f2466
DM
725 }
726 }
727 ]);
728
729 Ext.apply(me, {
730 store: store,
731 selModel: sm,
732 tbar: tbar,
733 viewConfig: {
734 plugins: [
735 {
736 ptype: 'gridviewdragdrop',
737 dragGroup: 'FWRuleDDGroup',
738 dropGroup: 'FWRuleDDGroup'
739 }
740 ],
741 listeners: {
742 beforedrop: function(node, data, dropRec, dropPosition) {
743 if (!dropRec) {
744 return false; // empty view
745 }
746 var moveto = dropRec.get('pos');
747 if (dropPosition === 'after') {
748 moveto++;
749 }
750 var pos = data.records[0].get('pos');
751 me.moveRule(pos, moveto);
752 return 0;
753 },
754 itemdblclick: run_editor
755 }
756 },
1d921423 757 sortableColumns: false,
434f2466
DM
758 columns: columns
759 });
760
761 me.callParent();
762
763 if (me.base_url) {
764 me.setBaseUrl(me.base_url); // load
765 }
766 }
767}, function() {
768
769 Ext.define('pve-fw-rule', {
770 extend: 'Ext.data.Model',
771 fields: [ { name: 'enable', type: 'boolean' },
772 'type', 'action', 'macro', 'source', 'dest', 'proto', 'iface',
8f417489 773 'dport', 'sport', 'comment', 'pos', 'digest', 'errors' ],
434f2466
DM
774 idProperty: 'pos'
775 });
776
777});