]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/grid/FirewallRules.js
ui: firewall: refactor privilege checks and prevent double click
[pve-manager.git] / www / manager6 / grid / FirewallRules.js
CommitLineData
435cce27 1Ext.define('PVE.form.FWMacroSelector', {
0fc95a12 2 extend: 'Proxmox.form.ComboGrid',
435cce27 3 alias: 'widget.pveFWMacroSelector',
3d990919
EK
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,
f6710aac 14 width: 100,
3d990919
EK
15 },
16 {
17 header: gettext('Description'),
91535f2b 18 renderer: Ext.String.htmlEncode,
3d990919 19 flex: 1,
f6710aac
TL
20 dataIndex: 'descr',
21 },
22 ],
3d990919 23 },
435cce27
DM
24 initComponent: function() {
25 var me = this;
26
27 var store = Ext.create('Ext.data.Store', {
28 autoLoad: true,
8058410f 29 fields: ['macro', 'descr'],
435cce27
DM
30 idProperty: 'macro',
31 proxy: {
56a353b9 32 type: 'proxmox',
f6710aac 33 url: "/api2/json/cluster/firewall/macros",
435cce27
DM
34 },
35 sorters: {
36 property: 'macro',
392e3cf1 37 direction: 'ASC',
f6710aac 38 },
435cce27
DM
39 });
40
41 Ext.apply(me, {
f6710aac 42 store: store,
435cce27
DM
43 });
44
45 me.callParent();
f6710aac 46 },
435cce27
DM
47});
48
d09a68e4
ML
49Ext.define('PVE.form.ICMPTypeSelector', {
50 extend: 'Proxmox.form.ComboGrid',
51 alias: 'widget.pveICMPTypeSelector',
52 allowBlank: true,
53 autoSelect: false,
54 valueField: 'name',
55 displayField: 'name',
56 listConfig: {
57 columns: [
58 {
59 header: gettext('Type'),
60 dataIndex: 'type',
61 hideable: false,
62 sortable: false,
63 width: 50,
64 },
65 {
66 header: gettext('Name'),
67 dataIndex: 'name',
68 hideable: false,
69 sortable: false,
70 flex: 1,
71 },
72 ],
73 },
74 setName: function(value) {
75 this.name = value;
9882c956 76 },
d09a68e4
ML
77});
78
79let ICMP_TYPE_NAMES_STORE = Ext.create('Ext.data.Store', {
80 field: ['type', 'name'],
81 data: [
82 { type: 'any', name: 'any' },
83 { type: '0', name: 'echo-reply' },
84 { type: '3', name: 'destination-unreachable' },
85 { type: '3/0', name: 'network-unreachable' },
86 { type: '3/1', name: 'host-unreachable' },
87 { type: '3/2', name: 'protocol-unreachable' },
88 { type: '3/3', name: 'port-unreachable' },
89 { type: '3/4', name: 'fragmentation-needed' },
90 { type: '3/5', name: 'source-route-failed' },
91 { type: '3/6', name: 'network-unknown' },
92 { type: '3/7', name: 'host-unknown' },
93 { type: '3/9', name: 'network-prohibited' },
94 { type: '3/10', name: 'host-prohibited' },
95 { type: '3/11', name: 'TOS-network-unreachable' },
96 { type: '3/12', name: 'TOS-host-unreachable' },
97 { type: '3/13', name: 'communication-prohibited' },
98 { type: '3/14', name: 'host-precedence-violation' },
99 { type: '3/15', name: 'precedence-cutoff' },
100 { type: '4', name: 'source-quench' },
101 { type: '5', name: 'redirect' },
102 { type: '5/0', name: 'network-redirect' },
103 { type: '5/1', name: 'host-redirect' },
104 { type: '5/2', name: 'TOS-network-redirect' },
105 { type: '5/3', name: 'TOS-host-redirect' },
106 { type: '8', name: 'echo-request' },
107 { type: '9', name: 'router-advertisement' },
108 { type: '10', name: 'router-solicitation' },
109 { type: '11', name: 'time-exceeded' },
110 { type: '11/0', name: 'ttl-zero-during-transit' },
111 { type: '11/1', name: 'ttl-zero-during-reassembly' },
112 { type: '12', name: 'parameter-problem' },
113 { type: '12/0', name: 'ip-header-bad' },
114 { type: '12/1', name: 'required-option-missing' },
115 { type: '13', name: 'timestamp-request' },
116 { type: '14', name: 'timestamp-reply' },
117 { type: '17', name: 'address-mask-request' },
118 { type: '18', name: 'address-mask-reply' },
119 ],
120});
121let ICMPV6_TYPE_NAMES_STORE = Ext.create('Ext.data.Store', {
122 field: ['type', 'name'],
123 data: [
124 { type: '1', name: 'destination-unreachable' },
125 { type: '1/0', name: 'no-route' },
126 { type: '1/1', name: 'communication-prohibited' },
127 { type: '1/2', name: 'beyond-scope' },
128 { type: '1/3', name: 'address-unreachable' },
129 { type: '1/4', name: 'port-unreachable' },
130 { type: '1/5', name: 'failed-policy' },
9882c956 131 { type: '1/6', name: 'reject-route' },
d09a68e4
ML
132 { type: '2', name: 'packet-too-big' },
133 { type: '3', name: 'time-exceeded' },
134 { type: '3/0', name: 'ttl-zero-during-transit' },
135 { type: '3/1', name: 'ttl-zero-during-reassembly' },
136 { type: '4', name: 'parameter-problem' },
137 { type: '4/0', name: 'bad-header' },
138 { type: '4/1', name: 'unknown-header-type' },
139 { type: '4/2', name: 'unknown-option' },
140 { type: '128', name: 'echo-request' },
141 { type: '129', name: 'echo-reply' },
142 { type: '133', name: 'router-solicitation' },
143 { type: '134', name: 'router-advertisement' },
144 { type: '135', name: 'neighbour-solicitation' },
145 { type: '136', name: 'neighbour-advertisement' },
146 { type: '137', name: 'redirect' },
147 ],
148});
149
435cce27 150Ext.define('PVE.FirewallRulePanel', {
ef4ef788 151 extend: 'Proxmox.panel.InputPanel',
435cce27
DM
152
153 allow_iface: false,
154
155 list_refs_url: undefined,
156
157 onGetValues: function(values) {
158 var me = this;
159
160 // hack: editable ComboGrid returns nothing when empty, so we need to set ''
161 // Also, disabled text fields return nothing, so we need to set ''
162
d09a68e4 163 Ext.Array.each(['source', 'dest', 'macro', 'proto', 'sport', 'dport', 'icmp-type', 'log'], function(key) {
435cce27
DM
164 if (values[key] === undefined) {
165 values[key] = '';
166 }
167 });
168
169 delete values.modified_marker;
2a4971d8 170
435cce27
DM
171 return values;
172 },
173
8058410f 174 initComponent: function() {
435cce27
DM
175 var me = this;
176
177 if (!me.list_refs_url) {
178 throw "no list_refs_url specified";
179 }
180
181 me.column1 = [
182 {
183 // hack: we use this field to mark the form 'dirty' when the
2a4971d8 184 // record has errors- so that the user can safe the unmodified
435cce27
DM
185 // form again.
186 xtype: 'hiddenfield',
187 name: 'modified_marker',
f6710aac 188 value: '',
435cce27
DM
189 },
190 {
09cacce7 191 xtype: 'proxmoxKVComboBox',
435cce27
DM
192 name: 'type',
193 value: 'in',
e7bc7f31 194 comboItems: [['in', 'in'], ['out', 'out']],
435cce27 195 fieldLabel: gettext('Direction'),
f6710aac 196 allowBlank: false,
435cce27
DM
197 },
198 {
09cacce7 199 xtype: 'proxmoxKVComboBox',
435cce27
DM
200 name: 'action',
201 value: 'ACCEPT',
e7bc7f31 202 comboItems: [['ACCEPT', 'ACCEPT'], ['DROP', 'DROP'], ['REJECT', 'REJECT']],
435cce27 203 fieldLabel: gettext('Action'),
f6710aac
TL
204 allowBlank: false,
205 },
435cce27
DM
206 ];
207
208 if (me.allow_iface) {
209 me.column1.push({
dbed4c1c 210 xtype: 'proxmoxtextfield',
435cce27 211 name: 'iface',
d5e771ce 212 deleteEmpty: !me.isCreate,
435cce27 213 value: '',
f6710aac 214 fieldLabel: gettext('Interface'),
435cce27
DM
215 });
216 } else {
217 me.column1.push({
218 xtype: 'displayfield',
219 fieldLabel: '',
f6710aac 220 value: '',
435cce27
DM
221 });
222 }
223
fa94a977 224 me.column1.push(
435cce27
DM
225 {
226 xtype: 'displayfield',
227 fieldLabel: '',
228 height: 7,
f6710aac 229 value: '',
435cce27
DM
230 },
231 {
232 xtype: 'pveIPRefSelector',
233 name: 'source',
234 autoSelect: false,
235 editable: true,
236 base_url: me.list_refs_url,
237 value: '',
f6710aac 238 fieldLabel: gettext('Source'),
1490b5eb
AL
239 maxLength: 512,
240 maxLengthText: gettext('Too long, consider using IP sets.'),
435cce27
DM
241 },
242 {
243 xtype: 'pveIPRefSelector',
244 name: 'dest',
245 autoSelect: false,
246 editable: true,
247 base_url: me.list_refs_url,
248 value: '',
f6710aac 249 fieldLabel: gettext('Destination'),
1490b5eb
AL
250 maxLength: 512,
251 maxLengthText: gettext('Too long, consider using IP sets.'),
f6710aac 252 },
fa94a977 253 );
435cce27 254
2a4971d8 255
435cce27
DM
256 me.column2 = [
257 {
896c0d50 258 xtype: 'proxmoxcheckbox',
435cce27
DM
259 name: 'enable',
260 checked: false,
435cce27 261 uncheckedValue: 0,
f6710aac 262 fieldLabel: gettext('Enable'),
435cce27
DM
263 },
264 {
265 xtype: 'pveFWMacroSelector',
266 name: 'macro',
435cce27 267 fieldLabel: gettext('Macro'),
1fafdce8 268 editable: true,
435cce27
DM
269 allowBlank: true,
270 listeners: {
271 change: function(f, value) {
1fafdce8 272 if (value === null) {
435cce27
DM
273 me.down('field[name=proto]').setDisabled(false);
274 me.down('field[name=sport]').setDisabled(false);
275 me.down('field[name=dport]').setDisabled(false);
276 } else {
277 me.down('field[name=proto]').setDisabled(true);
278 me.down('field[name=proto]').setValue('');
279 me.down('field[name=sport]').setDisabled(true);
280 me.down('field[name=sport]').setValue('');
281 me.down('field[name=dport]').setDisabled(true);
7c7ae44f 282 me.down('field[name=dport]').setValue('');
435cce27 283 }
f6710aac
TL
284 },
285 },
435cce27
DM
286 },
287 {
288 xtype: 'pveIPProtocolSelector',
289 name: 'proto',
290 autoSelect: false,
291 editable: true,
292 value: '',
f6710aac 293 fieldLabel: gettext('Protocol'),
d09a68e4
ML
294 listeners: {
295 change: function(f, value) {
296 if (value === 'icmp' || value === 'icmpv6' || value === 'ipv6-icmp') {
297 me.down('field[name=dport]').setHidden(true);
298 me.down('field[name=dport]').setDisabled(true);
299 if (value === 'icmp') {
300 me.down('#icmpv4-type').setHidden(false);
301 me.down('#icmpv4-type').setDisabled(false);
302 me.down('#icmpv6-type').setHidden(true);
303 me.down('#icmpv6-type').setDisabled(true);
304 } else {
305 me.down('#icmpv6-type').setHidden(false);
306 me.down('#icmpv6-type').setDisabled(false);
307 me.down('#icmpv4-type').setHidden(true);
308 me.down('#icmpv4-type').setDisabled(true);
309 }
310 } else {
311 me.down('#icmpv4-type').setHidden(true);
312 me.down('#icmpv4-type').setDisabled(true);
313 me.down('#icmpv6-type').setHidden(true);
314 me.down('#icmpv6-type').setDisabled(true);
315 me.down('field[name=dport]').setHidden(false);
316 me.down('field[name=dport]').setDisabled(false);
317 }
9882c956 318 },
d09a68e4 319 },
435cce27
DM
320 },
321 {
322 xtype: 'displayfield',
323 fieldLabel: '',
324 height: 7,
f6710aac 325 value: '',
435cce27
DM
326 },
327 {
328 xtype: 'textfield',
329 name: 'sport',
330 value: '',
f6710aac 331 fieldLabel: gettext('Source port'),
435cce27
DM
332 },
333 {
334 xtype: 'textfield',
335 name: 'dport',
435cce27 336 value: '',
f6710aac
TL
337 fieldLabel: gettext('Dest. port'),
338 },
d09a68e4
ML
339 {
340 xtype: 'pveICMPTypeSelector',
341 name: 'icmp-type',
342 id: 'icmpv4-type',
343 autoSelect: false,
344 editable: true,
345 hidden: true,
346 disabled: true,
347 value: '',
348 fieldLabel: gettext('ICMP type'),
349 store: ICMP_TYPE_NAMES_STORE,
350 },
351 {
352 xtype: 'pveICMPTypeSelector',
353 name: 'icmp-type',
354 id: 'icmpv6-type',
355 autoSelect: false,
356 editable: true,
357 hidden: true,
358 disabled: true,
359 value: '',
360 fieldLabel: gettext('ICMP type'),
361 store: ICMPV6_TYPE_NAMES_STORE,
362 },
6db91b0f
TL
363 ];
364
365 me.advancedColumn1 = [
3c37fe48 366 {
f6710aac
TL
367 xtype: 'pveFirewallLogLevels',
368 },
435cce27 369 ];
3c37fe48 370
435cce27
DM
371 me.columnB = [
372 {
373 xtype: 'textfield',
374 name: 'comment',
375 value: '',
f6710aac
TL
376 fieldLabel: gettext('Comment'),
377 },
435cce27
DM
378 ];
379
380 me.callParent();
f6710aac 381 },
435cce27
DM
382});
383
384Ext.define('PVE.FirewallRuleEdit', {
9fccc702 385 extend: 'Proxmox.window.Edit',
435cce27
DM
386
387 base_url: undefined,
388 list_refs_url: undefined,
389
390 allow_iface: false,
391
8058410f 392 initComponent: function() {
435cce27
DM
393 var me = this;
394
395 if (!me.base_url) {
396 throw "no base_url specified";
397 }
398 if (!me.list_refs_url) {
399 throw "no list_refs_url specified";
400 }
401
53e3ea84 402 me.isCreate = me.rule_pos === undefined;
435cce27 403
d5e771ce 404 if (me.isCreate) {
435cce27
DM
405 me.url = '/api2/extjs' + me.base_url;
406 me.method = 'POST';
407 } else {
408 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
409 me.method = 'PUT';
410 }
411
412 var ipanel = Ext.create('PVE.FirewallRulePanel', {
d5e771ce 413 isCreate: me.isCreate,
435cce27
DM
414 list_refs_url: me.list_refs_url,
415 allow_iface: me.allow_iface,
f6710aac 416 rule_pos: me.rule_pos,
435cce27
DM
417 });
418
419 Ext.apply(me, {
420 subject: gettext('Rule'),
421 isAdd: true,
8058410f 422 items: [ipanel],
435cce27
DM
423 });
424
425 me.callParent();
426
d5e771ce 427 if (!me.isCreate) {
435cce27
DM
428 me.load({
429 success: function(response, options) {
430 var values = response.result.data;
431 ipanel.setValues(values);
d09a68e4
ML
432 // set icmp-type again after protocol has been set
433 if (values["icmp-type"] !== undefined) {
9882c956 434 ipanel.setValues({ "icmp-type": values["icmp-type"] });
d09a68e4 435 }
435cce27
DM
436 if (values.errors) {
437 var field = me.query('[isFormField][name=modified_marker]')[0];
438 field.setValue(1);
439 Ext.Function.defer(function() {
440 var form = ipanel.up('form').getForm();
a764c5f7 441 form.markInvalid(values.errors);
435cce27
DM
442 }, 100);
443 }
f6710aac 444 },
435cce27 445 });
330020d2
WL
446 } else if (me.rec) {
447 ipanel.setValues(me.rec.data);
435cce27 448 }
f6710aac 449 },
435cce27
DM
450});
451
452Ext.define('PVE.FirewallGroupRuleEdit', {
9fccc702 453 extend: 'Proxmox.window.Edit',
435cce27
DM
454
455 base_url: undefined,
456
457 allow_iface: false,
458
8058410f 459 initComponent: function() {
435cce27
DM
460 var me = this;
461
53e3ea84 462 me.isCreate = me.rule_pos === undefined;
435cce27 463
d5e771ce 464 if (me.isCreate) {
435cce27
DM
465 me.url = '/api2/extjs' + me.base_url;
466 me.method = 'POST';
467 } else {
468 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
469 me.method = 'PUT';
470 }
471
472 var column1 = [
473 {
474 xtype: 'hiddenfield',
475 name: 'type',
f6710aac 476 value: 'group',
435cce27
DM
477 },
478 {
479 xtype: 'pveSecurityGroupsSelector',
480 name: 'action',
481 value: '',
482 fieldLabel: gettext('Security Group'),
f6710aac
TL
483 allowBlank: false,
484 },
435cce27
DM
485 ];
486
487 if (me.allow_iface) {
488 column1.push({
dbed4c1c 489 xtype: 'proxmoxtextfield',
435cce27 490 name: 'iface',
d5e771ce 491 deleteEmpty: !me.isCreate,
435cce27 492 value: '',
f6710aac 493 fieldLabel: gettext('Interface'),
435cce27
DM
494 });
495 }
496
ef4ef788 497 var ipanel = Ext.create('Proxmox.panel.InputPanel', {
d5e771ce 498 isCreate: me.isCreate,
435cce27
DM
499 column1: column1,
500 column2: [
501 {
896c0d50 502 xtype: 'proxmoxcheckbox',
435cce27
DM
503 name: 'enable',
504 checked: false,
435cce27 505 uncheckedValue: 0,
f6710aac
TL
506 fieldLabel: gettext('Enable'),
507 },
435cce27
DM
508 ],
509 columnB: [
510 {
511 xtype: 'textfield',
512 name: 'comment',
513 value: '',
f6710aac
TL
514 fieldLabel: gettext('Comment'),
515 },
516 ],
435cce27
DM
517 });
518
519 Ext.apply(me, {
520 subject: gettext('Rule'),
521 isAdd: true,
8058410f 522 items: [ipanel],
435cce27
DM
523 });
524
525 me.callParent();
526
d5e771ce 527 if (!me.isCreate) {
435cce27 528 me.load({
8058410f 529 success: function(response, options) {
435cce27
DM
530 var values = response.result.data;
531 ipanel.setValues(values);
f6710aac 532 },
435cce27
DM
533 });
534 }
f6710aac 535 },
435cce27
DM
536});
537
538Ext.define('PVE.FirewallRules', {
539 extend: 'Ext.grid.Panel',
540 alias: 'widget.pveFirewallRules',
541
ba93a9c6
DC
542 onlineHelp: 'chapter_pve_firewall',
543
123e1c80
DC
544 stateful: true,
545 stateId: 'grid-firewall-rules',
546
435cce27
DM
547 base_url: undefined,
548 list_refs_url: undefined,
549
550 addBtn: undefined,
551 removeBtn: undefined,
552 editBtn: undefined,
553 groupBtn: undefined,
554
555 tbar_prefix: undefined,
556
557 allow_groups: true,
558 allow_iface: false,
559
560 setBaseUrl: function(url) {
561 var me = this;
562
563 me.base_url = url;
564
565 if (url === undefined) {
566 me.addBtn.setDisabled(true);
567 if (me.groupBtn) {
568 me.groupBtn.setDisabled(true);
569 }
570 me.store.removeAll();
571 } else {
1056e10c 572 if (me.canEdit) {
2e37e779
AD
573 me.addBtn.setDisabled(false);
574 if (me.groupBtn) {
575 me.groupBtn.setDisabled(false);
576 }
435cce27 577 }
2e37e779
AD
578 me.removeBtn.baseurl = url + '/';
579
435cce27 580 me.store.setProxy({
56a353b9 581 type: 'proxmox',
f6710aac 582 url: '/api2/json' + url,
435cce27
DM
583 });
584
585 me.store.load();
586 }
587 },
588
589 moveRule: function(from, to) {
590 var me = this;
591
2a4971d8 592 if (!me.base_url) {
435cce27
DM
593 return;
594 }
595
e7ade592 596 Proxmox.Utils.API2Request({
435cce27
DM
597 url: me.base_url + "/" + from,
598 method: 'PUT',
599 params: { moveto: to },
600 waitMsgTarget: me,
601 failure: function(response, options) {
602 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
603 },
604 callback: function() {
605 me.store.load();
f6710aac 606 },
435cce27
DM
607 });
608 },
609
610 updateRule: function(rule) {
611 var me = this;
612
2a4971d8 613 if (!me.base_url) {
435cce27
DM
614 return;
615 }
616
617 rule.enable = rule.enable ? 1 : 0;
618
619 var pos = rule.pos;
620 delete rule.pos;
621 delete rule.errors;
622
e7ade592 623 Proxmox.Utils.API2Request({
435cce27
DM
624 url: me.base_url + '/' + pos.toString(),
625 method: 'PUT',
626 params: rule,
627 waitMsgTarget: me,
628 failure: function(response, options) {
629 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
630 },
631 callback: function() {
632 me.store.load();
f6710aac 633 },
435cce27
DM
634 });
635 },
636
435cce27
DM
637
638 initComponent: function() {
435cce27
DM
639 var me = this;
640
641 if (!me.list_refs_url) {
642 throw "no list_refs_url specified";
643 }
644
f6710aac
TL
645 var store = Ext.create('Ext.data.Store', {
646 model: 'pve-fw-rule',
435cce27
DM
647 });
648
649 var reload = function() {
650 store.load();
651 };
652
653 var sm = Ext.create('Ext.selection.RowModel', {});
654
2e37e779 655 me.caps = Ext.state.Manager.get('GuiCap');
1056e10c 656 me.canEdit = !!me.caps.vms['VM.Config.Network'] || !!me.caps.dc['Sys.Modify'] || !!me.caps.nodes['Sys.Modify'];
2e37e779 657
435cce27
DM
658 var run_editor = function() {
659 var rec = sm.getSelection()[0];
1056e10c 660 if (!rec || !me.canEdit) {
435cce27
DM
661 return;
662 }
663 var type = rec.data.type;
664
665 var editor;
666 if (type === 'in' || type === 'out') {
667 editor = 'PVE.FirewallRuleEdit';
668 } else if (type === 'group') {
669 editor = 'PVE.FirewallGroupRuleEdit';
670 } else {
671 return;
672 }
673
674 var win = Ext.create(editor, {
675 digest: rec.data.digest,
676 allow_iface: me.allow_iface,
677 base_url: me.base_url,
678 list_refs_url: me.list_refs_url,
f6710aac 679 rule_pos: rec.data.pos,
435cce27
DM
680 });
681
682 win.show();
683 win.on('destroy', reload);
684 };
685
f6710aac 686 me.editBtn = Ext.create('Proxmox.button.Button', {
435cce27
DM
687 text: gettext('Edit'),
688 disabled: true,
1056e10c 689 enableFn: rec => me.canEdit,
435cce27 690 selModel: sm,
f6710aac 691 handler: run_editor,
435cce27
DM
692 });
693
8058410f 694 me.addBtn = Ext.create('Ext.Button', {
435cce27
DM
695 text: gettext('Add'),
696 disabled: true,
697 handler: function() {
698 var win = Ext.create('PVE.FirewallRuleEdit', {
699 allow_iface: me.allow_iface,
700 base_url: me.base_url,
f6710aac 701 list_refs_url: me.list_refs_url,
435cce27
DM
702 });
703 win.on('destroy', reload);
704 win.show();
f6710aac 705 },
435cce27
DM
706 });
707
330020d2 708 var run_copy_editor = function() {
5e7d2723 709 let rec = sm.getSelection()[0];
330020d2
WL
710 if (!rec) {
711 return;
712 }
5e7d2723 713 let type = rec.data.type;
330020d2
WL
714 if (!(type === 'in' || type === 'out')) {
715 return;
716 }
717
5e7d2723 718 let win = Ext.create('PVE.FirewallRuleEdit', {
330020d2
WL
719 allow_iface: me.allow_iface,
720 base_url: me.base_url,
721 list_refs_url: me.list_refs_url,
f6710aac 722 rec: rec,
330020d2 723 });
330020d2
WL
724 win.show();
725 win.on('destroy', reload);
726 };
727
f6710aac 728 me.copyBtn = Ext.create('Proxmox.button.Button', {
330020d2
WL
729 text: gettext('Copy'),
730 selModel: sm,
1056e10c 731 enableFn: ({ data }) => (data.type === 'in' || data.type === 'out') && me.canEdit,
330020d2 732 disabled: true,
f6710aac 733 handler: run_copy_editor,
330020d2
WL
734 });
735
435cce27 736 if (me.allow_groups) {
8058410f 737 me.groupBtn = Ext.create('Ext.Button', {
2a4971d8 738 text: gettext('Insert') + ': ' +
435cce27
DM
739 gettext('Security Group'),
740 disabled: true,
741 handler: function() {
742 var win = Ext.create('PVE.FirewallGroupRuleEdit', {
743 allow_iface: me.allow_iface,
f6710aac 744 base_url: me.base_url,
435cce27
DM
745 });
746 win.on('destroy', reload);
747 win.show();
f6710aac 748 },
435cce27
DM
749 });
750 }
751
f6710aac 752 me.removeBtn = Ext.create('Proxmox.button.StdRemoveButton', {
1056e10c 753 enableFn: rec => me.canEdit,
435cce27 754 selModel: sm,
3b1ca3ff
DC
755 baseurl: me.base_url + '/',
756 confirmMsg: false,
757 getRecordName: function(rec) {
758 var rule = rec.data;
759 return rule.pos.toString() +
760 '?digest=' + encodeURIComponent(rule.digest);
761 },
762 callback: function() {
763 me.store.load();
f6710aac 764 },
435cce27
DM
765 });
766
5e7d2723 767 let tbar = me.tbar_prefix ? [me.tbar_prefix] : [];
330020d2 768 tbar.push(me.addBtn, me.copyBtn);
435cce27
DM
769 if (me.groupBtn) {
770 tbar.push(me.groupBtn);
771 }
fa94a977 772 tbar.push(me.removeBtn, me.editBtn);
435cce27 773
5e7d2723
TL
774 let render_errors = function(name, value, metaData, record) {
775 let errors = record.data.errors;
435cce27 776 if (errors && errors[name]) {
3ab7e0ec 777 metaData.tdCls = 'proxmox-invalid-row';
5e7d2723
TL
778 let html = '<p>' + Ext.htmlEncode(errors[name]) + '</p>';
779 metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' + html + '"';
435cce27
DM
780 }
781 return value;
782 };
783
5e7d2723 784 let columns = [
435cce27
DM
785 {
786 // similar to xtype: 'rownumberer',
787 dataIndex: 'pos',
788 resizable: false,
4ad65791
DC
789 minWidth: 65,
790 maxWidth: 83,
ac69b28b 791 flex: 1,
435cce27 792 sortable: false,
435cce27
DM
793 hideable: false,
794 menuDisabled: true,
5e7d2723 795 renderer: function(value, metaData, record, rowIdx, colIdx) {
435cce27 796 metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
4ad65791 797 let dragHandle = "<i class='pve-grid-fa fa fa-fw fa-reorder cursor-move'></i>";
435cce27 798 if (value >= 0) {
4ad65791 799 return dragHandle + value;
435cce27 800 }
4ad65791 801 return dragHandle;
f6710aac 802 },
435cce27
DM
803 },
804 {
805 xtype: 'checkcolumn',
97a537de 806 header: gettext('On'),
435cce27
DM
807 dataIndex: 'enable',
808 listeners: {
7a4c3133
EK
809 checkchange: function(column, recordIndex, checked) {
810 var record = me.getStore().getData().items[recordIndex];
435cce27
DM
811 record.commit();
812 var data = {};
7a4c3133 813 Ext.Array.forEach(record.getFields(), function(field) {
435cce27
DM
814 data[field.name] = record.get(field.name);
815 });
816 if (!me.allow_iface || !data.iface) {
817 delete data.iface;
818 }
819 me.updateRule(data);
f6710aac 820 },
435cce27 821 },
97a537de 822 width: 40,
435cce27
DM
823 },
824 {
825 header: gettext('Type'),
826 dataIndex: 'type',
827 renderer: function(value, metaData, record) {
828 return render_errors('type', value, metaData, record);
829 },
33bf9791
TL
830 minWidth: 60,
831 maxWidth: 80,
ac69b28b 832 flex: 2,
435cce27
DM
833 },
834 {
835 header: gettext('Action'),
836 dataIndex: 'action',
837 renderer: function(value, metaData, record) {
838 return render_errors('action', value, metaData, record);
839 },
ac69b28b 840 minWidth: 80,
33bf9791
TL
841 maxWidth: 200,
842 flex: 2,
435cce27
DM
843 },
844 {
845 header: gettext('Macro'),
846 dataIndex: 'macro',
847 renderer: function(value, metaData, record) {
848 return render_errors('macro', value, metaData, record);
849 },
ac69b28b 850 minWidth: 80,
33bf9791 851 flex: 2,
f6710aac 852 },
435cce27
DM
853 ];
854
855 if (me.allow_iface) {
856 columns.push({
857 header: gettext('Interface'),
858 dataIndex: 'iface',
859 renderer: function(value, metaData, record) {
860 return render_errors('iface', value, metaData, record);
861 },
ac69b28b 862 minWidth: 80,
33bf9791 863 flex: 2,
435cce27
DM
864 });
865 }
866
fa94a977 867 columns.push(
33bf9791
TL
868 {
869 header: gettext('Protocol'),
870 dataIndex: 'proto',
871 renderer: function(value, metaData, record) {
872 return render_errors('proto', value, metaData, record);
873 },
874 width: 75,
875 },
435cce27
DM
876 {
877 header: gettext('Source'),
878 dataIndex: 'source',
879 renderer: function(value, metaData, record) {
880 return render_errors('source', value, metaData, record);
881 },
ac69b28b 882 minWidth: 100,
33bf9791 883 flex: 2,
435cce27
DM
884 },
885 {
33bf9791
TL
886 header: gettext('S.Port'),
887 dataIndex: 'sport',
435cce27 888 renderer: function(value, metaData, record) {
33bf9791 889 return render_errors('sport', value, metaData, record);
435cce27 890 },
33bf9791 891 width: 75,
435cce27
DM
892 },
893 {
33bf9791
TL
894 header: gettext('Destination'),
895 dataIndex: 'dest',
435cce27 896 renderer: function(value, metaData, record) {
33bf9791 897 return render_errors('dest', value, metaData, record);
435cce27 898 },
ac69b28b 899 minWidth: 100,
33bf9791 900 flex: 2,
435cce27
DM
901 },
902 {
33bf9791 903 header: gettext('D.Port'),
435cce27
DM
904 dataIndex: 'dport',
905 renderer: function(value, metaData, record) {
906 return render_errors('dport', value, metaData, record);
907 },
33bf9791 908 width: 75,
435cce27 909 },
3c37fe48
CE
910 {
911 header: gettext('Log level'),
912 dataIndex: 'log',
913 renderer: function(value, metaData, record) {
914 return render_errors('log', value, metaData, record);
915 },
33bf9791 916 width: 100,
3c37fe48 917 },
435cce27
DM
918 {
919 header: gettext('Comment'),
920 dataIndex: 'comment',
33bf9791
TL
921 flex: 10,
922 minWidth: 75,
435cce27 923 renderer: function(value, metaData, record) {
2d1ed7d3
TL
924 let comment = render_errors('comment', Ext.util.Format.htmlEncode(value), metaData, record) || '';
925 if (comment.length * 12 > metaData.column.cellWidth) {
926 comment = `<span data-qtip="${comment}">${comment}</span>`;
927 }
928 return comment;
929 },
f6710aac 930 },
fa94a977 931 );
435cce27
DM
932
933 Ext.apply(me, {
934 store: store,
935 selModel: sm,
936 tbar: tbar,
5e7d2723 937 viewConfig: {
435cce27
DM
938 plugins: [
939 {
940 ptype: 'gridviewdragdrop',
941 dragGroup: 'FWRuleDDGroup',
f6710aac
TL
942 dropGroup: 'FWRuleDDGroup',
943 },
435cce27
DM
944 ],
945 listeners: {
5e7d2723 946 beforedrop: function(node, data, dropRec, dropPosition) {
435cce27
DM
947 if (!dropRec) {
948 return false; // empty view
949 }
5e7d2723 950 let moveto = dropRec.get('pos');
435cce27
DM
951 if (dropPosition === 'after') {
952 moveto++;
953 }
5e7d2723 954 let pos = data.records[0].get('pos');
435cce27
DM
955 me.moveRule(pos, moveto);
956 return 0;
957 },
f6710aac
TL
958 itemdblclick: run_editor,
959 },
435cce27
DM
960 },
961 sortableColumns: false,
f6710aac 962 columns: columns,
435cce27
DM
963 });
964
965 me.callParent();
966
967 if (me.base_url) {
968 me.setBaseUrl(me.base_url); // load
969 }
f6710aac 970 },
435cce27 971}, function() {
435cce27
DM
972 Ext.define('pve-fw-rule', {
973 extend: 'Ext.data.Model',
5e7d2723
TL
974 fields: [
975 { name: 'enable', type: 'boolean' },
976 'type',
977 'action',
978 'macro',
979 'source',
980 'dest',
981 'proto',
982 'iface',
983 'dport',
984 'sport',
985 'comment',
986 'pos',
987 'digest',
988 'errors',
989 ],
f6710aac 990 idProperty: 'pos',
435cce27 991 });
435cce27 992});