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