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