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