]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/grid/FirewallRules.js
update shipped appliance info index
[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,
f6710aac 237 fieldLabel: gettext('Source'),
1490b5eb
AL
238 maxLength: 512,
239 maxLengthText: gettext('Too long, consider using IP sets.'),
435cce27
DM
240 },
241 {
242 xtype: 'pveIPRefSelector',
243 name: 'dest',
244 autoSelect: false,
245 editable: true,
246 base_url: me.list_refs_url,
f6710aac 247 fieldLabel: gettext('Destination'),
1490b5eb
AL
248 maxLength: 512,
249 maxLengthText: gettext('Too long, consider using IP sets.'),
f6710aac 250 },
fa94a977 251 );
435cce27 252
2a4971d8 253
435cce27
DM
254 me.column2 = [
255 {
896c0d50 256 xtype: 'proxmoxcheckbox',
435cce27
DM
257 name: 'enable',
258 checked: false,
435cce27 259 uncheckedValue: 0,
f6710aac 260 fieldLabel: gettext('Enable'),
435cce27
DM
261 },
262 {
263 xtype: 'pveFWMacroSelector',
264 name: 'macro',
435cce27 265 fieldLabel: gettext('Macro'),
1fafdce8 266 editable: true,
435cce27
DM
267 allowBlank: true,
268 listeners: {
269 change: function(f, value) {
1fafdce8 270 if (value === null) {
435cce27
DM
271 me.down('field[name=proto]').setDisabled(false);
272 me.down('field[name=sport]').setDisabled(false);
273 me.down('field[name=dport]').setDisabled(false);
274 } else {
275 me.down('field[name=proto]').setDisabled(true);
276 me.down('field[name=proto]').setValue('');
277 me.down('field[name=sport]').setDisabled(true);
278 me.down('field[name=sport]').setValue('');
279 me.down('field[name=dport]').setDisabled(true);
7c7ae44f 280 me.down('field[name=dport]').setValue('');
435cce27 281 }
f6710aac
TL
282 },
283 },
435cce27
DM
284 },
285 {
286 xtype: 'pveIPProtocolSelector',
287 name: 'proto',
288 autoSelect: false,
289 editable: true,
290 value: '',
f6710aac 291 fieldLabel: gettext('Protocol'),
d09a68e4
ML
292 listeners: {
293 change: function(f, value) {
294 if (value === 'icmp' || value === 'icmpv6' || value === 'ipv6-icmp') {
295 me.down('field[name=dport]').setHidden(true);
296 me.down('field[name=dport]').setDisabled(true);
297 if (value === 'icmp') {
298 me.down('#icmpv4-type').setHidden(false);
299 me.down('#icmpv4-type').setDisabled(false);
300 me.down('#icmpv6-type').setHidden(true);
301 me.down('#icmpv6-type').setDisabled(true);
302 } else {
303 me.down('#icmpv6-type').setHidden(false);
304 me.down('#icmpv6-type').setDisabled(false);
305 me.down('#icmpv4-type').setHidden(true);
306 me.down('#icmpv4-type').setDisabled(true);
307 }
308 } else {
309 me.down('#icmpv4-type').setHidden(true);
310 me.down('#icmpv4-type').setDisabled(true);
311 me.down('#icmpv6-type').setHidden(true);
312 me.down('#icmpv6-type').setDisabled(true);
313 me.down('field[name=dport]').setHidden(false);
314 me.down('field[name=dport]').setDisabled(false);
315 }
9882c956 316 },
d09a68e4 317 },
435cce27
DM
318 },
319 {
320 xtype: 'displayfield',
321 fieldLabel: '',
322 height: 7,
f6710aac 323 value: '',
435cce27
DM
324 },
325 {
326 xtype: 'textfield',
327 name: 'sport',
328 value: '',
f6710aac 329 fieldLabel: gettext('Source port'),
435cce27
DM
330 },
331 {
332 xtype: 'textfield',
333 name: 'dport',
435cce27 334 value: '',
f6710aac
TL
335 fieldLabel: gettext('Dest. port'),
336 },
d09a68e4
ML
337 {
338 xtype: 'pveICMPTypeSelector',
339 name: 'icmp-type',
340 id: 'icmpv4-type',
341 autoSelect: false,
342 editable: true,
343 hidden: true,
344 disabled: true,
345 value: '',
346 fieldLabel: gettext('ICMP type'),
347 store: ICMP_TYPE_NAMES_STORE,
348 },
349 {
350 xtype: 'pveICMPTypeSelector',
351 name: 'icmp-type',
352 id: 'icmpv6-type',
353 autoSelect: false,
354 editable: true,
355 hidden: true,
356 disabled: true,
357 value: '',
358 fieldLabel: gettext('ICMP type'),
359 store: ICMPV6_TYPE_NAMES_STORE,
360 },
6db91b0f
TL
361 ];
362
363 me.advancedColumn1 = [
3c37fe48 364 {
f6710aac
TL
365 xtype: 'pveFirewallLogLevels',
366 },
435cce27 367 ];
3c37fe48 368
435cce27
DM
369 me.columnB = [
370 {
371 xtype: 'textfield',
372 name: 'comment',
373 value: '',
f6710aac
TL
374 fieldLabel: gettext('Comment'),
375 },
435cce27
DM
376 ];
377
378 me.callParent();
f6710aac 379 },
435cce27
DM
380});
381
382Ext.define('PVE.FirewallRuleEdit', {
9fccc702 383 extend: 'Proxmox.window.Edit',
435cce27
DM
384
385 base_url: undefined,
386 list_refs_url: undefined,
387
388 allow_iface: false,
389
8058410f 390 initComponent: function() {
435cce27
DM
391 var me = this;
392
393 if (!me.base_url) {
394 throw "no base_url specified";
395 }
396 if (!me.list_refs_url) {
397 throw "no list_refs_url specified";
398 }
399
53e3ea84 400 me.isCreate = me.rule_pos === undefined;
435cce27 401
d5e771ce 402 if (me.isCreate) {
435cce27
DM
403 me.url = '/api2/extjs' + me.base_url;
404 me.method = 'POST';
405 } else {
406 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
407 me.method = 'PUT';
408 }
409
410 var ipanel = Ext.create('PVE.FirewallRulePanel', {
d5e771ce 411 isCreate: me.isCreate,
435cce27
DM
412 list_refs_url: me.list_refs_url,
413 allow_iface: me.allow_iface,
f6710aac 414 rule_pos: me.rule_pos,
435cce27
DM
415 });
416
417 Ext.apply(me, {
418 subject: gettext('Rule'),
419 isAdd: true,
8058410f 420 items: [ipanel],
435cce27
DM
421 });
422
423 me.callParent();
424
d5e771ce 425 if (!me.isCreate) {
435cce27
DM
426 me.load({
427 success: function(response, options) {
428 var values = response.result.data;
429 ipanel.setValues(values);
d09a68e4
ML
430 // set icmp-type again after protocol has been set
431 if (values["icmp-type"] !== undefined) {
9882c956 432 ipanel.setValues({ "icmp-type": values["icmp-type"] });
d09a68e4 433 }
435cce27
DM
434 if (values.errors) {
435 var field = me.query('[isFormField][name=modified_marker]')[0];
436 field.setValue(1);
437 Ext.Function.defer(function() {
438 var form = ipanel.up('form').getForm();
a764c5f7 439 form.markInvalid(values.errors);
435cce27
DM
440 }, 100);
441 }
f6710aac 442 },
435cce27 443 });
330020d2
WL
444 } else if (me.rec) {
445 ipanel.setValues(me.rec.data);
435cce27 446 }
f6710aac 447 },
435cce27
DM
448});
449
450Ext.define('PVE.FirewallGroupRuleEdit', {
9fccc702 451 extend: 'Proxmox.window.Edit',
435cce27
DM
452
453 base_url: undefined,
454
455 allow_iface: false,
456
8058410f 457 initComponent: function() {
435cce27
DM
458 var me = this;
459
53e3ea84 460 me.isCreate = me.rule_pos === undefined;
435cce27 461
d5e771ce 462 if (me.isCreate) {
435cce27
DM
463 me.url = '/api2/extjs' + me.base_url;
464 me.method = 'POST';
465 } else {
466 me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
467 me.method = 'PUT';
468 }
469
470 var column1 = [
471 {
472 xtype: 'hiddenfield',
473 name: 'type',
f6710aac 474 value: 'group',
435cce27
DM
475 },
476 {
477 xtype: 'pveSecurityGroupsSelector',
478 name: 'action',
479 value: '',
480 fieldLabel: gettext('Security Group'),
f6710aac
TL
481 allowBlank: false,
482 },
435cce27
DM
483 ];
484
485 if (me.allow_iface) {
486 column1.push({
dbed4c1c 487 xtype: 'proxmoxtextfield',
435cce27 488 name: 'iface',
d5e771ce 489 deleteEmpty: !me.isCreate,
435cce27 490 value: '',
f6710aac 491 fieldLabel: gettext('Interface'),
435cce27
DM
492 });
493 }
494
ef4ef788 495 var ipanel = Ext.create('Proxmox.panel.InputPanel', {
d5e771ce 496 isCreate: me.isCreate,
435cce27
DM
497 column1: column1,
498 column2: [
499 {
896c0d50 500 xtype: 'proxmoxcheckbox',
435cce27
DM
501 name: 'enable',
502 checked: false,
435cce27 503 uncheckedValue: 0,
f6710aac
TL
504 fieldLabel: gettext('Enable'),
505 },
435cce27
DM
506 ],
507 columnB: [
508 {
509 xtype: 'textfield',
510 name: 'comment',
511 value: '',
f6710aac
TL
512 fieldLabel: gettext('Comment'),
513 },
514 ],
435cce27
DM
515 });
516
517 Ext.apply(me, {
518 subject: gettext('Rule'),
519 isAdd: true,
8058410f 520 items: [ipanel],
435cce27
DM
521 });
522
523 me.callParent();
524
d5e771ce 525 if (!me.isCreate) {
435cce27 526 me.load({
8058410f 527 success: function(response, options) {
435cce27
DM
528 var values = response.result.data;
529 ipanel.setValues(values);
f6710aac 530 },
435cce27
DM
531 });
532 }
f6710aac 533 },
435cce27
DM
534});
535
536Ext.define('PVE.FirewallRules', {
537 extend: 'Ext.grid.Panel',
538 alias: 'widget.pveFirewallRules',
539
ba93a9c6
DC
540 onlineHelp: 'chapter_pve_firewall',
541
123e1c80
DC
542 stateful: true,
543 stateId: 'grid-firewall-rules',
544
435cce27
DM
545 base_url: undefined,
546 list_refs_url: undefined,
547
548 addBtn: undefined,
549 removeBtn: undefined,
550 editBtn: undefined,
551 groupBtn: undefined,
552
553 tbar_prefix: undefined,
554
555 allow_groups: true,
556 allow_iface: false,
557
558 setBaseUrl: function(url) {
559 var me = this;
560
561 me.base_url = url;
562
563 if (url === undefined) {
564 me.addBtn.setDisabled(true);
565 if (me.groupBtn) {
566 me.groupBtn.setDisabled(true);
567 }
568 me.store.removeAll();
569 } else {
1056e10c 570 if (me.canEdit) {
2e37e779
AD
571 me.addBtn.setDisabled(false);
572 if (me.groupBtn) {
573 me.groupBtn.setDisabled(false);
574 }
435cce27 575 }
2e37e779
AD
576 me.removeBtn.baseurl = url + '/';
577
435cce27 578 me.store.setProxy({
56a353b9 579 type: 'proxmox',
f6710aac 580 url: '/api2/json' + url,
435cce27
DM
581 });
582
583 me.store.load();
584 }
585 },
586
587 moveRule: function(from, to) {
588 var me = this;
589
2a4971d8 590 if (!me.base_url) {
435cce27
DM
591 return;
592 }
593
e7ade592 594 Proxmox.Utils.API2Request({
435cce27
DM
595 url: me.base_url + "/" + from,
596 method: 'PUT',
597 params: { moveto: to },
598 waitMsgTarget: me,
599 failure: function(response, options) {
600 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
601 },
602 callback: function() {
603 me.store.load();
f6710aac 604 },
435cce27
DM
605 });
606 },
607
608 updateRule: function(rule) {
609 var me = this;
610
2a4971d8 611 if (!me.base_url) {
435cce27
DM
612 return;
613 }
614
615 rule.enable = rule.enable ? 1 : 0;
616
617 var pos = rule.pos;
618 delete rule.pos;
619 delete rule.errors;
620
e7ade592 621 Proxmox.Utils.API2Request({
435cce27
DM
622 url: me.base_url + '/' + pos.toString(),
623 method: 'PUT',
624 params: rule,
625 waitMsgTarget: me,
626 failure: function(response, options) {
627 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
628 },
629 callback: function() {
630 me.store.load();
f6710aac 631 },
435cce27
DM
632 });
633 },
634
435cce27
DM
635
636 initComponent: function() {
435cce27
DM
637 var me = this;
638
639 if (!me.list_refs_url) {
640 throw "no list_refs_url specified";
641 }
642
f6710aac
TL
643 var store = Ext.create('Ext.data.Store', {
644 model: 'pve-fw-rule',
435cce27
DM
645 });
646
647 var reload = function() {
648 store.load();
649 };
650
651 var sm = Ext.create('Ext.selection.RowModel', {});
652
2e37e779 653 me.caps = Ext.state.Manager.get('GuiCap');
1056e10c 654 me.canEdit = !!me.caps.vms['VM.Config.Network'] || !!me.caps.dc['Sys.Modify'] || !!me.caps.nodes['Sys.Modify'];
2e37e779 655
435cce27
DM
656 var run_editor = function() {
657 var rec = sm.getSelection()[0];
1056e10c 658 if (!rec || !me.canEdit) {
435cce27
DM
659 return;
660 }
661 var type = rec.data.type;
662
663 var editor;
664 if (type === 'in' || type === 'out') {
665 editor = 'PVE.FirewallRuleEdit';
666 } else if (type === 'group') {
667 editor = 'PVE.FirewallGroupRuleEdit';
668 } else {
669 return;
670 }
671
672 var win = Ext.create(editor, {
673 digest: rec.data.digest,
674 allow_iface: me.allow_iface,
675 base_url: me.base_url,
676 list_refs_url: me.list_refs_url,
f6710aac 677 rule_pos: rec.data.pos,
435cce27
DM
678 });
679
680 win.show();
681 win.on('destroy', reload);
682 };
683
f6710aac 684 me.editBtn = Ext.create('Proxmox.button.Button', {
435cce27
DM
685 text: gettext('Edit'),
686 disabled: true,
1056e10c 687 enableFn: rec => me.canEdit,
435cce27 688 selModel: sm,
f6710aac 689 handler: run_editor,
435cce27
DM
690 });
691
8058410f 692 me.addBtn = Ext.create('Ext.Button', {
435cce27
DM
693 text: gettext('Add'),
694 disabled: true,
695 handler: function() {
696 var win = Ext.create('PVE.FirewallRuleEdit', {
697 allow_iface: me.allow_iface,
698 base_url: me.base_url,
f6710aac 699 list_refs_url: me.list_refs_url,
435cce27
DM
700 });
701 win.on('destroy', reload);
702 win.show();
f6710aac 703 },
435cce27
DM
704 });
705
330020d2 706 var run_copy_editor = function() {
5e7d2723 707 let rec = sm.getSelection()[0];
330020d2
WL
708 if (!rec) {
709 return;
710 }
5e7d2723 711 let type = rec.data.type;
330020d2
WL
712 if (!(type === 'in' || type === 'out')) {
713 return;
714 }
715
5e7d2723 716 let win = Ext.create('PVE.FirewallRuleEdit', {
330020d2
WL
717 allow_iface: me.allow_iface,
718 base_url: me.base_url,
719 list_refs_url: me.list_refs_url,
f6710aac 720 rec: rec,
330020d2 721 });
330020d2
WL
722 win.show();
723 win.on('destroy', reload);
724 };
725
f6710aac 726 me.copyBtn = Ext.create('Proxmox.button.Button', {
330020d2
WL
727 text: gettext('Copy'),
728 selModel: sm,
1056e10c 729 enableFn: ({ data }) => (data.type === 'in' || data.type === 'out') && me.canEdit,
330020d2 730 disabled: true,
f6710aac 731 handler: run_copy_editor,
330020d2
WL
732 });
733
435cce27 734 if (me.allow_groups) {
8058410f 735 me.groupBtn = Ext.create('Ext.Button', {
2a4971d8 736 text: gettext('Insert') + ': ' +
435cce27
DM
737 gettext('Security Group'),
738 disabled: true,
739 handler: function() {
740 var win = Ext.create('PVE.FirewallGroupRuleEdit', {
741 allow_iface: me.allow_iface,
f6710aac 742 base_url: me.base_url,
435cce27
DM
743 });
744 win.on('destroy', reload);
745 win.show();
f6710aac 746 },
435cce27
DM
747 });
748 }
749
f6710aac 750 me.removeBtn = Ext.create('Proxmox.button.StdRemoveButton', {
1056e10c 751 enableFn: rec => me.canEdit,
435cce27 752 selModel: sm,
3b1ca3ff
DC
753 baseurl: me.base_url + '/',
754 confirmMsg: false,
755 getRecordName: function(rec) {
756 var rule = rec.data;
757 return rule.pos.toString() +
758 '?digest=' + encodeURIComponent(rule.digest);
759 },
760 callback: function() {
761 me.store.load();
f6710aac 762 },
435cce27
DM
763 });
764
5e7d2723 765 let tbar = me.tbar_prefix ? [me.tbar_prefix] : [];
330020d2 766 tbar.push(me.addBtn, me.copyBtn);
435cce27
DM
767 if (me.groupBtn) {
768 tbar.push(me.groupBtn);
769 }
fa94a977 770 tbar.push(me.removeBtn, me.editBtn);
435cce27 771
5e7d2723
TL
772 let render_errors = function(name, value, metaData, record) {
773 let errors = record.data.errors;
435cce27 774 if (errors && errors[name]) {
3ab7e0ec 775 metaData.tdCls = 'proxmox-invalid-row';
5e7d2723
TL
776 let html = '<p>' + Ext.htmlEncode(errors[name]) + '</p>';
777 metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' + html + '"';
435cce27
DM
778 }
779 return value;
780 };
781
5e7d2723 782 let columns = [
435cce27
DM
783 {
784 // similar to xtype: 'rownumberer',
785 dataIndex: 'pos',
786 resizable: false,
4ad65791
DC
787 minWidth: 65,
788 maxWidth: 83,
ac69b28b 789 flex: 1,
435cce27 790 sortable: false,
435cce27
DM
791 hideable: false,
792 menuDisabled: true,
5e7d2723 793 renderer: function(value, metaData, record, rowIdx, colIdx) {
435cce27 794 metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
4ad65791 795 let dragHandle = "<i class='pve-grid-fa fa fa-fw fa-reorder cursor-move'></i>";
435cce27 796 if (value >= 0) {
4ad65791 797 return dragHandle + value;
435cce27 798 }
4ad65791 799 return dragHandle;
f6710aac 800 },
435cce27
DM
801 },
802 {
803 xtype: 'checkcolumn',
97a537de 804 header: gettext('On'),
435cce27
DM
805 dataIndex: 'enable',
806 listeners: {
7a4c3133
EK
807 checkchange: function(column, recordIndex, checked) {
808 var record = me.getStore().getData().items[recordIndex];
435cce27
DM
809 record.commit();
810 var data = {};
7a4c3133 811 Ext.Array.forEach(record.getFields(), function(field) {
435cce27
DM
812 data[field.name] = record.get(field.name);
813 });
814 if (!me.allow_iface || !data.iface) {
815 delete data.iface;
816 }
817 me.updateRule(data);
f6710aac 818 },
435cce27 819 },
97a537de 820 width: 40,
435cce27
DM
821 },
822 {
823 header: gettext('Type'),
824 dataIndex: 'type',
825 renderer: function(value, metaData, record) {
826 return render_errors('type', value, metaData, record);
827 },
33bf9791
TL
828 minWidth: 60,
829 maxWidth: 80,
ac69b28b 830 flex: 2,
435cce27
DM
831 },
832 {
833 header: gettext('Action'),
834 dataIndex: 'action',
835 renderer: function(value, metaData, record) {
836 return render_errors('action', value, metaData, record);
837 },
ac69b28b 838 minWidth: 80,
33bf9791
TL
839 maxWidth: 200,
840 flex: 2,
435cce27
DM
841 },
842 {
843 header: gettext('Macro'),
844 dataIndex: 'macro',
845 renderer: function(value, metaData, record) {
846 return render_errors('macro', value, metaData, record);
847 },
ac69b28b 848 minWidth: 80,
33bf9791 849 flex: 2,
f6710aac 850 },
435cce27
DM
851 ];
852
853 if (me.allow_iface) {
854 columns.push({
855 header: gettext('Interface'),
856 dataIndex: 'iface',
857 renderer: function(value, metaData, record) {
858 return render_errors('iface', value, metaData, record);
859 },
ac69b28b 860 minWidth: 80,
33bf9791 861 flex: 2,
435cce27
DM
862 });
863 }
864
fa94a977 865 columns.push(
33bf9791
TL
866 {
867 header: gettext('Protocol'),
868 dataIndex: 'proto',
869 renderer: function(value, metaData, record) {
870 return render_errors('proto', value, metaData, record);
871 },
872 width: 75,
873 },
435cce27
DM
874 {
875 header: gettext('Source'),
876 dataIndex: 'source',
877 renderer: function(value, metaData, record) {
878 return render_errors('source', value, metaData, record);
879 },
ac69b28b 880 minWidth: 100,
33bf9791 881 flex: 2,
435cce27
DM
882 },
883 {
33bf9791
TL
884 header: gettext('S.Port'),
885 dataIndex: 'sport',
435cce27 886 renderer: function(value, metaData, record) {
33bf9791 887 return render_errors('sport', value, metaData, record);
435cce27 888 },
33bf9791 889 width: 75,
435cce27
DM
890 },
891 {
33bf9791
TL
892 header: gettext('Destination'),
893 dataIndex: 'dest',
435cce27 894 renderer: function(value, metaData, record) {
33bf9791 895 return render_errors('dest', value, metaData, record);
435cce27 896 },
ac69b28b 897 minWidth: 100,
33bf9791 898 flex: 2,
435cce27
DM
899 },
900 {
33bf9791 901 header: gettext('D.Port'),
435cce27
DM
902 dataIndex: 'dport',
903 renderer: function(value, metaData, record) {
904 return render_errors('dport', value, metaData, record);
905 },
33bf9791 906 width: 75,
435cce27 907 },
3c37fe48
CE
908 {
909 header: gettext('Log level'),
910 dataIndex: 'log',
911 renderer: function(value, metaData, record) {
912 return render_errors('log', value, metaData, record);
913 },
33bf9791 914 width: 100,
3c37fe48 915 },
435cce27
DM
916 {
917 header: gettext('Comment'),
918 dataIndex: 'comment',
33bf9791
TL
919 flex: 10,
920 minWidth: 75,
435cce27 921 renderer: function(value, metaData, record) {
2d1ed7d3
TL
922 let comment = render_errors('comment', Ext.util.Format.htmlEncode(value), metaData, record) || '';
923 if (comment.length * 12 > metaData.column.cellWidth) {
924 comment = `<span data-qtip="${comment}">${comment}</span>`;
925 }
926 return comment;
927 },
f6710aac 928 },
fa94a977 929 );
435cce27
DM
930
931 Ext.apply(me, {
932 store: store,
933 selModel: sm,
934 tbar: tbar,
5e7d2723 935 viewConfig: {
435cce27
DM
936 plugins: [
937 {
938 ptype: 'gridviewdragdrop',
939 dragGroup: 'FWRuleDDGroup',
f6710aac
TL
940 dropGroup: 'FWRuleDDGroup',
941 },
435cce27
DM
942 ],
943 listeners: {
5e7d2723 944 beforedrop: function(node, data, dropRec, dropPosition) {
435cce27
DM
945 if (!dropRec) {
946 return false; // empty view
947 }
5e7d2723 948 let moveto = dropRec.get('pos');
435cce27
DM
949 if (dropPosition === 'after') {
950 moveto++;
951 }
5e7d2723 952 let pos = data.records[0].get('pos');
435cce27
DM
953 me.moveRule(pos, moveto);
954 return 0;
955 },
f6710aac
TL
956 itemdblclick: run_editor,
957 },
435cce27
DM
958 },
959 sortableColumns: false,
f6710aac 960 columns: columns,
435cce27
DM
961 });
962
963 me.callParent();
964
965 if (me.base_url) {
966 me.setBaseUrl(me.base_url); // load
967 }
f6710aac 968 },
435cce27 969}, function() {
435cce27
DM
970 Ext.define('pve-fw-rule', {
971 extend: 'Ext.data.Model',
5e7d2723
TL
972 fields: [
973 { name: 'enable', type: 'boolean' },
974 'type',
975 'action',
976 'macro',
977 'source',
978 'dest',
979 'proto',
980 'iface',
981 'dport',
982 'sport',
983 'comment',
984 'pos',
985 'digest',
986 'errors',
987 ],
f6710aac 988 idProperty: 'pos',
435cce27 989 });
435cce27 990});