luci2: adapt views to changed luci2 framework
[project/luci2/ui.git] / luci2 / htdocs / luci2 / view / system.admin.js
1 L.ui.view.extend({
2 PubkeyListValue: L.cbi.AbstractValue.extend({
3 base64Table: {
4 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6,
5 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13,
6 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20,
7 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27,
8 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34,
9 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41,
10 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48,
11 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
12 '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62,
13 '/': 63, '=': 64
14 },
15
16 base64Decode: function(s)
17 {
18 var i = 0;
19 var d = '';
20
21 if (s.match(/[^A-Za-z0-9\+\/\=]/))
22 return undefined;
23
24 while (i < s.length)
25 {
26 var e1 = this.base64Table[s.charAt(i++)];
27 var e2 = this.base64Table[s.charAt(i++)];
28 var e3 = this.base64Table[s.charAt(i++)];
29 var e4 = this.base64Table[s.charAt(i++)];
30
31 var c1 = ( e1 << 2) | (e2 >> 4);
32 var c2 = ((e2 & 15) << 4) | (e3 >> 2);
33 var c3 = ((e3 & 3) << 6) | e4;
34
35 d += String.fromCharCode(c1);
36
37 if (e3 < 64)
38 d += String.fromCharCode(c2);
39
40 if (e4 < 64)
41 d += String.fromCharCode(c3);
42 }
43
44 return d;
45 },
46
47 lengthDecode: function(s, off)
48 {
49 var l = (s.charCodeAt(off++) << 24) |
50 (s.charCodeAt(off++) << 16) |
51 (s.charCodeAt(off++) << 8) |
52 s.charCodeAt(off++);
53
54 if (l < 0 || (off + l) > s.length)
55 return -1;
56
57 return l;
58 },
59
60 pubkeyDecode: function(s)
61 {
62 var parts = s.split(/\s+/);
63 if (parts.length < 2)
64 return undefined;
65
66 var key = this.base64Decode(parts[1]);
67 if (!key)
68 return undefined;
69
70 var off, len;
71
72 off = 0;
73 len = this.lengthDecode(key, off);
74
75 if (len < 0)
76 return undefined;
77
78 var type = key.substr(off + 4, len);
79 if (type != parts[0])
80 return undefined;
81
82 off += 4 + len;
83
84 var len1 = this.lengthDecode(key, off);
85 if (len1 < 0)
86 return undefined;
87
88 off += 4 + len1;
89
90 var len2 = this.lengthDecode(key, off);
91 if (len2 < 0)
92 return undefined;
93
94 if (len1 & 1)
95 len1--;
96
97 if (len2 & 1)
98 len2--;
99
100 switch (type)
101 {
102 case 'ssh-rsa':
103 return { type: 'RSA', bits: len2 * 8, comment: parts[2] };
104
105 case 'ssh-dss':
106 return { type: 'DSA', bits: len1 * 8, comment: parts[2] };
107
108 default:
109 return undefined;
110 }
111 },
112
113 _remove: function(ev)
114 {
115 var self = ev.data.self;
116
117 self._keys.splice(ev.data.index, 1);
118 self._render(ev.data.div);
119 },
120
121 _add: function(ev)
122 {
123 var self = ev.data.self;
124
125 var form = $('<div />')
126 .append($('<p />')
127 .text(L.tr('Paste the public key line into the field below and press "%s" to continue.').format(L.tr('Ok'))))
128 .append($('<p />')
129 .text(L.tr('Unrecognized public key! Please add only RSA or DSA keys.'))
130 .addClass('alert alert-danger')
131 .hide())
132 .append($('<p />')
133 .append($('<input />')
134 .attr('type', 'text')
135 .attr('placeholder', L.tr('Paste key here'))
136 .addClass('form-control')));
137
138 L.ui.dialog(L.tr('Add new public key'), form, {
139 style: 'confirm',
140 confirm: function() {
141 var val = form.find('input').val();
142 if (!val)
143 {
144 return;
145 }
146
147 var key = self.pubkeyDecode(val);
148 if (!key)
149 {
150 form.find('input').val('');
151 form.find('.alert').show();
152 return;
153 }
154
155 self._keys.push(val);
156 self._render(ev.data.div);
157
158 L.ui.dialog(false);
159 }
160 });
161 },
162
163 _show: function(ev)
164 {
165 var self = ev.data.self;
166
167 L.ui.dialog(
168 L.tr('Public key'),
169 $('<pre />').text(self._keys[ev.data.index]),
170 { style: 'close' }
171 );
172 },
173
174 _render: function(div)
175 {
176 div.empty();
177
178 for (var i = 0; i < this._keys.length; i++)
179 {
180 var k = this.pubkeyDecode(this._keys[i] || '');
181
182 if (!k)
183 continue;
184
185 $('<div />')
186 .addClass('input-group')
187 .append($('<input />')
188 .addClass('form-control')
189 .attr('type', 'text')
190 .prop('readonly', true)
191 .click({ self: this, index: i }, this._show)
192 .val('%dBit %s - %s'.format(k.bits, k.type, k.comment || '?')))
193 .append($('<span />')
194 .addClass('input-group-btn')
195 .append($('<button />')
196 .addClass('btn btn-danger')
197 .attr('title', L.tr('Remove public key'))
198 .text('–')
199 .click({ self: this, div: div, index: i }, this._remove)))
200 .appendTo(div);
201 }
202
203 if (this._keys.length > 0)
204 $('<br />').appendTo(div);
205
206 L.ui.button(L.tr('Add public key …'), 'success')
207 .click({ self: this, div: div }, this._add)
208 .appendTo(div);
209 },
210
211 widget: function(sid)
212 {
213 this._keys = [ ];
214
215 for (var i = 0; i < this.options.keys.length; i++)
216 this._keys.push(this.options.keys[i]);
217
218 var d = $('<div />')
219 .attr('id', this.id(sid));
220
221 this._render(d);
222
223 return d;
224 },
225
226 changed: function(sid)
227 {
228 if (this.options.keys.length != this._keys.length)
229 return true;
230
231 for (var i = 0; i < this.options.keys.length; i++)
232 if (this.options.keys[i] != this._keys[i])
233 return true;
234
235 return false;
236 },
237
238 save: function(sid)
239 {
240 if (this.changed(sid))
241 {
242 this.options.keys = [ ];
243
244 for (var i = 0; i < this._keys.length; i++)
245 this.options.keys.push(this._keys[i]);
246
247 return L.system.setSSHKeys(this._keys);
248 }
249
250 return undefined;
251 }
252 }),
253
254 execute: function() {
255 var self = this;
256 return L.system.getSSHKeys().then(function(keys) {
257 var m = new L.cbi.Map('dropbear', {
258 caption: L.tr('SSH Access'),
259 description: L.tr('Dropbear offers SSH network shell access and an integrated SCP server'),
260 tabbed: true
261 });
262
263 var s1 = m.section(L.cbi.DummySection, '__password', {
264 caption: L.tr('Router Password'),
265 description: L.tr('Changes the administrator password for accessing the device'),
266 readonly: !self.options.acls.admin
267 });
268
269 var p1 = s1.option(L.cbi.PasswordValue, 'pass1', {
270 caption: L.tr('Password'),
271 optional: true
272 });
273
274 var p2 = s1.option(L.cbi.PasswordValue, 'pass2', {
275 caption: L.tr('Confirmation'),
276 optional: true,
277 datatype: function(v) {
278 var v1 = p1.formvalue('__password');
279 if (v1 && v1.length && v != v1)
280 return L.tr('Passwords must match!');
281 return true;
282 }
283 });
284
285 p1.save = function(sid) { };
286 p2.save = function(sid) {
287 var v1 = p1.formvalue(sid);
288 var v2 = p2.formvalue(sid);
289 if (v2 && v2.length > 0 && v1 == v2)
290 return L.system.setPassword('root', v2);
291 };
292
293
294 var s2 = m.section(L.cbi.DummySection, '__pubkeys', {
295 caption: L.tr('SSH-Keys'),
296 description: L.tr('Specifies public keys for passwordless SSH authentication'),
297 readonly: !self.options.acls.admin
298 });
299
300 var k = s2.option(self.PubkeyListValue, 'keys', {
301 caption: L.tr('Saved keys'),
302 keys: keys
303 });
304
305
306 var s3 = m.section(L.cbi.TypedSection, 'dropbear', {
307 caption: L.tr('SSH Server'),
308 description: L.tr('This sections define listening instances of the builtin Dropbear SSH server'),
309 addremove: true,
310 add_caption: L.tr('Add instance ...'),
311 readonly: !self.options.acls.admin,
312 collabsible: true
313 });
314
315 s3.option(L.cbi.NetworkList, 'Interface', {
316 caption: L.tr('Interface'),
317 description: L.tr('Listen only on the given interface or, if unspecified, on all')
318 });
319
320 s3.option(L.cbi.InputValue, 'Port', {
321 caption: L.tr('Port'),
322 description: L.tr('Specifies the listening port of this Dropbear instance'),
323 datatype: 'port',
324 placeholder: 22,
325 optional: true
326 });
327
328 s3.option(L.cbi.CheckboxValue, 'PasswordAuth', {
329 caption: L.tr('Password authentication'),
330 description: L.tr('Allow SSH password authentication'),
331 initial: true,
332 enabled: 'on',
333 disabled: 'off'
334 });
335
336 s3.option(L.cbi.CheckboxValue, 'RootPasswordAuth', {
337 caption: L.tr('Allow root logins with password'),
338 description: L.tr('Allow the root user to login with password'),
339 initial: true,
340 enabled: 'on',
341 disabled: 'off'
342 });
343
344 s3.option(L.cbi.CheckboxValue, 'GatewayPorts', {
345 caption: L.tr('Gateway ports'),
346 description: L.tr('Allow remote hosts to connect to local SSH forwarded ports'),
347 initial: false,
348 enabled: 'on',
349 disabled: 'off'
350 });
351
352 return m.insertInto('#map');
353 });
354 }
355 });