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