luci2: nested section support and initial code refactoring
[project/luci2/ui.git] / luci2 / htdocs / luci2 / view / network.interfaces.js
1 L.ui.view.extend({
2 title: L.tr('Interface Overview'),
3
4 pendingRestart: [ ],
5 pendingShutdown: [ ],
6
7 setUp: L.rpc.declare({
8 object: 'luci2.network',
9 method: 'ifup',
10 params: [ 'data' ],
11 expect: { '': { code: -1 } }
12 }),
13
14 setDown: L.rpc.declare({
15 object: 'luci2.network',
16 method: 'ifdown',
17 params: [ 'data' ],
18 expect: { '': { code: -1 } }
19 }),
20
21 renderDeviceIcon: function(dev, up)
22 {
23 var icon = dev ? dev.icon(up) : L.globals.resource + '/icons/ethernet_disabled.png';
24 var desc = dev ? '%s (%s)'.format(dev.description(), dev.name()) : L.tr('Network interface not present');
25
26 return $('<img />')
27 .attr('title', desc)
28 .attr('src', icon);
29 },
30
31 renderNetworkBadge: function(network, div)
32 {
33 var dest = div || $('#network-badge-%s'.format(network.name()));
34 var device = network.getDevice(); //network.device || { type: 'Network device', device: '?' };
35 var subdevs = network.getSubdevices();
36
37 if (div)
38 {
39 var h = $('<div />')
40 .addClass('ifacebox-head')
41 .text(network.name());
42
43 if (network.zone)
44 h.css('background-color', network.zone.color).attr('title', L.trc('Interface status', 'Part of zone "%s"').format(network.zone.name));
45 else
46 h.css('background-color', '#cccccc').attr('title', L.trc('Interface status', 'Not part of any zone'));
47
48 dest.append(h);
49 }
50 else
51 {
52 dest.children('div.ifacebox-body').remove();
53 }
54
55 var b = $('<div />')
56 .addClass('ifacebox-body');
57
58 b.append(this.renderDeviceIcon(device, network.isUp()));
59
60 if (subdevs.length)
61 {
62 b.append('(');
63
64 for (var i = 0; i < subdevs.length; i++)
65 b.append(this.renderDeviceIcon(subdevs[i], subdevs[i].isUp()));
66
67 b.append(')');
68 }
69
70 b.append($('<br />')).append($('<small />').text(device ? device.name() : '?'));
71
72 return dest.append(b);
73 },
74
75 renderNetworkStatus: function(network, div)
76 {
77 var rv = '';
78
79 if (network.isUp())
80 {
81 rv += '<strong>%s</strong>: %t<br />'.format(
82 L.tr('Uptime'),
83 network.getUptime()
84 );
85 }
86 else
87 {
88 rv += '<strong>%s</strong>: %s<br />'.format(
89 L.tr('Uptime'),
90 L.tr('Interface is down')
91 );
92 }
93
94 var v4 = network.getIPv4Addrs();
95 if (v4.length)
96 rv += '<strong>%s</strong>: %s<br />'.format(
97 L.trc('Interface status', 'IPv4'),
98 v4.join(', ')
99 );
100
101 var v6 = network.getIPv6Addrs();
102 if (v6.length)
103 rv += '<strong>%s</strong>: %s<br />'.format(
104 L.trc('Interface status', 'IPv6'),
105 v6.join(', ')
106 );
107
108 return (div || $('#network-status-%s'.format(network.name())))
109 .empty()
110 .append(rv);
111 },
112
113 renderNetworkChart: function(network, div)
114 {
115 var dest = (div || $('#network-chart-%s'.format(network.name())));
116
117 dest.empty();
118
119 dest.append($('<div />')
120 .addClass('traffic-chart')
121 .append($('<span />')
122 .attr('id', 'network-chart-tx-%s'.format(network.name()))
123 .hide())
124 .append($('<label />')));
125
126 dest.append($('<div />')
127 .addClass('traffic-chart')
128 .append($('<span />')
129 .attr('id', 'network-chart-rx-%s'.format(network.name()))
130 .hide())
131 .append($('<label />')));
132
133 dest.append($('<small />')
134 .addClass('traffic-stats')
135 .text(L.tr('Loading statistics…')));
136
137 return dest;
138 },
139
140 refreshNetworkStatus: function()
141 {
142 var self = this;
143 var deferreds = [ ];
144
145 while (self.pendingRestart.length)
146 deferreds.push(self.setUp(self.pendingRestart.shift()));
147
148 while (self.pendingShutdown.length)
149 deferreds.push(self.setDown(self.pendingShutdown.shift()));
150
151 return $.when.apply($, deferreds).then(function() {
152 $('button').prop('disabled', false);
153 return $.when(
154 L.NetworkModel.refreshDeviceStatus(),
155 L.NetworkModel.refreshInterfaceStatus()
156 );
157 }).then(function() {
158 var networks = L.NetworkModel.getInterfaces();
159
160 for (var i = 0; i < networks.length; i++)
161 {
162 self.renderNetworkBadge(networks[i]);
163 self.renderNetworkStatus(networks[i]);
164 }
165
166 var max = 0.1;
167 var networks = L.NetworkModel.getInterfaces();
168
169 for (var i = 0; i < networks.length; i++)
170 {
171 var network = networks[i];
172 var history = network.getTrafficHistory();
173 var stats = network.getStatistics();
174
175 var tx = $('#network-chart-tx-%s'.format(network.name()));
176 var rx = $('#network-chart-rx-%s'.format(network.name()));
177
178 var tx_rate = history.tx_bytes[history.tx_bytes.length - 1];
179 var rx_rate = history.rx_bytes[history.rx_bytes.length - 1];
180
181 max = Math.max(Math.max.apply(Math, history.rx_bytes),
182 Math.max.apply(Math, history.tx_bytes),
183 max);
184
185 for (var j = 0; j < history.rx_bytes.length; j++)
186 history.rx_bytes[j] = -Math.abs(history.rx_bytes[j]);
187
188 tx.text(history.tx_bytes.join(','));
189 rx.text(history.rx_bytes.join(','));
190
191 tx.next().attr('title', '%.2mB/s'.format(tx_rate));
192 rx.next().attr('title', '%.2mB/s'.format(rx_rate));
193
194 tx.nextAll('label').html('↑ %.2mB/s'.format(tx_rate));
195 rx.nextAll('label').html('↓ %.2mB/s'.format(rx_rate));
196
197 tx.parent().nextAll('small.traffic-stats').html(
198 '<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
199 L.trc('Interface status', 'TX'),
200 stats.tx_bytes, stats.tx_packets) +
201 '<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
202 L.trc('Interface status', 'RX'),
203 stats.rx_bytes, stats.rx_packets));
204 }
205
206 for (var i = 0; i < networks.length; i++)
207 {
208 var network = networks[i];
209
210 var tx = $('#network-chart-tx-%s'.format(network.name()));
211 var rx = $('#network-chart-rx-%s'.format(network.name()));
212
213 tx.peity('line', { width: 200, min: 0, max: max });
214 rx.peity('line', { width: 200, min: -max, max: 0 });
215 }
216
217 L.ui.loading(false);
218 });
219 },
220
221 renderContents: function(networks)
222 {
223 var self = this;
224
225 var list = new L.ui.table({
226 columns: [ {
227 caption: L.tr('Network'),
228 width: '120px',
229 format: function(v) {
230 var div = $('<div />')
231 .attr('id', 'network-badge-%s'.format(v.name()))
232 .addClass('ifacebox');
233
234 return self.renderNetworkBadge(v, div);
235 }
236 }, {
237 caption: L.tr('Traffic'),
238 width: '215px',
239 format: function(v) {
240 var div = $('<div />').attr('id', 'network-chart-%s'.format(v.name()));
241 return self.renderNetworkChart(v, div);
242 }
243 }, {
244 caption: L.tr('Status'),
245 format: function(v) {
246 var div = $('<small />').attr('id', 'network-status-%s'.format(v.name()));
247 return self.renderNetworkStatus(v, div);
248 }
249 }, {
250 caption: L.tr('Actions'),
251 format: function(v, n) {
252 return $('<div />')
253 .addClass('btn-group btn-group-sm')
254 .append(L.ui.button(L.tr('Restart'), 'default', L.tr('Enable or restart interface'))
255 .click({ self: self, network: v }, self.handleIfup))
256 .append(L.ui.button(L.tr('Shutdown'), 'default', L.tr('Shut down interface'))
257 .click({ self: self, network: v }, self.handleIfdown))
258 .append(L.ui.button(L.tr('Edit'), 'primary', L.tr('Edit interface'))
259 .click({ self: self, network: v }, self.handleEdit))
260 .append(L.ui.button(L.tr('Delete'), 'danger', L.tr('Delete interface'))
261 .click({ self: self, network: v }, self.handleRemove));
262 }
263 } ]
264 });
265
266 for (var i = 0; i < networks.length; i++)
267 list.row([ networks[i], networks[i], networks[i], networks[i] ]);
268
269 self.repeat(self.refreshNetworkStatus, 5000);
270
271 $('#map')
272 .append(list.render());
273 },
274
275 renderInterfaceForm: function(network)
276 {
277 var m = new L.cbi.Map('network', {
278 tabbed: true,
279 caption: 'Interface config',
280 description: 'I can config interface!!!!'
281 });
282
283
284
285 var s4 = m.section(L.cbi.TypedSection, 'route', {
286 caption: L.tr('Static IPv4 Routes'),
287 anonymous: true,
288 addremove: true,
289 sortable: true,
290 add_caption: L.tr('Add new route'),
291 remove_caption: L.tr('Remove route')
292 });
293
294 var ifc = s4.option(L.cbi.ListValue, 'interface', {
295 caption: L.tr('Interface')
296 });
297
298 ifc.value('foo');
299
300 s4.option(L.cbi.InputValue, 'target', {
301 caption: L.tr('Target'),
302 datatype: 'ip4addr'
303 });
304
305 s4.option(L.cbi.InputValue, 'netmask', {
306 caption: L.tr('IPv4-Netmask'),
307 datatype: 'ip4addr',
308 placeholder: '255.255.255.255',
309 optional: true
310 });
311
312 s4.option(L.cbi.InputValue, 'gateway', {
313 caption: L.tr('IPv4-Gateway'),
314 datatype: 'ip4addr',
315 optional: true
316 });
317
318 s4.option(L.cbi.InputValue, 'metric', {
319 caption: L.tr('Metric'),
320 datatype: 'range(0,255)',
321 placeholder: 0,
322 optional: true
323 });
324
325 s4.option(L.cbi.InputValue, 'mtu', {
326 caption: L.tr('MTU'),
327 datatype: 'range(64,9000)',
328 placeholder: 1500,
329 optional: true
330 });
331
332 return m;
333 },
334
335 handleIfup: function(ev) {
336 this.disabled = true;
337 this.blur();
338 ev.data.self.pendingRestart.push(ev.data.network['interface']);
339 },
340
341 handleIfdown: function(ev) {
342 this.disabled = true;
343 this.blur();
344 ev.data.self.pendingShutdown.push(ev.data.network['interface']);
345 },
346
347 handleEdit: function(ev) {
348 var self = ev.data.self;
349 var network = ev.data.network;
350
351 return network.createForm(L.cbi.Modal).show();
352 },
353
354 execute: function() {
355 var self = this;
356
357 return L.NetworkModel.init().then(function() {
358 self.renderContents(L.NetworkModel.getInterfaces());
359 });
360 }
361 });