luci2: move most RPC proxy function declarations into the views using them to reduce...
[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.views.SystemAdmin.setSSHKeys(this._keys);
248                         }
249
250                         return undefined;
251                 }
252         }),
253
254         getSSHKeys: L.rpc.declare({
255                 object: 'luci2.system',
256                 method: 'sshkeys_get',
257                 expect: { keys: [ ] }
258         }),
259
260         setSSHKeys: L.rpc.declare({
261                 object: 'luci2.system',
262                 method: 'sshkeys_set',
263                 params: [ 'keys' ]
264         }),
265
266         setPassword: L.rpc.declare({
267                 object: 'luci2.system',
268                 method: 'password_set',
269                 params: [ 'user', 'password' ]
270         }),
271
272         execute: function() {
273                 var self = this;
274                 return self.getSSHKeys().then(function(keys) {
275                         var m = new L.cbi.Map('dropbear', {
276                                 caption:     L.tr('SSH Access'),
277                                 description: L.tr('Dropbear offers SSH network shell access and an integrated SCP server'),
278                                 tabbed:      true
279                         });
280
281                         var s1 = m.section(L.cbi.DummySection, '__password', {
282                                 caption:     L.tr('Router Password'),
283                                 description: L.tr('Changes the administrator password for accessing the device'),
284                                 readonly:    !self.options.acls.admin
285                         });
286
287                         var p1 = s1.option(L.cbi.PasswordValue, 'pass1', {
288                                 caption:     L.tr('Password'),
289                                 optional:    true
290                         });
291
292                         var p2 = s1.option(L.cbi.PasswordValue, 'pass2', {
293                                 caption:     L.tr('Confirmation'),
294                                 optional:    true,
295                                 datatype:    function(v) {
296                                         var v1 = p1.formvalue('__password');
297                                         if (v1 && v1.length && v != v1)
298                                                 return L.tr('Passwords must match!');
299                                         return true;
300                                 }
301                         });
302
303                         p1.save = function(sid) { };
304                         p2.save = function(sid) {
305                                 var v1 = p1.formvalue(sid);
306                                 var v2 = p2.formvalue(sid);
307                                 if (v2 && v2.length > 0 && v1 == v2)
308                                         return L.system.setPassword('root', v2);
309                         };
310
311
312                         var s2 = m.section(L.cbi.DummySection, '__pubkeys', {
313                                 caption:     L.tr('SSH-Keys'),
314                                 description: L.tr('Specifies public keys for passwordless SSH authentication'),
315                                 readonly:    !self.options.acls.admin
316                         });
317
318                         var k = s2.option(self.PubkeyListValue, 'keys', {
319                                 caption:     L.tr('Saved keys'),
320                                 keys:        keys
321                         });
322
323
324                         var s3 = m.section(L.cbi.TypedSection, 'dropbear', {
325                                 caption:     L.tr('SSH Server'),
326                                 description: L.tr('This sections define listening instances of the builtin Dropbear SSH server'),
327                                 addremove:   true,
328                                 add_caption: L.tr('Add instance ...'),
329                                 readonly:    !self.options.acls.admin,
330                                 collabsible: true
331                         });
332
333                         s3.option(L.cbi.NetworkList, 'Interface', {
334                                 caption:     L.tr('Interface'),
335                                 description: L.tr('Listen only on the given interface or, if unspecified, on all')
336                         });
337
338                         s3.option(L.cbi.InputValue, 'Port', {
339                                 caption:     L.tr('Port'),
340                                 description: L.tr('Specifies the listening port of this Dropbear instance'),
341                                 datatype:    'port',
342                                 placeholder: 22,
343                                 optional:    true
344                         });
345
346                         s3.option(L.cbi.CheckboxValue, 'PasswordAuth', {
347                                 caption:     L.tr('Password authentication'),
348                                 description: L.tr('Allow SSH password authentication'),
349                                 initial:     true,
350                                 enabled:     'on',
351                                 disabled:    'off'
352                         });
353
354                         s3.option(L.cbi.CheckboxValue, 'RootPasswordAuth', {
355                                 caption:     L.tr('Allow root logins with password'),
356                                 description: L.tr('Allow the root user to login with password'),
357                                 initial:     true,
358                                 enabled:     'on',
359                                 disabled:    'off'
360                         });
361
362                         s3.option(L.cbi.CheckboxValue, 'GatewayPorts', {
363                                 caption:     L.tr('Gateway ports'),
364                                 description: L.tr('Allow remote hosts to connect to local SSH forwarded ports'),
365                                 initial:     false,
366                                 enabled:     'on',
367                                 disabled:    'off'
368                         });
369
370                         return m.insertInto('#map');
371                 });
372         }
373 });