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