]>
Commit | Line | Data |
---|---|---|
5a691a50 DM |
1 | Ext.define('PVE.lxc.NetworkInputPanel', { |
2 | extend: 'PVE.panel.InputPanel', | |
3 | alias: 'widget.pveLxcNetworkInputPanel', | |
4 | ||
5 | insideWizard: false, | |
6 | ||
7 | setNodename: function(nodename) { | |
8 | var me = this; | |
9 | ||
10 | if (!nodename || (me.nodename === nodename)) { | |
11 | return; | |
12 | } | |
13 | ||
14 | me.nodename = nodename; | |
15 | ||
16 | var bridgesel = me.query("[isFormField][name=bridge]")[0]; | |
17 | bridgesel.setNodename(nodename); | |
18 | }, | |
19 | ||
20 | onGetValues: function(values) { | |
21 | var me = this; | |
22 | ||
23 | var id; | |
24 | if (me.create) { | |
25 | id = values.id; | |
26 | delete values.id; | |
27 | } else { | |
28 | id = me.ifname; | |
29 | } | |
30 | ||
31 | if (!id) { | |
32 | return {}; | |
33 | } | |
34 | ||
35 | var newdata = {}; | |
36 | ||
84de645d DC |
37 | if (values.ipv6mode !== 'static') { |
38 | values.ip6 = values.ipv6mode; | |
39 | } | |
40 | if (values.ipv4mode !== 'static') { | |
41 | values.ip = values.ipv4mode; | |
42 | } | |
5a691a50 DM |
43 | newdata[id] = PVE.Parser.printLxcNetwork(values); |
44 | return newdata; | |
45 | }, | |
46 | ||
47 | initComponent : function() { | |
48 | var me = this; | |
49 | ||
50 | if (!me.dataCache) { | |
51 | throw "no dataCache specified"; | |
52 | } | |
53 | ||
54 | var cdata = {}; | |
55 | ||
56 | if (me.insideWizard) { | |
57 | me.ifname = 'net0'; | |
58 | cdata.name = 'eth0'; | |
59 | } | |
60 | ||
61 | if (!me.create) { | |
62 | if (!me.ifname) { | |
63 | throw "no interface name specified"; | |
64 | } | |
65 | if (!me.dataCache[me.ifname]) { | |
66 | throw "no such interface '" + me.ifname + "'"; | |
67 | } | |
68 | ||
69 | cdata = PVE.Parser.parseLxcNetwork(me.dataCache[me.ifname]); | |
70 | } | |
71 | ||
72 | var i, netlist = []; | |
73 | for (i = 0; i < 10; i++) { | |
ec0bd652 | 74 | netlist.push({ "name": "net" + i.toString() }); |
5a691a50 DM |
75 | } |
76 | ||
77 | var netliststore = Ext.create('Ext.data.Store', { | |
78 | fields: ['name'], | |
79 | data: netlist | |
80 | }); | |
81 | ||
82 | var ifselector = { | |
83 | xtype: 'combobox', | |
84 | fieldLabel: gettext('ID'), | |
85 | store: netliststore, | |
86 | editable: false, | |
87 | name: 'id', | |
88 | value: me.ifname, | |
89 | disabled: !me.create, | |
90 | queryMode: 'local', | |
91 | displayField: 'name', | |
92 | valueField: 'name', | |
93 | validator: function(value) { | |
94 | if (me.create && me.dataCache[value]) { | |
95 | return "Network ID already in use"; | |
96 | } | |
ec0bd652 DC |
97 | // validator can return bool/String |
98 | /*jslint confusion: true*/ | |
5a691a50 DM |
99 | return true; |
100 | } | |
101 | }; | |
7c7ae44f | 102 | me.column1 = [ |
5a691a50 DM |
103 | ifselector, |
104 | { | |
105 | xtype: 'textfield', | |
106 | name: 'name', | |
5a691a50 DM |
107 | fieldLabel: gettext('Name') + ' (i.e. eth0)', |
108 | allowBlank: false, | |
109 | value: cdata.name, | |
110 | validator: function(value) { | |
ec0bd652 | 111 | var result = ''; |
5a691a50 DM |
112 | Ext.Object.each(me.dataCache, function(key, netstr) { |
113 | if (!key.match(/^net\d+/) || key === me.ifname) { | |
114 | return; // continue | |
115 | } | |
116 | var net = PVE.Parser.parseLxcNetwork(netstr); | |
117 | if (net.name === value) { | |
118 | result = "interface name already in use"; | |
119 | return false; | |
120 | } | |
121 | }); | |
ec0bd652 DC |
122 | if (result !== '') { |
123 | return result; | |
124 | } | |
125 | // validator can return bool/string | |
126 | /*jslint confusion:true*/ | |
127 | return true; | |
5a691a50 DM |
128 | } |
129 | }, | |
130 | { | |
131 | xtype: 'textfield', | |
132 | name: 'hwaddr', | |
133 | fieldLabel: gettext('MAC address'), | |
134 | vtype: 'MacAddress', | |
135 | value: cdata.hwaddr, | |
41bfbd0a | 136 | allowBlank: true, |
5a691a50 DM |
137 | emptyText: 'auto' |
138 | }, | |
139 | { | |
140 | xtype: 'PVE.form.BridgeSelector', | |
141 | name: 'bridge', | |
142 | nodename: me.nodename, | |
143 | fieldLabel: gettext('Bridge'), | |
144 | value: cdata.bridge, | |
145 | allowBlank: false | |
146 | }, | |
147 | { | |
148 | xtype: 'pveVlanField', | |
149 | name: 'tag', | |
22f2f9d6 | 150 | value: cdata.tag |
5a691a50 DM |
151 | }, |
152 | { | |
519ca7fa DM |
153 | xtype: 'numberfield', |
154 | name: 'rate', | |
155 | fieldLabel: gettext('Rate limit') + ' (MB/s)', | |
156 | minValue: 0, | |
157 | maxValue: 10*1024, | |
158 | value: cdata.rate, | |
159 | emptyText: 'unlimited', | |
160 | allowBlank: true | |
161 | }, | |
162 | { | |
5a691a50 DM |
163 | xtype: 'pvecheckbox', |
164 | fieldLabel: gettext('Firewall'), | |
165 | name: 'firewall', | |
22f2f9d6 | 166 | checked: cdata.firewall |
5a691a50 DM |
167 | } |
168 | ]; | |
169 | ||
170 | var dhcp4 = (cdata.ip === 'dhcp'); | |
171 | if (dhcp4) { | |
172 | cdata.ip = ''; | |
173 | cdata.gw = ''; | |
174 | } | |
175 | ||
176 | var auto6 = (cdata.ip6 === 'auto'); | |
177 | var dhcp6 = (cdata.ip6 === 'dhcp'); | |
178 | if (auto6 || dhcp6) { | |
179 | cdata.ip6 = ''; | |
180 | cdata.gw6 = ''; | |
181 | } | |
182 | ||
183 | me.column2 = [ | |
184 | { | |
185 | layout: { | |
186 | type: 'hbox', | |
187 | align: 'middle' | |
188 | }, | |
189 | border: false, | |
190 | margin: '0 0 5 0', | |
5a691a50 DM |
191 | items: [ |
192 | { | |
193 | xtype: 'label', | |
22f2f9d6 | 194 | text: gettext('IPv4') + ':' |
5a691a50 DM |
195 | }, |
196 | { | |
197 | xtype: 'radiofield', | |
198 | boxLabel: gettext('Static'), | |
199 | name: 'ipv4mode', | |
200 | inputValue: 'static', | |
201 | checked: !dhcp4, | |
202 | margin: '0 0 0 10', | |
203 | listeners: { | |
204 | change: function(cb, value) { | |
205 | me.down('field[name=ip]').setDisabled(!value); | |
206 | me.down('field[name=gw]').setDisabled(!value); | |
207 | } | |
208 | } | |
209 | }, | |
210 | { | |
211 | xtype: 'radiofield', | |
212 | boxLabel: gettext('DHCP'), | |
213 | name: 'ipv4mode', | |
214 | inputValue: 'dhcp', | |
215 | checked: dhcp4, | |
216 | margin: '0 0 0 10' | |
217 | } | |
218 | ] | |
219 | }, | |
220 | { | |
221 | xtype: 'textfield', | |
222 | name: 'ip', | |
223 | vtype: 'IPCIDRAddress', | |
224 | value: cdata.ip, | |
225 | disabled: dhcp4, | |
226 | fieldLabel: gettext('IPv4/CIDR') | |
227 | }, | |
228 | { | |
229 | xtype: 'textfield', | |
230 | name: 'gw', | |
231 | value: cdata.gw, | |
232 | vtype: 'IPAddress', | |
233 | disabled: dhcp4, | |
234 | fieldLabel: gettext('Gateway') + ' (' + gettext('IPv4') +')', | |
235 | margin: '0 0 3 0' // override bottom margin to account for the menuseparator | |
236 | }, | |
237 | { | |
238 | xtype: 'menuseparator', | |
239 | height: '3', | |
240 | margin: '0' | |
241 | }, | |
242 | { | |
243 | layout: { | |
244 | type: 'hbox', | |
245 | align: 'middle' | |
246 | }, | |
247 | border: false, | |
248 | margin: '0 0 5 0', | |
5a691a50 DM |
249 | items: [ |
250 | { | |
251 | xtype: 'label', | |
22f2f9d6 | 252 | text: gettext('IPv6') + ':' |
5a691a50 DM |
253 | }, |
254 | { | |
255 | xtype: 'radiofield', | |
256 | boxLabel: gettext('Static'), | |
257 | name: 'ipv6mode', | |
258 | inputValue: 'static', | |
259 | checked: !(auto6 || dhcp6), | |
260 | margin: '0 0 0 10', | |
261 | listeners: { | |
262 | change: function(cb, value) { | |
263 | me.down('field[name=ip6]').setDisabled(!value); | |
264 | me.down('field[name=gw6]').setDisabled(!value); | |
265 | } | |
266 | } | |
267 | }, | |
268 | { | |
269 | xtype: 'radiofield', | |
270 | boxLabel: gettext('DHCP'), | |
271 | name: 'ipv6mode', | |
272 | inputValue: 'dhcp', | |
273 | checked: dhcp6, | |
274 | margin: '0 0 0 10' | |
275 | }, | |
276 | { | |
277 | xtype: 'radiofield', | |
278 | boxLabel: gettext('SLAAC'), | |
279 | name: 'ipv6mode', | |
280 | inputValue: 'auto', | |
281 | checked: auto6, | |
282 | margin: '0 0 0 10' | |
283 | } | |
284 | ] | |
285 | }, | |
286 | { | |
287 | xtype: 'textfield', | |
288 | name: 'ip6', | |
289 | value: cdata.ip6, | |
290 | vtype: 'IP6CIDRAddress', | |
291 | disabled: (dhcp6 || auto6), | |
292 | fieldLabel: gettext('IPv6/CIDR') | |
293 | }, | |
294 | { | |
295 | xtype: 'textfield', | |
296 | name: 'gw6', | |
297 | vtype: 'IP6Address', | |
298 | value: cdata.gw6, | |
299 | disabled: (dhcp6 || auto6), | |
300 | fieldLabel: gettext('Gateway') + ' (' + gettext('IPv6') +')' | |
301 | } | |
302 | ]; | |
303 | ||
304 | me.callParent(); | |
305 | } | |
306 | }); | |
307 | ||
308 | /*jslint confusion: true */ | |
309 | Ext.define('PVE.lxc.NetworkEdit', { | |
310 | extend: 'PVE.window.Edit', | |
311 | ||
312 | isAdd: true, | |
313 | ||
314 | initComponent : function() { | |
315 | var me = this; | |
316 | ||
317 | if (!me.dataCache) { | |
318 | throw "no dataCache specified"; | |
319 | } | |
320 | ||
321 | if (!me.nodename) { | |
322 | throw "no node name specified"; | |
323 | } | |
324 | ||
325 | var ipanel = Ext.create('PVE.lxc.NetworkInputPanel', { | |
326 | ifname: me.ifname, | |
327 | nodename: me.nodename, | |
328 | dataCache: me.dataCache, | |
329 | create: me.create | |
330 | }); | |
331 | ||
332 | Ext.apply(me, { | |
333 | subject: gettext('Network Device') + ' (veth)', | |
334 | digest: me.dataCache.digest, | |
335 | items: [ ipanel ] | |
336 | }); | |
337 | ||
338 | me.callParent(); | |
339 | } | |
340 | }); | |
341 | ||
342 | Ext.define('PVE.lxc.NetworkView', { | |
343 | extend: 'Ext.grid.GridPanel', | |
344 | alias: ['widget.pveLxcNetworkView'], | |
345 | ||
346 | dataCache: {}, // used to store result of last load | |
347 | ||
348 | load: function() { | |
349 | var me = this; | |
350 | ||
351 | PVE.Utils.setErrorMask(me, true); | |
352 | ||
353 | PVE.Utils.API2Request({ | |
354 | url: me.url, | |
355 | failure: function(response, opts) { | |
356 | PVE.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus); | |
357 | }, | |
358 | success: function(response, opts) { | |
359 | PVE.Utils.setErrorMask(me, false); | |
360 | var result = Ext.decode(response.responseText); | |
361 | var data = result.data || {}; | |
362 | me.dataCache = data; | |
363 | var records = []; | |
364 | Ext.Object.each(data, function(key, value) { | |
365 | if (!key.match(/^net\d+/)) { | |
366 | return; // continue | |
367 | } | |
368 | var net = PVE.Parser.parseLxcNetwork(value); | |
369 | net.id = key; | |
370 | records.push(net); | |
371 | }); | |
372 | me.store.loadData(records); | |
373 | } | |
374 | }); | |
375 | }, | |
376 | ||
377 | initComponent : function() { | |
378 | var me = this; | |
379 | ||
380 | var nodename = me.pveSelNode.data.node; | |
381 | if (!nodename) { | |
382 | throw "no node name specified"; | |
383 | } | |
384 | ||
385 | var vmid = me.pveSelNode.data.vmid; | |
386 | if (!vmid) { | |
387 | throw "no VM ID specified"; | |
388 | } | |
389 | ||
390 | var caps = Ext.state.Manager.get('GuiCap'); | |
391 | ||
392 | me.url = '/nodes/' + nodename + '/lxc/' + vmid + '/config'; | |
393 | ||
394 | var store = new Ext.data.Store({ | |
395 | model: 'pve-lxc-network', | |
396 | sorters: [ | |
397 | { | |
398 | property : 'id', | |
399 | direction: 'ASC' | |
400 | } | |
401 | ] | |
402 | }); | |
403 | ||
404 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
405 | ||
406 | var remove_btn = new PVE.button.Button({ | |
407 | text: gettext('Remove'), | |
408 | disabled: true, | |
409 | selModel: sm, | |
410 | enableFn: function(rec) { | |
411 | return !!caps.vms['VM.Config.Network']; | |
412 | }, | |
413 | confirmMsg: function (rec) { | |
414 | return Ext.String.format(gettext('Are you sure you want to remove entry {0}'), | |
415 | "'" + rec.data.id + "'"); | |
416 | }, | |
417 | handler: function(btn, event, rec) { | |
418 | PVE.Utils.API2Request({ | |
419 | url: me.url, | |
420 | waitMsgTarget: me, | |
421 | method: 'PUT', | |
422 | params: { 'delete': rec.data.id, digest: me.dataCache.digest }, | |
423 | callback: function() { | |
424 | me.load(); | |
425 | }, | |
426 | failure: function (response, opts) { | |
427 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
428 | } | |
429 | }); | |
430 | } | |
431 | }); | |
432 | ||
433 | var run_editor = function() { | |
434 | var rec = sm.getSelection()[0]; | |
435 | if (!rec) { | |
436 | return; | |
437 | } | |
438 | ||
439 | if (!caps.vms['VM.Config.Network']) { | |
440 | return false; | |
441 | } | |
442 | ||
443 | var win = Ext.create('PVE.lxc.NetworkEdit', { | |
444 | url: me.url, | |
445 | nodename: nodename, | |
446 | dataCache: me.dataCache, | |
447 | ifname: rec.data.id | |
448 | }); | |
449 | win.on('destroy', me.load, me); | |
450 | win.show(); | |
451 | }; | |
452 | ||
453 | var edit_btn = new PVE.button.Button({ | |
454 | text: gettext('Edit'), | |
455 | selModel: sm, | |
456 | disabled: true, | |
457 | enableFn: function(rec) { | |
458 | if (!caps.vms['VM.Config.Network']) { | |
459 | return false; | |
460 | } | |
461 | return true; | |
462 | }, | |
463 | handler: run_editor | |
464 | }); | |
465 | ||
f7993618 | 466 | Ext.apply(me, { |
5a691a50 DM |
467 | store: store, |
468 | selModel: sm, | |
469 | stateful: false, | |
470 | tbar: [ | |
471 | { | |
472 | text: gettext('Add'), | |
473 | disabled: !caps.vms['VM.Config.Network'], | |
474 | handler: function() { | |
475 | var win = Ext.create('PVE.lxc.NetworkEdit', { | |
476 | url: me.url, | |
477 | nodename: nodename, | |
478 | create: true, | |
479 | dataCache: me.dataCache | |
480 | }); | |
481 | win.on('destroy', me.load, me); | |
482 | win.show(); | |
483 | } | |
484 | }, | |
485 | remove_btn, | |
486 | edit_btn | |
487 | ], | |
488 | columns: [ | |
489 | { | |
490 | header: gettext('ID'), | |
491 | width: 50, | |
492 | dataIndex: 'id' | |
493 | }, | |
494 | { | |
495 | header: gettext('Name'), | |
496 | width: 80, | |
497 | dataIndex: 'name' | |
498 | }, | |
499 | { | |
500 | header: gettext('Bridge'), | |
501 | width: 80, | |
502 | dataIndex: 'bridge' | |
503 | }, | |
504 | { | |
505 | header: gettext('Firewall'), | |
506 | width: 80, | |
507 | dataIndex: 'firewall', | |
508 | renderer: PVE.Utils.format_boolean | |
509 | }, | |
510 | { | |
511 | header: gettext('VLAN Tag'), | |
512 | width: 80, | |
513 | dataIndex: 'tag' | |
514 | }, | |
515 | { | |
516 | header: gettext('MAC address'), | |
517 | width: 110, | |
518 | dataIndex: 'hwaddr' | |
519 | }, | |
520 | { | |
521 | header: gettext('IP address'), | |
522 | width: 150, | |
523 | dataIndex: 'ip', | |
524 | renderer: function(value, metaData, rec) { | |
525 | if (rec.data.ip && rec.data.ip6) { | |
526 | return rec.data.ip + "<br>" + rec.data.ip6; | |
527 | } else if (rec.data.ip6) { | |
528 | return rec.data.ip6; | |
529 | } else { | |
530 | return rec.data.ip; | |
531 | } | |
532 | } | |
533 | }, | |
534 | { | |
535 | header: gettext('Gateway'), | |
536 | width: 150, | |
537 | dataIndex: 'gw', | |
538 | renderer: function(value, metaData, rec) { | |
539 | if (rec.data.gw && rec.data.gw6) { | |
540 | return rec.data.gw + "<br>" + rec.data.gw6; | |
541 | } else if (rec.data.gw6) { | |
542 | return rec.data.gw6; | |
543 | } else { | |
544 | return rec.data.gw; | |
545 | } | |
546 | } | |
547 | } | |
548 | ], | |
549 | listeners: { | |
4b488565 | 550 | activate: me.load, |
5a691a50 DM |
551 | itemdblclick: run_editor |
552 | } | |
553 | }); | |
554 | ||
555 | me.callParent(); | |
5a691a50 DM |
556 | } |
557 | }, function() { | |
558 | ||
559 | Ext.define('pve-lxc-network', { | |
560 | extend: "Ext.data.Model", | |
561 | proxy: { type: 'memory' }, | |
562 | fields: [ 'id', 'name', 'hwaddr', 'bridge', | |
563 | 'ip', 'gw', 'ip6', 'gw6', 'tag', 'firewall' ] | |
564 | }); | |
565 | ||
566 | }); | |
567 |