e02c3a7888cc0a71c57fae027e459a27bb79f2b0
[project/luci.git] / applications / luci-app-lxc / luasrc / view / lxc.htm
1 <%#
2
3 LuCI LXC module
4
5 Copyright (C) 2014, Cisco Systems, Inc.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Author: Petar Koretic <petar.koretic@sartura.hr>
14
15 -%>
16
17 <%
18 local fs = require "nixio"
19 local target = fs.uname().machine
20 %>
21
22 <style type="text/css">
23 table.cbi-section-table th,
24 table.cbi-section-table td,
25 .cbi-section-table-cell,
26 .cbi-input-select,
27 .cbi-input-text
28 {
29 text-align:left;
30 vertical-align:top;
31 padding-left:2px;
32 margin-right:auto;
33 margin-left:0px;
34 }
35
36 .cbi-input-select,
37 .cbi-input-text
38 {
39 width:15em;
40 }
41 </style>
42
43 <fieldset class="cbi-section">
44 <legend><%:Available Containers%></legend>
45 <div class="cbi-section-node">
46 <table id="t_lxc_list" class="cbi-section-table">
47 <tr class="cbi-section-table-titles">
48 <th class="cbi-section-table-cell"><%:Name%></th>
49 <th class="cbi-section-table-cell"><%:Status%></th>
50 <th class="cbi-section-table-cell"><%:Actions%></th>
51 </tr>
52 </table>
53 </div>
54 </fieldset>
55
56 <fieldset class="cbi-section">
57 <span id="lxc-list-output"></span>
58 </fieldset>
59
60 <hr/>
61 <fieldset class="cbi-section">
62 <legend><%:Create New Container%></legend>
63 <div class="cbi-section-node">
64 <table id="t_lxc_create" class="cbi-section-table">
65 <tr class="cbi-section-table-titles">
66 <th class="cbi-section-table-cell"><%:Name%></th>
67 <th class="cbi-section-table-cell"><%:Template%></th>
68 <th class="cbi-section-table-cell"><%:Actions%></th>
69 </tr>
70 <tr id="tr_holder">
71 <td>
72 <input class="cbi-input-text" type="text" id="tx_name" placeholder="<%:Enter new name%>" value='' />
73 </td>
74 <td>
75 <select id="s_template" class="cbi-input-select cbi-button">
76 </select>
77 </td>
78 <td>
79 <input type="button" id="bt_create" value="<%:Create%>" onclick="lxc_create(tr_holder)" class="cbi-button cbi-button-add" />
80 <span id="lxc-add-loader" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>
81 </td>
82 </tr>
83 </table>
84 </div>
85 </fieldset>
86
87 <fieldset class="cbi-section">
88 <span id="lxc-add-output"></span>
89 </fieldset>
90
91 <hr/>
92
93 <script type="text/javascript" src="<%=resource%>/cbi.js"></script>
94 <script type="text/javascript">
95 //<![CDATA[
96 window.img = { "red" : "<%=resource%>/cbi/red.gif", "green" : "<%=resource%>/cbi/green.gif", "purple" : "<%=resource%>/cbi/purple.gif" }
97 window.states = { "STOPPED" : "red", "RUNNING" : "green", "FROZEN" : "purple" }
98
99 var t_lxc_list = document.getElementById('t_lxc_list');
100 var loader_html = '<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" width="16" height="16" style="vertical-align:middle" />';
101 var timeout_msg = 0
102 var output_list = document.getElementById("lxc-list-output")
103 var output_add = document.getElementById("lxc-add-output")
104 var loader_add = document.getElementById("lxc-add-loader")
105
106 info_message(output_add, "Template download in progress, please be patient!", 10000)
107
108 function lxc_create(tr)
109 {
110 var lxc_name = tr.querySelector("#tx_name").value.replace(/[\s!@#$%^&*()+=\[\]{};':"\\|,<>\/?]/g,'')
111 var lxc_template = tr.querySelector("#s_template").value
112 var bt_create = tr.querySelector("#bt_create")
113
114 if (t_lxc_list.querySelector("[data-id='" + lxc_name + "']") != null)
115 return info_message(output_add, "Container with that name already exists!", 4000)
116
117 bt_create.disabled = true
118 output_add.innerHTML = ''
119
120 if (!lxc_name || !lxc_name.length)
121 {
122 bt_create.disabled = false
123 return info_message(output_add, "The 'Name' field must not be empty!", 4000)
124 }
125
126 if (!lxc_template)
127 {
128 bt_create.disabled = false
129 return info_message(output_add, "The 'Template' field must not be empty!", 4000)
130 }
131
132 loading(loader_add)
133
134 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_create/' + '%h/%h'.format(lxc_name, lxc_template) , null,
135 function(x)
136 {
137 bt_create.disabled = false
138
139 loading(loader_add, 0)
140
141 if (!x)
142 info_message(output_add, "Container creation failed!")
143 })
144 }
145
146 function lxc_create_template(lxc_name, lxc_state)
147 {
148 var info_row = t_lxc_list.querySelector("#empty")
149 if (info_row)
150 t_lxc_list.deleteRow(1)
151
152 var actions = ''
153 actions += '<input type="button" onclick="action_handler(this)" data-action="start" value="<%:Start%>" class="cbi-button cbi-button-apply" />'
154 actions += '&#160;<input type="button" onclick="action_handler(this)" data-action="stop" value="<%:Stop%>" class="cbi-button cbi-button-reset" />'
155 actions += '&#160;<input type="button" onclick="action_handler(this)" data-action="destroy" value="<%:Delete%>" class="cbi-button cbi-button-remove" />'
156 actions += '&#160;<select class="cbi-input-select cbi-button" onchange="action_more_handler(this)">\
157 <option selected="selected" disabled="disabled">more</option>\
158 <option>configure</option>\
159 <option>freeze</option>\
160 <option>unfreeze</option>\
161 <option>reboot</option>\
162 </select>'
163 actions += '<span data-loader="" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>'
164
165 var row = t_lxc_list.insertRow(-1)
166 var cell = row.insertCell(-1)
167 cell.innerHTML = '%q%h%q'.format("<strong>", lxc_name, "</strong>")
168 cell.width = "30%"
169 cell.setAttribute("data-id", lxc_name)
170
171 cell = row.insertCell(-1)
172 cell.width = "20%"
173 cell.innerHTML = "<img src='"+window.img[lxc_state]+"'/>"
174
175 cell = row.insertCell(-1)
176 cell.width = "50%"
177 cell.innerHTML = actions
178 }
179
180 function action_handler(self)
181 {
182 var action = self.getAttribute("data-action");
183 var bt_action = self
184 var lxc_name = self.parentNode.parentNode.children[0].getAttribute('data-id')
185 var status_img = self.parentNode.parentNode.querySelector('img')
186 var loader = self.parentNode.querySelector('[data-loader]')
187
188 bt_action.disabled = true
189
190 if (action == "stop")
191 {
192 loading(loader)
193
194 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
195 function(x, ec)
196 {
197 loading(loader, 0)
198 bt_action.disabled = false
199
200 if (!x || ec)
201 return info_message(output_list,"Action failed!")
202
203 set_status(status_img, "red")
204
205 });
206 }
207
208 else if (action == "start")
209 {
210 loading(loader)
211
212 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
213 function(x, data)
214 {
215 loading(loader, 0)
216 bt_action.disabled = false
217
218 if (!x /*|| ec */)
219 return info_message(output_list,"Action failed!")
220
221 set_status(status_img, "green")
222 });
223 }
224
225 else if (action == "destroy")
226 {
227 var tr = self.parentNode.parentNode
228 var img = tr.querySelector('img')
229 if (img.getAttribute('src') != window.img["red"])
230 {
231 bt_action.disabled = false
232 return info_message(output_list,"Container is still running!")
233 }
234
235 if (!confirm("This will completely remove a stopped LXC container from disk. Are you sure?"))
236 return
237
238 loading(loader)
239
240 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
241 function(x, ec)
242 {
243 loading(loader, 0)
244 bt_action.disabled = false
245
246 if (!x || ec)
247 return info_message(output_list,"Action failed!")
248
249 var row = self.parentNode.parentNode
250 row.parentNode.removeChild(row)
251
252 });
253 }
254 }
255
256 function lxc_configure_handler(self)
257 {
258 var td = self.parentNode
259 var textarea = td.querySelector('[data-id]')
260 var lxc_name = textarea.getAttribute('data-id')
261 var lxc_configuration = textarea.value
262
263 new XHR().post('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_set/' + lxc_name, "lxc_configuration=" + encodeURIComponent(lxc_configuration) ,
264 function(x)
265 {
266 if (!x || x.responseText != "0")
267 return info_message(output_list,"Action failed!")
268
269 info_message(output_list,"LXC configuration updated")
270 var row = td.parentNode
271 row.parentNode.removeChild(row)
272 })
273 }
274
275 function lxc_rename_template(lxc_name)
276 {
277 var h = '\
278 <input data-id="'+ lxc_name + '" type="text" placeholder="Enter new name" /> \
279 <input data-id="bt_confirm" onclick="lxc_rename_handler(this)" type="button" class="cbi-button" value="Confirm" />'
280
281 return h
282 }
283
284 function lxc_configure_template(lxc_name, lxc_configuration)
285 {
286 var h = '\
287 <textarea data-id="'+ lxc_name + '" rows="20" style="width:100%">'+ lxc_configuration +'</textarea> \
288 <input data-id="bt_confirm" onclick="lxc_configure_handler(this)" type="button" class="cbi-button" value="Confirm" />'
289
290 return h
291 }
292
293 function action_more_handler(self)
294 {
295 var lxc_name = self.parentNode.parentNode.querySelector('[data-id]').getAttribute('data-id')
296 var loader = self.parentNode.parentNode.querySelector('[data-loader]')
297 var option = self.options[self.selectedIndex].text
298
299 self.value = "more"
300
301 switch(option)
302 {
303 case "configure":
304 var tr = document.createElement('tr')
305 var row = self.parentNode.parentNode
306 var next_row = row.nextSibling
307 if (next_row && next_row.getAttribute('data-action') !== null)
308 row.parentNode.removeChild(next_row)
309
310 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_get/' + lxc_name, null,
311 function(x)
312 {
313 tr.innerHTML="<td colspan='" + row.cells.length + "'>" + lxc_configure_template(lxc_name, x.responseText) + "</td>"
314 tr.setAttribute('data-action','')
315 row.parentNode.insertBefore(tr, row.nextSibling)
316 })
317
318 break
319
320 case "freeze":
321 var tr = self.parentNode.parentNode
322 var img = tr.querySelector('img')
323 if(img.getAttribute('src') != window.img["green"])
324 return info_message(output_list,"Container is not running!")
325
326 loading(loader)
327 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
328 function(x, ec)
329 {
330 loading(loader, 0)
331 if (!x || ec)
332 return info_message(output_list,"Action failed!")
333
334 set_status(img, "purple")
335 })
336
337 break
338
339 case "unfreeze":
340 var tr = self.parentNode.parentNode
341 var img = tr.querySelector('img')
342
343 if(img.getAttribute('src') != window.img["purple"])
344 return info_message(output_list,"Container is not frozen!")
345
346 loading(loader)
347 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
348 function(x, ec)
349 {
350 loading(loader, 0)
351 if (!x || ec)
352 return info_message(output_list,"Action failed!")
353
354 set_status(img, "green")
355 })
356
357 break
358
359 case "reboot":
360 var tr = self.parentNode.parentNode
361 var img = tr.querySelector('img')
362 if(img.getAttribute('src') != window.img["green"])
363 return info_message(output_list,"Container is not running!")
364
365 if (!confirm("Are you sure?"))
366 return
367
368 loading(loader)
369 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
370 function(x, ec)
371 {
372 loading(loader, 0)
373 if (!x || ec)
374 return info_message(output_list,"Action failed!")
375
376 info_message(output_list,"LXC rebooted")
377 })
378 break
379 }
380
381 }
382
383 function set_empty(t_lxc_list)
384 {
385 if (document.getElementById('empty') !== null)
386 return
387
388 var row_count = t_lxc_list.rows.length;
389 while(--row_count) t_lxc_list.deleteRow(row_count);
390
391 var row = t_lxc_list.insertRow(-1);
392 row.id = 'empty'
393 var cell = row.insertCell(0);
394 cell.colSpan = 3;
395 cell.innerHTML = '<em><br />There are no containers available yet.</em>';
396 }
397
398 function set_empty_template()
399 {
400 var row_count = t_lxc_create.rows.length;
401 while(--row_count) t_lxc_create.deleteRow(row_count);
402
403 var row = t_lxc_create.insertRow(-1);
404 row.id = 'tr_holder'
405 var cell = row.insertCell(0);
406 cell.colSpan = 3;
407 cell.innerHTML = '<em><br />There are no templates for your architecture (<%=target%>) available, please select another containers URL.</em>';
408 }
409
410 function lxc_list_update()
411 {
412 XHR.poll(4, '<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/list', null,
413 function(x, data)
414 {
415 if (!x) return;
416
417 if (!data)
418 return set_empty(t_lxc_list)
419
420 var lxc_count = Object.keys(data).length
421 if (!lxc_count)
422 return set_empty(t_lxc_list)
423
424 if (document.getElementById('empty') !== null)
425 t_lxc_list.deleteRow(1);
426
427 var lxcs = t_lxc_list.querySelectorAll('td[data-id]')
428 var lxc_name_table = {}
429 for (var i = 0, len = lxcs.length; i < len; i++)
430 {
431 var lxc_name = lxcs[i].getAttribute('data-id')
432 if (!(lxc_name in data))
433 {
434 var row = t_lxc_list.querySelector("[data-id='" + lxc_name + "']").parentNode
435 row.parentNode.removeChild(row)
436 continue
437 }
438
439 lxc_name_table[lxc_name] = lxcs[i].parentNode.querySelector('img')
440 }
441
442 for(var key in data)
443 {
444 var lxc_name = key
445 var state = window.states[data[key]]
446
447 if (!(lxc_name in lxc_name_table))
448 lxc_create_template(lxc_name, state)
449
450 else if (state != get_status(lxc_name_table[lxc_name]))
451 set_status(lxc_name_table[lxc_name], state)
452 }
453
454 })
455 }
456
457 function loading(elem, state)
458 {
459 state = (typeof state === 'undefined') ? 1 : state
460
461 if (state === 1)
462 elem.innerHTML = loader_html
463 else
464 setTimeout(function() { elem.innerHTML = ''}, 1000)
465 }
466
467 function set_status(elem, state)
468 {
469 state = (typeof state === 'undefined') ? 1 : state
470
471 setTimeout(function() { elem.setAttribute('src', window.img[state])}, 300)
472 }
473
474 function get_status(elem)
475 {
476 var src = elem.getAttribute('src')
477
478 for (var i in img)
479 {
480 if (img[i] == src)
481 return i
482 }
483 }
484
485 function info_message(output, msg, timeout)
486 {
487 timeout = timeout || 3000
488 output.innerHTML = msg
489 clearTimeout(timeout_msg)
490 timeout_msg = setTimeout(function(){ output.innerHTML=""}, timeout);
491 }
492
493 lxc_list_update()
494
495 new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_get_downloadable', null,
496 function(x, data)
497 {
498 if (!x) return;
499
500 if (!data) return set_empty_template();
501
502 var lxc_count = Object.keys(data).length;
503 if (!lxc_count) return set_empty_template();
504
505 var select = document.getElementById("s_template");
506 for(var key in data)
507 {
508 var option = document.createElement('option');
509 option.value = data[key];
510 option.text = data[key].replace(/[_:]/g, ' ');
511 select.add(option, -1);
512 }
513 })
514 //]]>
515 </script>