luci-app-olsr: handle empty result for non-status tables
[oweals/luci.git] / modules / luci-mod-system / htdocs / luci-static / resources / view / system / sshkeys.js
1 SSHPubkeyDecoder.prototype = {
2         lengthDecode: function(s, off)
3         {
4                 var l = (s.charCodeAt(off++) << 24) |
5                                 (s.charCodeAt(off++) << 16) |
6                                 (s.charCodeAt(off++) <<  8) |
7                                  s.charCodeAt(off++);
8
9                 if (l < 0 || (off + l) > s.length)
10                         return -1;
11
12                 return l;
13         },
14
15         decode: function(s)
16         {
17                 var parts = s.split(/\s+/);
18                 if (parts.length < 2)
19                         return null;
20
21                 var key = null;
22                 try { key = atob(parts[1]); } catch(e) {}
23                 if (!key)
24                         return null;
25
26                 var off, len;
27
28                 off = 0;
29                 len = this.lengthDecode(key, off);
30
31                 if (len <= 0)
32                         return null;
33
34                 var type = key.substr(off + 4, len);
35                 if (type !== parts[0])
36                         return null;
37
38                 off += 4 + len;
39
40                 var len1 = off < key.length ? this.lengthDecode(key, off) : 0;
41                 if (len1 <= 0)
42                         return null;
43
44                 var curve = null;
45                 if (type.indexOf('ecdsa-sha2-') === 0) {
46                         curve = key.substr(off + 4, len1);
47
48                         if (!len1 || type.substr(11) !== curve)
49                                 return null;
50
51                         type = 'ecdsa-sha2';
52                         curve = curve.replace(/^nistp(\d+)$/, 'NIST P-$1');
53                 }
54
55                 off += 4 + len1;
56
57                 var len2 = off < key.length ? this.lengthDecode(key, off) : 0;
58                 if (len2 < 0)
59                         return null;
60
61                 if (len1 & 1)
62                         len1--;
63
64                 if (len2 & 1)
65                         len2--;
66
67                 var comment = parts.slice(2).join(' '),
68                     fprint = parts[1].length > 68 ? parts[1].substr(0, 33) + '…' + parts[1].substr(-34) : parts[1];
69
70                 switch (type)
71                 {
72                 case 'ssh-rsa':
73                         return { type: 'RSA', bits: len2 * 8, comment: comment, fprint: fprint };
74
75                 case 'ssh-dss':
76                         return { type: 'DSA', bits: len1 * 8, comment: comment, fprint: fprint };
77
78                 case 'ssh-ed25519':
79                         return { type: 'ECDH', curve: 'Curve25519', comment: comment, fprint: fprint };
80
81                 case 'ecdsa-sha2':
82                         return { type: 'ECDSA', curve: curve, comment: comment, fprint: fprint };
83
84                 default:
85                         return null;
86                 }
87         }
88 };
89
90 function SSHPubkeyDecoder() {}
91
92 function renderKeys(keys) {
93         var list = document.querySelector('.cbi-dynlist[name="sshkeys"]'),
94             decoder = new SSHPubkeyDecoder();
95
96         while (!matchesElem(list.firstElementChild, '.add-item'))
97                 list.removeChild(list.firstElementChild);
98
99         keys.forEach(function(key) {
100                 var pubkey = decoder.decode(key);
101                 if (pubkey)
102                         list.insertBefore(E('div', {
103                                 class: 'item',
104                                 click: removeKey,
105                                 'data-key': key
106                         }, [
107                                 E('strong', pubkey.comment || _('Unnamed key')), E('br'),
108                                 E('small', [
109                                         '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)),
110                                         E('br'), E('code', pubkey.fprint)
111                                 ])
112                         ]), list.lastElementChild);
113         });
114
115         if (list.firstElementChild === list.lastElementChild)
116                 list.insertBefore(E('p', _('No public keys present yet.')), list.lastElementChild);
117 }
118
119 function saveKeys(keys) {
120         L.showModal(_('Add key'), E('div', { class: 'spinning' }, _('Saving keys…')));
121         L.post('admin/system/admin/sshkeys/json', { keys: JSON.stringify(keys) }, function(xhr, keys) {
122                 renderKeys(keys);
123                 L.hideModal();
124         });
125 }
126
127 function addKey(ev) {
128         var decoder = new SSHPubkeyDecoder(),
129             list = findParent(ev.target, '.cbi-dynlist'),
130             input = list.querySelector('input[type="text"]'),
131             key = input.value.trim(),
132             pubkey = decoder.decode(key),
133             keys = [];
134
135         if (!key.length)
136                 return;
137
138         list.querySelectorAll('.item').forEach(function(item) {
139                 keys.push(item.getAttribute('data-key'));
140         });
141
142         if (keys.indexOf(key) !== -1) {
143                 L.showModal(_('Add key'), [
144                         E('div', { class: 'alert-message warning' }, _('The given SSH public key has already been added.')),
145                         E('div', { class: 'right' }, E('div', { class: 'btn', click: L.hideModal }, _('Close')))
146                 ]);
147         }
148         else if (!pubkey) {
149                 L.showModal(_('Add key'), [
150                         E('div', { class: 'alert-message warning' }, _('The given SSH public key is invalid. Please supply proper public RSA or ECDSA keys.')),
151                         E('div', { class: 'right' }, E('div', { class: 'btn', click: L.hideModal }, _('Close')))
152                 ]);
153         }
154         else {
155                 keys.push(key);
156                 saveKeys(keys);
157                 input.value = '';
158         }
159 }
160
161 function removeKey(ev) {
162         var list = findParent(ev.target, '.cbi-dynlist'),
163             delkey = ev.target.getAttribute('data-key'),
164             keys = [];
165
166         list.querySelectorAll('.item').forEach(function(item) {
167                 var key = item.getAttribute('data-key');
168                 if (key !== delkey)
169                         keys.push(key);
170         });
171
172         L.showModal(_('Delete key'), [
173                 E('div', _('Do you really want to delete the following SSH key?')),
174                 E('pre', delkey),
175                 E('div', { class: 'right' }, [
176                         E('div', { class: 'btn', click: L.hideModal }, _('Cancel')),
177                         ' ',
178                         E('div', { class: 'btn danger', click: function(ev) { saveKeys(keys) } }, _('Delete key')),
179                 ])
180         ]);
181 }
182
183 function dragKey(ev) {
184         ev.stopPropagation();
185         ev.preventDefault();
186         ev.dataTransfer.dropEffect = 'copy';
187 }
188
189 function dropKey(ev) {
190         var file = ev.dataTransfer.files[0],
191             input = ev.currentTarget.querySelector('input[type="text"]'),
192             reader = new FileReader();
193
194         if (file) {
195                 reader.onload = function(rev) {
196                         input.value = rev.target.result.trim();
197                         addKey(ev);
198                         input.value = '';
199                 };
200
201                 reader.readAsText(file);
202         }
203
204         ev.stopPropagation();
205         ev.preventDefault();
206 }
207
208 window.addEventListener('dragover', function(ev) { ev.preventDefault() });
209 window.addEventListener('drop', function(ev) { ev.preventDefault() });
210
211 requestAnimationFrame(function() {
212         L.get('admin/system/admin/sshkeys/json', null, function(xhr, keys) {
213                 renderKeys(keys);
214         });
215 });