]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/grid/FirewallRules.js
ui: firewall panel/grids : add privilege checks on buttons
[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 {
2e37e779
AD
572 if (me.caps.vms['VM.Config.Network'] || me.caps.dc['Sys.Modify'] || me.caps.nodes['Sys.Modify']) {
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
AD
655 me.caps = Ext.state.Manager.get('GuiCap');
656
435cce27
DM
657 var run_editor = function() {
658 var rec = sm.getSelection()[0];
659 if (!rec) {
660 return;
661 }
662 var type = rec.data.type;
663
664 var editor;
665 if (type === 'in' || type === 'out') {
666 editor = 'PVE.FirewallRuleEdit';
667 } else if (type === 'group') {
668 editor = 'PVE.FirewallGroupRuleEdit';
669 } else {
670 return;
671 }
672
673 var win = Ext.create(editor, {
674 digest: rec.data.digest,
675 allow_iface: me.allow_iface,
676 base_url: me.base_url,
677 list_refs_url: me.list_refs_url,
f6710aac 678 rule_pos: rec.data.pos,
435cce27
DM
679 });
680
681 win.show();
682 win.on('destroy', reload);
683 };
684
f6710aac 685 me.editBtn = Ext.create('Proxmox.button.Button', {
435cce27
DM
686 text: gettext('Edit'),
687 disabled: true,
2e37e779 688 enableFn: rec => !!me.caps.vms['VM.Config.Network'] || !!me.caps.dc['Sys.Modify'] || !!me.caps.nodes['Sys.Modify'],
435cce27 689 selModel: sm,
f6710aac 690 handler: run_editor,
435cce27
DM
691 });
692
8058410f 693 me.addBtn = Ext.create('Ext.Button', {
435cce27
DM
694 text: gettext('Add'),
695 disabled: true,
696 handler: function() {
697 var win = Ext.create('PVE.FirewallRuleEdit', {
698 allow_iface: me.allow_iface,
699 base_url: me.base_url,
f6710aac 700 list_refs_url: me.list_refs_url,
435cce27
DM
701 });
702 win.on('destroy', reload);
703 win.show();
f6710aac 704 },
435cce27
DM
705 });
706
330020d2 707 var run_copy_editor = function() {
5e7d2723 708 let rec = sm.getSelection()[0];
330020d2
WL
709 if (!rec) {
710 return;
711 }
5e7d2723 712 let type = rec.data.type;
330020d2
WL
713 if (!(type === 'in' || type === 'out')) {
714 return;
715 }
716
5e7d2723 717 let win = Ext.create('PVE.FirewallRuleEdit', {
330020d2
WL
718 allow_iface: me.allow_iface,
719 base_url: me.base_url,
720 list_refs_url: me.list_refs_url,
f6710aac 721 rec: rec,
330020d2 722 });
330020d2
WL
723 win.show();
724 win.on('destroy', reload);
725 };
726
f6710aac 727 me.copyBtn = Ext.create('Proxmox.button.Button', {
330020d2
WL
728 text: gettext('Copy'),
729 selModel: sm,
2e37e779 730 enableFn: ({ data }) => (data.type === 'in' || data.type === 'out') && (!!me.caps.vms['VM.Config.Network'] || !!me.caps.dc['Sys.Modify'] || !!me.caps.nodes['Sys.Modify']),
330020d2 731 disabled: true,
f6710aac 732 handler: run_copy_editor,
330020d2
WL
733 });
734
435cce27 735 if (me.allow_groups) {
8058410f 736 me.groupBtn = Ext.create('Ext.Button', {
2a4971d8 737 text: gettext('Insert') + ': ' +
435cce27
DM
738 gettext('Security Group'),
739 disabled: true,
740 handler: function() {
741 var win = Ext.create('PVE.FirewallGroupRuleEdit', {
742 allow_iface: me.allow_iface,
f6710aac 743 base_url: me.base_url,
435cce27
DM
744 });
745 win.on('destroy', reload);
746 win.show();
f6710aac 747 },
435cce27
DM
748 });
749 }
750
f6710aac 751 me.removeBtn = Ext.create('Proxmox.button.StdRemoveButton', {
2e37e779 752 enableFn: rec => !!me.caps.vms['VM.Config.Network'] || !!me.caps.dc['Sys.Modify'] || !!me.caps.nodes['Sys.Modify'],
435cce27 753 selModel: sm,
3b1ca3ff
DC
754 baseurl: me.base_url + '/',
755 confirmMsg: false,
756 getRecordName: function(rec) {
757 var rule = rec.data;
758 return rule.pos.toString() +
759 '?digest=' + encodeURIComponent(rule.digest);
760 },
761 callback: function() {
762 me.store.load();
f6710aac 763 },
435cce27
DM
764 });
765
5e7d2723 766 let tbar = me.tbar_prefix ? [me.tbar_prefix] : [];
330020d2 767 tbar.push(me.addBtn, me.copyBtn);
435cce27
DM
768 if (me.groupBtn) {
769 tbar.push(me.groupBtn);
770 }
fa94a977 771 tbar.push(me.removeBtn, me.editBtn);
435cce27 772
5e7d2723
TL
773 let render_errors = function(name, value, metaData, record) {
774 let errors = record.data.errors;
435cce27 775 if (errors && errors[name]) {
3ab7e0ec 776 metaData.tdCls = 'proxmox-invalid-row';
5e7d2723
TL
777 let html = '<p>' + Ext.htmlEncode(errors[name]) + '</p>';
778 metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' + html + '"';
435cce27
DM
779 }
780 return value;
781 };
782
5e7d2723 783 let columns = [
435cce27
DM
784 {
785 // similar to xtype: 'rownumberer',
786 dataIndex: 'pos',
787 resizable: false,
4ad65791
DC
788 minWidth: 65,
789 maxWidth: 83,
ac69b28b 790 flex: 1,
435cce27 791 sortable: false,
435cce27
DM
792 hideable: false,
793 menuDisabled: true,
5e7d2723 794 renderer: function(value, metaData, record, rowIdx, colIdx) {
435cce27 795 metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
4ad65791 796 let dragHandle = "<i class='pve-grid-fa fa fa-fw fa-reorder cursor-move'></i>";
435cce27 797 if (value >= 0) {
4ad65791 798 return dragHandle + value;
435cce27 799 }
4ad65791 800 return dragHandle;
f6710aac 801 },
435cce27
DM
802 },
803 {
804 xtype: 'checkcolumn',
97a537de 805 header: gettext('On'),
435cce27
DM
806 dataIndex: 'enable',
807 listeners: {
7a4c3133
EK
808 checkchange: function(column, recordIndex, checked) {
809 var record = me.getStore().getData().items[recordIndex];
435cce27
DM
810 record.commit();
811 var data = {};
7a4c3133 812 Ext.Array.forEach(record.getFields(), function(field) {
435cce27
DM
813 data[field.name] = record.get(field.name);
814 });
815 if (!me.allow_iface || !data.iface) {
816 delete data.iface;
817 }
818 me.updateRule(data);
f6710aac 819 },
435cce27 820 },
97a537de 821 width: 40,
435cce27
DM
822 },
823 {
824 header: gettext('Type'),
825 dataIndex: 'type',
826 renderer: function(value, metaData, record) {
827 return render_errors('type', value, metaData, record);
828 },
33bf9791
TL
829 minWidth: 60,
830 maxWidth: 80,
ac69b28b 831 flex: 2,
435cce27
DM
832 },
833 {
834 header: gettext('Action'),
835 dataIndex: 'action',
836 renderer: function(value, metaData, record) {
837 return render_errors('action', value, metaData, record);
838 },
ac69b28b 839 minWidth: 80,
33bf9791
TL
840 maxWidth: 200,
841 flex: 2,
435cce27
DM
842 },
843 {
844 header: gettext('Macro'),
845 dataIndex: 'macro',
846 renderer: function(value, metaData, record) {
847 return render_errors('macro', value, metaData, record);
848 },
ac69b28b 849 minWidth: 80,
33bf9791 850 flex: 2,
f6710aac 851 },
435cce27
DM
852 ];
853
854 if (me.allow_iface) {
855 columns.push({
856 header: gettext('Interface'),
857 dataIndex: 'iface',
858 renderer: function(value, metaData, record) {
859 return render_errors('iface', value, metaData, record);
860 },
ac69b28b 861 minWidth: 80,
33bf9791 862 flex: 2,
435cce27
DM
863 });
864 }
865
fa94a977 866 columns.push(
33bf9791
TL
867 {
868 header: gettext('Protocol'),
869 dataIndex: 'proto',
870 renderer: function(value, metaData, record) {
871 return render_errors('proto', value, metaData, record);
872 },
873 width: 75,
874 },
435cce27
DM
875 {
876 header: gettext('Source'),
877 dataIndex: 'source',
878 renderer: function(value, metaData, record) {
879 return render_errors('source', value, metaData, record);
880 },
ac69b28b 881 minWidth: 100,
33bf9791 882 flex: 2,
435cce27
DM
883 },
884 {
33bf9791
TL
885 header: gettext('S.Port'),
886 dataIndex: 'sport',
435cce27 887 renderer: function(value, metaData, record) {
33bf9791 888 return render_errors('sport', value, metaData, record);
435cce27 889 },
33bf9791 890 width: 75,
435cce27
DM
891 },
892 {
33bf9791
TL
893 header: gettext('Destination'),
894 dataIndex: 'dest',
435cce27 895 renderer: function(value, metaData, record) {
33bf9791 896 return render_errors('dest', value, metaData, record);
435cce27 897 },
ac69b28b 898 minWidth: 100,
33bf9791 899 flex: 2,
435cce27
DM
900 },
901 {
33bf9791 902 header: gettext('D.Port'),
435cce27
DM
903 dataIndex: 'dport',
904 renderer: function(value, metaData, record) {
905 return render_errors('dport', value, metaData, record);
906 },
33bf9791 907 width: 75,
435cce27 908 },
3c37fe48
CE
909 {
910 header: gettext('Log level'),
911 dataIndex: 'log',
912 renderer: function(value, metaData, record) {
913 return render_errors('log', value, metaData, record);
914 },
33bf9791 915 width: 100,
3c37fe48 916 },
435cce27
DM
917 {
918 header: gettext('Comment'),
919 dataIndex: 'comment',
33bf9791
TL
920 flex: 10,
921 minWidth: 75,
435cce27 922 renderer: function(value, metaData, record) {
2d1ed7d3
TL
923 let comment = render_errors('comment', Ext.util.Format.htmlEncode(value), metaData, record) || '';
924 if (comment.length * 12 > metaData.column.cellWidth) {
925 comment = `<span data-qtip="${comment}">${comment}</span>`;
926 }
927 return comment;
928 },
f6710aac 929 },
fa94a977 930 );
435cce27
DM
931
932 Ext.apply(me, {
933 store: store,
934 selModel: sm,
935 tbar: tbar,
5e7d2723 936 viewConfig: {
435cce27
DM
937 plugins: [
938 {
939 ptype: 'gridviewdragdrop',
940 dragGroup: 'FWRuleDDGroup',
f6710aac
TL
941 dropGroup: 'FWRuleDDGroup',
942 },
435cce27
DM
943 ],
944 listeners: {
5e7d2723 945 beforedrop: function(node, data, dropRec, dropPosition) {
435cce27
DM
946 if (!dropRec) {
947 return false; // empty view
948 }
5e7d2723 949 let moveto = dropRec.get('pos');
435cce27
DM
950 if (dropPosition === 'after') {
951 moveto++;
952 }
5e7d2723 953 let pos = data.records[0].get('pos');
435cce27
DM
954 me.moveRule(pos, moveto);
955 return 0;
956 },
f6710aac
TL
957 itemdblclick: run_editor,
958 },
435cce27
DM
959 },
960 sortableColumns: false,
f6710aac 961 columns: columns,
435cce27
DM
962 });
963
964 me.callParent();
965
966 if (me.base_url) {
967 me.setBaseUrl(me.base_url); // load
968 }
f6710aac 969 },
435cce27 970}, function() {
435cce27
DM
971 Ext.define('pve-fw-rule', {
972 extend: 'Ext.data.Model',
5e7d2723
TL
973 fields: [
974 { name: 'enable', type: 'boolean' },
975 'type',
976 'action',
977 'macro',
978 'source',
979 'dest',
980 'proto',
981 'iface',
982 'dport',
983 'sport',
984 'comment',
985 'pos',
986 'digest',
987 'errors',
988 ],
f6710aac 989 idProperty: 'pos',
435cce27 990 });
435cce27 991});