luci2: move most RPC proxy function declarations into the views using them to reduce...
[project/luci2/ui.git] / luci2 / htdocs / luci2 / view / network.switch.js
1 L.ui.view.extend({
2 title: L.tr('Switch'),
3 description: L.tr('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'),
4
5 listSwitchNames: L.rpc.declare({
6 object: 'luci2.network',
7 method: 'switch_list',
8 expect: { switches: [ ] }
9 }),
10
11 getSwitchInfo: L.rpc.declare({
12 object: 'luci2.network',
13 method: 'switch_info',
14 params: [ 'switch' ],
15 expect: { info: { } },
16 filter: function(data, params) {
17 data['attrs'] = data['switch'];
18 data['vlan_attrs'] = data['vlan'];
19 data['port_attrs'] = data['port'];
20 data['switch'] = params['switch'];
21
22 delete data.vlan;
23 delete data.port;
24
25 return data;
26 }
27 }),
28
29 getSwitchStatus: L.rpc.declare({
30 object: 'luci2.network',
31 method: 'switch_status',
32 params: [ 'switch' ],
33 expect: { ports: [ ] }
34 }),
35
36 switchPortState: L.cbi.ListValue.extend({
37 choices: [
38 [ 'n', L.trc('Switch port state', 'off') ],
39 [ 'u', L.trc('Switch port state', 'untagged') ],
40 [ 't', L.trc('Switch port state', 'tagged') ]
41 ],
42
43 init: function(name, options)
44 {
45 var self = this;
46
47 options.datatype = function(val, elem)
48 {
49 if (val == 'u')
50 {
51 var u = false;
52 var sections = self.section.sections();
53
54 for (var i = 0; i < sections.length; i++)
55 {
56 var v = self.formvalue(sections[i]['.name']);
57 if (v == 'u')
58 {
59 if (u)
60 return L.tr('Port must not be untagged in multiple VLANs');
61
62 u = true;
63 }
64 }
65 }
66
67 return true;
68 };
69
70 this.callSuper('init', name, options);
71 },
72
73 ucivalue: function(sid)
74 {
75 var ports = (this.map.get('network', sid, 'ports') || '').match(/[0-9]+[tu]?/g);
76
77 if (ports)
78 for (var i = 0; i < ports.length; i++)
79 if (ports[i].match(/^([0-9]+)([tu]?)$/))
80 if (RegExp.$1 == this.name)
81 return RegExp.$2 || 'u';
82
83 return 'n';
84 },
85
86 save: function(sid)
87 {
88 return;
89 }
90 }),
91
92 execute: function() {
93 var self = this;
94 return self.listSwitchNames().then(function(switches) {
95 L.rpc.batch();
96
97 for (var i = 0; i < switches.length; i++)
98 self.getSwitchInfo(switches[i]);
99
100 return L.rpc.flush();
101 }).then(function(switches) {
102 var m = new L.cbi.Map('network', {
103 readonly: !self.options.acls.network
104 });
105
106 for (var i = 0; i < switches.length; i++)
107 {
108 var swname = switches[i]['switch'];
109
110 var vid_opt = 'vlan';
111 var v4k_opt = undefined;
112 var pvid_opt = undefined;
113 var max_vid = switches[i].num_vlans - 1;
114 var num_vlans = switches[i].num_vlans;
115
116 for (var j = 0; j < switches[i].vlan_attrs.length; j++)
117 {
118 switch (switches[i].vlan_attrs[j].name)
119 {
120 case 'tag':
121 case 'vid':
122 case 'pvid':
123 vid_opt = switches[i].vlan_attrs[j].name;
124 max_vid = 4095;
125 break;
126 }
127 }
128
129 for (var j = 0; j < switches[i].port_attrs.length; j++)
130 {
131 switch (switches[i].port_attrs[j].name)
132 {
133 case 'pvid':
134 pvid_opt = switches[i].port_attrs[j].name;
135 break;
136 }
137 }
138
139
140 var sw = m.section(L.cbi.TypedSection, 'switch', {
141 caption: L.tr('Switch "%s"').format(switches[i].model),
142 swname: swname
143 });
144
145 sw.filter = function(section) {
146 return (section['.name'] == this.options.swname ||
147 section.name == this.options.swname);
148 };
149
150 for (var j = 0; j < switches[i].attrs.length; j++)
151 {
152 switch (switches[i].attrs[j].name)
153 {
154 case 'enable_vlan':
155 sw.option(L.cbi.CheckboxValue, 'enable_vlan', {
156 caption: L.tr('Enable VLAN functionality')
157 });
158 break;
159
160 case 'enable_learning':
161 sw.option(L.cbi.CheckboxValue, 'enable_learning', {
162 caption: L.tr('Enable learning and aging'),
163 initial: true,
164 optional: true
165 });
166 break;
167
168 case 'max_length':
169 sw.option(L.cbi.CheckboxValue, 'max_length', {
170 caption: L.tr('Enable Jumbo Frame passthrough'),
171 enabled: '3',
172 optional: true
173 });
174 break;
175
176 case 'enable_vlan4k':
177 v4k_opt = switches[i].attrs[j].name;
178 break;
179 }
180 }
181
182 var vlans = m.section(L.cbi.TableSection, 'switch_vlan', {
183 caption: L.tr('VLANs on "%s"').format(switches[i].model),
184 swname: swname,
185 addremove: true,
186 add_caption: L.tr('Add VLAN entry …')
187 });
188
189 vlans.add = function() {
190 var sections = this.sections();
191 var used_vids = { };
192
193 for (var j = 0; j < sections.length; j++)
194 {
195 var v = this.map.get('network', sections[j]['.name'], 'vlan');
196 if (v)
197 used_vids[v] = true;
198 }
199
200 for (var j = 1; j < num_vlans; j++)
201 {
202 if (used_vids[j.toString()])
203 continue;
204
205 var sid = this.map.add('network', 'switch_vlan');
206 this.map.set('network', sid, 'device', this.options.swname);
207 this.map.set('network', sid, 'vlan', j);
208 break;
209 }
210 };
211
212 vlans.filter = function(section) {
213 return (section.device == this.options.swname);
214 };
215
216 vlans.sections = function() {
217 var s = this.callSuper('sections');
218
219 s.sort(function(a, b) {
220 var x = parseInt(a[vid_opt] || a.vlan);
221 if (isNaN(x))
222 x = 9999;
223
224 var y = parseInt(b[vid_opt] || b.vlan);
225 if (isNaN(y))
226 y = 9999;
227
228 return (x - y);
229 });
230
231 return s;
232 };
233
234 var port_opts = [ ];
235
236 var vo = vlans.option(L.cbi.InputValue, vid_opt, {
237 caption: L.tr('VLAN ID'),
238 datatype: function(val) {
239 var sections = vlans.sections();
240 var used_vids = { };
241
242 for (var j = 0; j < sections.length; j++)
243 {
244 var v = vlans.fields[vid_opt].formvalue(sections[j]['.name']);
245 if (!v)
246 continue;
247
248 if (used_vids[v])
249 return L.tr('VLAN ID must be unique');
250
251 used_vids[v] = true;
252 }
253
254 if (val.match(/[^0-9]/))
255 return L.tr('Invalid VLAN ID');
256
257 val = parseInt(val, 10);
258
259 if (val < 1 || val > max_vid)
260 return L.tr('VLAN ID must be a value between %u and %u').format(1, max_vid);
261
262 return true;
263 }
264 });
265
266 vo.ucivalue = function(sid) {
267 var id = this.map.get('network', sid, vid_opt);
268
269 if (isNaN(parseInt(id)))
270 id = this.map.get('network', sid, 'vlan');
271
272 return id;
273 };
274
275 vo.save = function(sid) {
276 var old_ports = this.map.get('network', sid, 'ports');
277 var new_ports = '';
278
279 for (var j = 0; j < port_opts.length; j++)
280 {
281 var v = port_opts[j].formvalue(sid);
282 if (v != 'n')
283 new_ports += '%s%d%s'.format(
284 new_ports ? ' ' : '', j,
285 (v == 'u') ? '' : 't');
286 }
287
288 if (new_ports != old_ports)
289 this.map.set('network', sid, 'ports', new_ports);
290
291 if (v4k_opt)
292 {
293 var s = sw.sections();
294 for (var j = 0; j < s.length; j++)
295 this.map.set('network', s[j]['.name'], v4k_opt, '1');
296 }
297
298 this.callSuper('save', sid);
299 };
300
301 for (var j = 0; j < switches[i].num_ports; j++)
302 {
303 var label = L.trc('Switch port label', 'Port %d').format(j);
304
305 if (j == switches[i].cpu_port)
306 label = L.trc('Switch port label', 'CPU');
307
308 var po = vlans.option(self.switchPortState, j.toString(), {
309 caption: label + '<br /><small id="portstatus-%s-%d"></small>'.format(swname, j)
310 });
311
312 port_opts.push(po);
313 }
314 }
315
316 return m.insertInto('#map').then(function() {
317 self.repeat(function() {
318 return self.getSwitchStatus(swname).then(function(ports) {
319 for (var j = 0; j < ports.length; j++)
320 {
321 var s = L.tr('No link');
322 var d = '&#160;';
323
324 if (ports[j].link)
325 {
326 s = '%dbaseT'.format(ports[j].speed);
327 d = ports[j].full_duplex ? L.tr('Full-duplex') : L.tr('Half-duplex');
328 }
329
330 $('#portstatus-%s-%d'.format(swname, j))
331 .empty().append(s + '<br />' + d);
332 }
333 });
334 }, 5000);
335 });
336 });
337 }
338 });