mirror of
https://github.com/openwrt/luci.git
synced 2026-04-15 10:51:51 +00:00
luci-app-lxc: convert to JS
migrate away from the old luci-app lua control system. See: https://github.com/openwrt/luci/issues/7310 Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (C) 2017 Dan Luedtke <mail@danrl.com>
|
||||
# Copyright (C) 2026 Paul Donald <newtwen+github@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
@@ -7,15 +7,14 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LXC management Web UI
|
||||
LUCI_DEPENDS:=@!arc +luci-compat +luci-base +lxc +lxc-attach +lxc-console +lxc-create +liblxc +rpcd-mod-lxc +getopt +!LXC_BUSYBOX_OPTIONS:tar
|
||||
LUCI_DEPENDS:=@!arc +luci-base +lxc +lxc-attach +lxc-console +lxc-create +liblxc +rpcd-mod-lxc +getopt +!LXC_BUSYBOX_OPTIONS:tar
|
||||
|
||||
define Package/luci-app-lxc/conffiles
|
||||
/etc/config/lxc
|
||||
endef
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Petar Koretic <petar.koretic@sartura.hr>, \
|
||||
Dirk Brenken <dev@brenken.org>
|
||||
PKG_MAINTAINER:=Paul Donald <newtwen+github@gmail.com>
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 310 B |
Binary file not shown.
|
Before Width: | Height: | Size: 317 B |
Binary file not shown.
|
Before Width: | Height: | Size: 320 B |
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
* Copyright (c) 2026. All Rights Reserved.
|
||||
* Paul Donald <newtwen+github@gmail.com>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
'require rpc';
|
||||
'require form';
|
||||
'require network';
|
||||
'require uci';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
return L.view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
|
||||
]);
|
||||
},
|
||||
|
||||
imagepanel() {
|
||||
|
||||
window.img = { 'red' : '🟥', 'green' : '🟩', 'blue' : '🟦' };
|
||||
window.states = { 'STOPPED' : 'red', 'RUNNING' : 'green', 'FROZEN' : 'blue' };
|
||||
|
||||
const t_lxc_list = document.getElementById('t_lxc_list');
|
||||
const loader_html = `<img src='${L.resource('icons/loading.svg')}' alt='loading' width='16' height='16' style='vertical-align:middle' />`;
|
||||
const output_list = document.getElementById('lxc-list-output');
|
||||
const output_add = document.getElementById('lxc-add-output');
|
||||
const loader_add = document.getElementById('lxc-add-loader');
|
||||
const div_create = document.getElementById('div_create');
|
||||
const bt_create = div_create.querySelector('#bt_create');
|
||||
|
||||
bt_create.disabled = true;
|
||||
info_message(output_add, _('Template download in progress, please be patient!'));
|
||||
bt_create.addEventListener('click', lxc_create);
|
||||
|
||||
function lxc_create() {
|
||||
const lxc_name = div_create.querySelector('#tx_name').value.replace(/[\s!@#$%^&*()+=[\]{};':'\\|,<>/?]/g,'');
|
||||
const lxc_template = div_create.querySelector('#s_template').value;
|
||||
|
||||
if (t_lxc_list.querySelector(`[data-id="${lxc_name}"]`) != null) {
|
||||
return info_message(output_add, _('Container with that name already exists!'), 2000);
|
||||
}
|
||||
|
||||
bt_create.disabled = true;
|
||||
output_add.innerHTML = '';
|
||||
|
||||
if (!lxc_template) {
|
||||
return set_no_template();
|
||||
}
|
||||
|
||||
if (!lxc_name || !lxc_name.length) {
|
||||
bt_create.disabled = false;
|
||||
return info_message(output_add, _('The Name field must not be empty!'), 2000);
|
||||
}
|
||||
|
||||
loading(loader_add);
|
||||
|
||||
new window.XHR().get(`/cgi-bin/luci/admin/services/lxc/lxc_create/${lxc_name}/${lxc_template}`, null,
|
||||
function(x) {
|
||||
bt_create.disabled = false;
|
||||
loading(loader_add, 0);
|
||||
|
||||
if (!x) {
|
||||
info_message(output_add, _('Container creation failed!'), 2000);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function lxc_create_template(lxc_name, lxc_state) {
|
||||
if (document.getElementById(lxc_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
info_message(output_list, '');
|
||||
let actions = '';
|
||||
actions += `<input type='button' onclick='action_handler(this)' data-action='start' value='${_('Start')}' class='cbi-button cbi-button-apply' />`;
|
||||
actions += ` <input type='button' onclick='action_handler(this)' data-action='stop' value='${_('Stop')}' class='cbi-button cbi-button-reset' />`;
|
||||
actions += ` <input type='button' onclick='action_handler(this)' data-action='destroy' value='${_('Delete')}' class='cbi-button cbi-button-remove' />`;
|
||||
actions += ` <select class='cbi-input-select cbi-button' style='width:10em' onchange='action_more_handler(this)'>\
|
||||
<option selected='selected' disabled='disabled'>more</option>\
|
||||
<option>configure</option>\
|
||||
<option>freeze</option>\
|
||||
<option>unfreeze</option>\
|
||||
<option>reboot</option>\
|
||||
</select>`;
|
||||
actions += `<span data-loader='' style='display:inline-block; width:16px; height:16px; margin:0 5px'></span>`;
|
||||
|
||||
const div0 = document.createElement('div');
|
||||
div0.className = 'tr cbi-section-table-row';
|
||||
div0.id = lxc_name;
|
||||
div0.setAttribute('data-id', lxc_name);
|
||||
|
||||
const div1 = document.createElement('div');
|
||||
div1.className = 'td';
|
||||
div1.style.width = "30%";
|
||||
div1.setAttribute('data-id', lxc_name);
|
||||
div1.innerHTML = `<strong>${lxc_name}</strong>`;
|
||||
|
||||
const div2 = document.createElement('div');
|
||||
div2.className = 'td statusimg';
|
||||
div2.style.width = '20%';
|
||||
div2.innerHTML = window.img[lxc_state];
|
||||
|
||||
const div3 = document.createElement('div');
|
||||
div3.className = 'td';
|
||||
div3.style.width = '50%';
|
||||
div3.innerHTML = actions;
|
||||
|
||||
document.getElementById('t_lxc_list').appendChild(div0);
|
||||
div0.appendChild(div1);
|
||||
div0.appendChild(div2);
|
||||
div0.appendChild(div3);
|
||||
}
|
||||
|
||||
function action_handler(self) {
|
||||
const bt_action = self;
|
||||
const action = self.dataset['action'];
|
||||
const lxc_name = self.parentNode.parentNode.dataset['id'];
|
||||
const status_img = self.parentNode.parentNode.querySelector('.statusimg');
|
||||
const loader = self.parentNode.querySelector('[data-loader]');
|
||||
|
||||
bt_action.disabled = true;
|
||||
|
||||
if (action == 'stop') {
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
|
||||
function(x, ec) {
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || ec) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
set_status(status_img, 'red');
|
||||
});
|
||||
}
|
||||
else if (action == 'start') {
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
|
||||
function(x, data) {
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || data) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
set_status(status_img, 'green');
|
||||
});
|
||||
}
|
||||
else if (action == 'destroy') {
|
||||
const div = self.parentNode.parentNode;
|
||||
const img = div.querySelector('.statusimg');;
|
||||
|
||||
if (img.innerHTML != window.img['red']) {
|
||||
bt_action.disabled = false;
|
||||
return info_message(output_list, _('Container is still running!'), 2000);
|
||||
}
|
||||
|
||||
if (!confirm(_('This will completely remove a stopped LXC container from disk. Are you sure?'))) {
|
||||
bt_action.disabled = false;
|
||||
return;
|
||||
}
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(action, lxc_name)), null,
|
||||
function(x, ec) {
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || ec) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
const div = self.parentNode.parentNode;
|
||||
div.parentNode.removeChild(div);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function lxc_configure_handler(self) {
|
||||
const div = self.parentNode;
|
||||
const textarea = div.querySelector('[data-id]');
|
||||
const lxc_name = textarea.dataset['id'];
|
||||
const lxc_conf = textarea.value;
|
||||
|
||||
new window.XHR().post(L.url('admin/services/lxc/lxc_configuration_set/' + lxc_name), {'lxc_conf': encodeURIComponent(lxc_conf)},
|
||||
function(x) {
|
||||
if (!x || x.responseText != '0') {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
info_message(output_list, _('LXC configuration updated'), 2000);
|
||||
var rmdiv = div.parentNode;
|
||||
rmdiv.parentNode.removeChild(rmdiv);
|
||||
})
|
||||
}
|
||||
|
||||
function lxc_configure_template(lxc_name, lxc_conf) {
|
||||
const h =
|
||||
`<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>` +
|
||||
`<input data-id="bt_confirm" onclick="lxc_configure_handler(this)" type="button" class="cbi-button" value="${_('Confirm')}" />`;
|
||||
return h;
|
||||
}
|
||||
|
||||
function action_more_handler(self) {
|
||||
const lxc_name = self.parentNode.parentNode.dataset['id'];
|
||||
const loader = self.parentNode.querySelector('[data-loader]');
|
||||
const option = self.options[self.selectedIndex].text;
|
||||
self.value = 'more';
|
||||
|
||||
let img;
|
||||
|
||||
const div0 = document.createElement('div');
|
||||
const div1 = self.parentNode.parentNode;
|
||||
const next_div = div1.nextSibling;
|
||||
|
||||
switch(option) {
|
||||
case 'configure':
|
||||
if (next_div && next_div.dataset['action'] !== null) {
|
||||
div1.parentNode.removeChild(next_div);
|
||||
}
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_configuration_get/' + lxc_name), null,
|
||||
function(x) {
|
||||
div0.innerHTML=`<div>${lxc_configure_template(lxc_name, x.responseText)}</div>`;
|
||||
div0.setAttribute('data-action','');
|
||||
div1.parentNode.insertBefore(div0, div1.nextSibling);
|
||||
})
|
||||
break;
|
||||
|
||||
case 'freeze':
|
||||
img = self.parentNode.parentNode.querySelector('.statusimg');
|
||||
if(img.innerHTML != window.img['green']) {
|
||||
return info_message(output_list, _('Container is not running!'), 2000);
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
|
||||
function(x, ec) {
|
||||
loading(loader, 0)
|
||||
if (!x || ec) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
set_status(img, 'blue');
|
||||
})
|
||||
break;
|
||||
|
||||
case 'unfreeze':
|
||||
img = self.parentNode.parentNode.querySelector('.statusimg');
|
||||
if(img.innerHTML != window.img['blue']) {
|
||||
return info_message(output_list, _('Container is not frozen!'), 2000);
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
|
||||
function(x, ec) {
|
||||
loading(loader, 0);
|
||||
if (!x || ec) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
set_status(img, 'green');
|
||||
})
|
||||
break;
|
||||
|
||||
case 'reboot':
|
||||
img = self.parentNode.parentNode.querySelector('.statusimg');
|
||||
if(img.innerHTML != window.img['green']) {
|
||||
return info_message(output_list, _('Container is not running!'), 2000);
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_action/%h/%h'.format(option, lxc_name)), null,
|
||||
function(x, ec) {
|
||||
loading(loader, 0)
|
||||
if (!x || ec) {
|
||||
return info_message(output_list, _('Action failed!'), 2000);
|
||||
}
|
||||
info_message(output_list, _('LXC container rebooted'), 2000);
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function set_no_container() {
|
||||
info_message(output_list, _('There are no containers available yet.'));
|
||||
}
|
||||
|
||||
function set_no_template() {
|
||||
bt_create.disabled = true;
|
||||
info_message(output_add, _('There are no templates for your architecture available.') + ' ' +
|
||||
_('Please select another containers URL.'));
|
||||
}
|
||||
|
||||
function lxc_list_update() {
|
||||
window.XHR.poll(4, L.url('admin/services/lxc/lxc_action/list'), null,
|
||||
function(x, data) {
|
||||
if (!x || !data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const lxc_count = Object.keys(data).length;
|
||||
if (!lxc_count) {
|
||||
return set_no_container();
|
||||
}
|
||||
|
||||
const lxcs = t_lxc_list.querySelectorAll('.td[data-id]');
|
||||
const lxc_name_div = {};
|
||||
for (let i = 0, len = lxcs.length; i < len; i++) {
|
||||
const lxc_name = lxcs[i].dataset['id'];
|
||||
if (!(lxc_name in data)) {
|
||||
const div = t_lxc_list.querySelector(`[data-id="${lxc_name}"]`).parentNode;
|
||||
div.parentNode.removeChild(div);
|
||||
continue;
|
||||
}
|
||||
lxc_name_div[lxc_name] = lxcs[i].parentNode.querySelector('.statusimg');
|
||||
}
|
||||
|
||||
for(let key in data) {
|
||||
const lxc_name = key;
|
||||
const state = window.states[data[key]];
|
||||
|
||||
if (!(lxc_name in lxc_name_div))
|
||||
{
|
||||
lxc_create_template(lxc_name, state);
|
||||
}
|
||||
else if (state != get_status(lxc_name_div[lxc_name]))
|
||||
{
|
||||
set_status(lxc_name_div[lxc_name], state);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loading(elem, state) {
|
||||
state = (typeof state === 'undefined') ? 1 : state;
|
||||
if (state === 1) {
|
||||
elem.innerHTML = loader_html;
|
||||
}
|
||||
else {
|
||||
setTimeout(function() { elem.innerHTML = ''}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function set_status(elem, state) {
|
||||
if (!elem || typeof elem.setAttribute !== 'function') {
|
||||
console.warn('set_status: invalid element for', state, elem);
|
||||
return;
|
||||
}
|
||||
|
||||
state = (typeof state === 'undefined') ? 1 : state;
|
||||
setTimeout(function() { elem.innerHTML = window.img[state] }, 300);
|
||||
}
|
||||
|
||||
function get_status(elem) {
|
||||
if (!elem || typeof elem.getAttribute !== 'function') {
|
||||
console.warn('get_status: invalid element', elem);
|
||||
return undefined;
|
||||
}
|
||||
const src = elem.innerHTML;
|
||||
for (let i in window.img) {
|
||||
if (window.img[i] == src) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function info_message(output, msg, timeout) {
|
||||
timeout = timeout || 0;
|
||||
output.innerHTML = '<em>' + msg + '</em>';
|
||||
if (timeout > 0) {
|
||||
setTimeout(function(){ output.innerHTML=''}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// expose handlers for generated inline attributes (keeps parity with legacy Lua view)
|
||||
window.action_handler = action_handler;
|
||||
window.action_more_handler = action_more_handler;
|
||||
window.lxc_configure_handler = lxc_configure_handler;
|
||||
window.lxc_create = lxc_create;
|
||||
|
||||
new window.XHR().get(L.url('admin/services/lxc/lxc_get_downloadable'), null,
|
||||
function(x, data) {
|
||||
if (!x) return;
|
||||
|
||||
if (!data) return set_no_template();
|
||||
|
||||
|
||||
var lxc_count = Object.keys(data).length;
|
||||
if (!lxc_count) return set_no_template();
|
||||
|
||||
var select = document.getElementById('s_template');
|
||||
for(var key in data) {
|
||||
var option = document.createElement('option');
|
||||
option.value = data[key];
|
||||
option.text = data[key].replace(/[_:]/g, ' ');
|
||||
select.add(option, -1);
|
||||
}
|
||||
|
||||
info_message(output_add, '');
|
||||
bt_create.disabled = false;
|
||||
})
|
||||
|
||||
lxc_list_update();
|
||||
|
||||
},
|
||||
|
||||
/** @private */
|
||||
populateBasicOptions(s, tab) {
|
||||
let o;
|
||||
|
||||
o = s.taboption(tab, form.Value, 'url', _('Containers URL'))
|
||||
o.value('images.linuxcontainers.org')
|
||||
o.value('repo.turris.cz/lxc', 'repo.turris.cz/lxc (SSL req.)')
|
||||
o.default = 'images.linuxcontainers.org'
|
||||
o.rmempty = false
|
||||
|
||||
o = s.taboption(tab, form.Value, 'min_space', _('Free Space Threshold'),
|
||||
_('Minimum required free space for LXC Container creation in KB'))
|
||||
o.default = '100000'
|
||||
o.datatype = 'min(50000)'
|
||||
o.rmempty = false
|
||||
|
||||
o = s.taboption(tab, form.Value, 'min_temp', _('Free Temp Threshold'),
|
||||
_('Minimum required free temp space for LXC Container creation in KB'))
|
||||
o.default = '100000'
|
||||
o.datatype = 'min(50000)'
|
||||
o.rmempty = false
|
||||
|
||||
},
|
||||
|
||||
/** @private */
|
||||
populateOptions(s) {
|
||||
|
||||
s.tab('basic', _('Basic Settings'));
|
||||
this.populateBasicOptions(s, 'basic');
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
let m, s;
|
||||
|
||||
m = new form.Map('lxc', _('LXC Containers'),
|
||||
_('<b>Please note:</b> LXC Containers require features not available on OpenWrt images for devices with small flash.') + '<br /> ' +
|
||||
_("Also you may want to install 'kmod-veth' for optional network support."));
|
||||
|
||||
s = m.section(form.TypedSection, 'lxc');
|
||||
s.addremove = false;
|
||||
s.anonymous = true;
|
||||
|
||||
let lxc_list = E('div', {'class': 'cbi-section'}, [
|
||||
E('h3', {}, [_('Available Containers')]),
|
||||
E('div', {'class': 'cbi-section-node'}, [
|
||||
E('div', { 'class': 'table cbi-section-table', id: 't_lxc_list' }, [
|
||||
E('div', { 'class': 'tr cbi-section-table-titles' }, [
|
||||
E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Name') ]),
|
||||
E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Status') ]),
|
||||
E('div', { 'class': 'th cbi-section-table-cell' }, [ _('Actions') ]),
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
let lxc_output= E('div', { 'class': 'cbi-section' }, [
|
||||
E('span', { 'id': 'lxc-list-output' }, [
|
||||
|
||||
])
|
||||
]);
|
||||
|
||||
let create_new = E('div', {'class': 'cbi-section'}, [
|
||||
E('h3', {}, [_('Create New Container')]),
|
||||
E('div', {'class': 'cbi-section-node'}, [
|
||||
E('table', { 'class': 'table cbi-section-table', id: 't_lxc_create' }, [
|
||||
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
|
||||
E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Name') ]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Status') ]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell' }, [ _('Actions') ]),
|
||||
]),
|
||||
E('tr', { 'class': 'tr cbi-section-table-row', id: 'div_create' }, [
|
||||
E('td', { 'class': 'td cbi-section-table-titles' }, [
|
||||
E('input', { id: 'tx_name', 'class': 'cbi-input-text', type: 'text', placeholder: _('Enter new name') }),
|
||||
]),
|
||||
E('td', { }, [
|
||||
E('select', { id: 's_template', 'class': 'cbi-input-select cbi-button' }),
|
||||
]),
|
||||
E('td', { }, [
|
||||
E('button', { id: 'bt_create', 'class': 'cbi-button cbi-button-add', disabled: false }, [ _('Create') ]),
|
||||
E('span', { 'id': 'lxc-add-loader' }, [ ]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
]);
|
||||
|
||||
let create_output = E('span', { id: 'lxc-add-output' }, []);
|
||||
|
||||
this.populateOptions(s);
|
||||
|
||||
return m.render().then(node => {
|
||||
document.getElementById('tabmenu').append(node);
|
||||
document.getElementById('tabmenu').append(lxc_list);
|
||||
document.getElementById('tabmenu').append(lxc_output);
|
||||
document.getElementById('tabmenu').append(create_new);
|
||||
document.getElementById('tabmenu').append(create_output);
|
||||
this.imagepanel();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,181 +0,0 @@
|
||||
--[[
|
||||
|
||||
LuCI LXC module
|
||||
|
||||
Copyright (C) 2014, Cisco Systems, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Author: Petar Koretic <petar.koretic@sartura.hr>
|
||||
|
||||
]]--
|
||||
|
||||
module("luci.controller.lxc", package.seeall)
|
||||
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local util = require "luci.util"
|
||||
local nx = require "nixio"
|
||||
local url = util.shellquote(uci:get("lxc", "lxc", "url"))
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/lxc") then
|
||||
return
|
||||
end
|
||||
|
||||
page = node("admin", "services", "lxc")
|
||||
page.target = cbi("lxc")
|
||||
page.title = _("LXC Containers")
|
||||
page.order = 70
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
|
||||
page = entry({"admin", "services", "lxc_create"}, call("lxc_create"), nil)
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
page.leaf = true
|
||||
|
||||
page = entry({"admin", "services", "lxc_action"}, call("lxc_action"), nil)
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
page.leaf = true
|
||||
|
||||
page = entry({"admin", "services", "lxc_get_downloadable"}, call("lxc_get_downloadable"), nil)
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
page.leaf = true
|
||||
|
||||
page = entry({"admin", "services", "lxc_configuration_get"}, call("lxc_configuration_get"), nil)
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
page.leaf = true
|
||||
|
||||
page = entry({"admin", "services", "lxc_configuration_set"}, call("lxc_configuration_set"), nil)
|
||||
page.acl_depends = { "luci-app-lxc" }
|
||||
page.leaf = true
|
||||
end
|
||||
|
||||
function lxc_get_downloadable()
|
||||
local target = lxc_get_arch_target(url)
|
||||
local templates = {}
|
||||
|
||||
local f = io.popen('sh /usr/share/lxc/templates/lxc-download --list --server %s 2>/dev/null'
|
||||
%{ url }, 'r')
|
||||
local line
|
||||
for line in f:lines() do
|
||||
local dist, version, dist_target = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+default%s+(%S+)%s*$")
|
||||
if dist and version and dist_target and dist_target == target then
|
||||
templates[#templates+1] = "%s:%s" %{ dist, version }
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(templates)
|
||||
end
|
||||
|
||||
function lxc_create(lxc_name, lxc_template)
|
||||
luci.http.prepare_content("text/plain")
|
||||
local path = lxc_get_config_path()
|
||||
if not path then
|
||||
return
|
||||
end
|
||||
|
||||
local lxc_dist, lxc_release = lxc_template:match("^(.+):(.+)$")
|
||||
luci.sys.call('/usr/bin/lxc-create --quiet --name %s --bdev best --template download -- --dist %s --release %s --arch %s --server %s'
|
||||
%{ lxc_name, lxc_dist, lxc_release, lxc_get_arch_target(url), url })
|
||||
|
||||
while (nx.fs.access(path .. lxc_name .. "/partial")) do
|
||||
nx.nanosleep(1)
|
||||
end
|
||||
|
||||
luci.http.write("0")
|
||||
end
|
||||
|
||||
function lxc_action(lxc_action, lxc_name)
|
||||
local data, ec = util.ubus("lxc", lxc_action, lxc_name and { name = lxc_name } or {})
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(ec and {} or data)
|
||||
end
|
||||
|
||||
function lxc_get_config_path()
|
||||
local f = io.open("/etc/lxc/lxc.conf", "r")
|
||||
local content = f:read("*all")
|
||||
f:close()
|
||||
|
||||
local ret = content:match('^%s*lxc.lxcpath%s*=%s*([^%s]*)')
|
||||
if ret then
|
||||
if nx.fs.access(ret) then
|
||||
local min_space = tonumber(uci:get("lxc", "lxc", "min_space")) or 100000
|
||||
local free_space = tonumber(util.exec("df " ..ret.. " | awk '{if(NR==2)print $4}'"))
|
||||
if free_space and free_space >= min_space then
|
||||
local min_temp = tonumber(uci:get("lxc", "lxc", "min_temp")) or 100000
|
||||
local free_temp = tonumber(util.exec("df /tmp | awk '{if(NR==2)print $4}'"))
|
||||
if free_temp and free_temp >= min_temp then
|
||||
return ret .. "/"
|
||||
else
|
||||
util.perror("lxc error: not enough temporary space (< " ..min_temp.. " KB)")
|
||||
end
|
||||
else
|
||||
util.perror("lxc error: not enough space (< " ..min_space.. " KB)")
|
||||
end
|
||||
else
|
||||
util.perror("lxc error: directory not found")
|
||||
end
|
||||
else
|
||||
util.perror("lxc error: config path is empty")
|
||||
end
|
||||
end
|
||||
|
||||
function lxc_configuration_get(lxc_name)
|
||||
luci.http.prepare_content("text/plain")
|
||||
|
||||
local f = io.open(lxc_get_config_path() .. lxc_name .. "/config", "r")
|
||||
local content = f:read("*all")
|
||||
f:close()
|
||||
|
||||
luci.http.write(content)
|
||||
end
|
||||
|
||||
function lxc_configuration_set(lxc_name)
|
||||
luci.http.prepare_content("text/plain")
|
||||
|
||||
local lxc_configuration = luci.http.formvalue("lxc_conf")
|
||||
lxc_configuration = luci.http.urldecode(lxc_configuration, true)
|
||||
if lxc_configuration == nil then
|
||||
util.perror("lxc error: config formvalue is empty")
|
||||
return
|
||||
end
|
||||
|
||||
local f, err = io.open(lxc_get_config_path() .. lxc_name .. "/config","w+")
|
||||
if not f then
|
||||
util.perror("lxc error: config file not found")
|
||||
return
|
||||
end
|
||||
|
||||
f:write(lxc_configuration)
|
||||
f:close()
|
||||
|
||||
luci.http.write("0")
|
||||
end
|
||||
|
||||
function lxc_get_arch_target(url)
|
||||
local target = nx.uname().machine
|
||||
if url and url:match("images.linuxcontainers.org") then
|
||||
local target_map = {
|
||||
armv5 = "armel",
|
||||
armv6 = "armel",
|
||||
armv7 = "armhf",
|
||||
armv8 = "arm64",
|
||||
aarch64 = "arm64",
|
||||
i686 = "i386",
|
||||
x86_64 = "amd64"
|
||||
}
|
||||
local k, v
|
||||
for k, v in pairs(target_map) do
|
||||
if target:find(k) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
return target
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
--[[
|
||||
|
||||
LuCI LXC module
|
||||
|
||||
Copyright (C) 2014, Cisco Systems, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Author: Petar Koretic <petar.koretic@sartura.hr>
|
||||
|
||||
]]--
|
||||
|
||||
m = Map("lxc", translate("LXC Containers"),
|
||||
translate("<b>Please note:</b> LXC Containers require features not available on OpenWrt images for devices with small flash.<br />")
|
||||
.. translate("Also you may want to install 'kmod-veth' for optional network support."))
|
||||
m:section(SimpleSection).template = "lxc"
|
||||
|
||||
s = m:section(TypedSection, "lxc", translate("Options"))
|
||||
s.anonymous = true
|
||||
|
||||
o1 = s:option(Value, "url", translate("Containers URL"))
|
||||
o1:value("images.linuxcontainers.org")
|
||||
o1:value("repo.turris.cz/lxc", "repo.turris.cz/lxc (SSL req.)")
|
||||
o1.default = "images.linuxcontainers.org"
|
||||
o1.rmempty = false
|
||||
|
||||
o3 = s:option(Value, "min_space", translate("Free Space Threshold"),
|
||||
translate("Minimum required free space for LXC Container creation in KB"))
|
||||
o3.default = "100000"
|
||||
o3.datatype = "min(50000)"
|
||||
o3.rmempty = false
|
||||
|
||||
o4 = s:option(Value, "min_temp", translate("Free Temp Threshold"),
|
||||
translate("Minimum required free temp space for LXC Container creation in KB"))
|
||||
o4.default = "100000"
|
||||
o4.datatype = "min(50000)"
|
||||
o4.rmempty = false
|
||||
|
||||
return m
|
||||
@@ -1,504 +0,0 @@
|
||||
<%#
|
||||
|
||||
LuCI LXC module
|
||||
|
||||
Copyright (C) 2014, Cisco Systems, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Author: Petar Koretic <petar.koretic@sartura.hr>
|
||||
|
||||
-%>
|
||||
|
||||
<%-
|
||||
local nx = require "nixio"
|
||||
local target = nx.uname().machine
|
||||
-%>
|
||||
|
||||
<div class="cbi-section">
|
||||
<h3><%:Available Containers%></h3>
|
||||
<div class="cbi-section-node">
|
||||
<div class="table cbi-section-table" id="t_lxc_list">
|
||||
<div class="tr cbi-section-table-titles">
|
||||
<div class="th cbi-section-table-cell"><%:Name%></div>
|
||||
<div class="th cbi-section-table-cell"><%:Status%></div>
|
||||
<div class="th cbi-section-table-cell"><%:Actions%></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section">
|
||||
<span id="lxc-list-output"></span>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="cbi-section">
|
||||
<h3><%:Create New Container%></h3>
|
||||
<div class="cbi-section-node">
|
||||
<div class="table cbi-section-table" id="t_lxc_create">
|
||||
<div class="tr cbi-section-table-titles">
|
||||
<div class="th cbi-section-table-cell"><%:Name%></div>
|
||||
<div class="th cbi-section-table-cell"><%:Template%></div>
|
||||
<div class="th cbi-section-table-cell"><%:Actions%></div>
|
||||
</div>
|
||||
<div class="tr cbi-section-table-row" id="div_create">
|
||||
<div class="td"><input class="cbi-input-text" type="text" id="tx_name" placeholder="<%:Enter new name%>" value='' /></div>
|
||||
<div class="td"><select id="s_template" class="cbi-input-select cbi-button"></select></div>
|
||||
<div class="td">
|
||||
<input type="button" id="bt_create" value="<%:Create%>" onclick="lxc_create()" class="cbi-button cbi-button-add" />
|
||||
<span id="lxc-add-loader" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section">
|
||||
<span id="lxc-add-output"></span>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<script>
|
||||
window.img = { "red" : "<%=resource%>/cbi/red.gif", "green" : "<%=resource%>/cbi/green.gif", "purple" : "<%=resource%>/cbi/purple.gif" };
|
||||
window.states = { "STOPPED" : "red", "RUNNING" : "green", "FROZEN" : "purple" };
|
||||
|
||||
var t_lxc_list = document.getElementById('t_lxc_list');
|
||||
var loader_html = '<img src="<%=resource%>/icons/loading.svg" alt="<%:Loading%>" width="16" height="16" style="vertical-align:middle" />';
|
||||
var output_list = document.getElementById("lxc-list-output");
|
||||
var output_add = document.getElementById("lxc-add-output");
|
||||
var loader_add = document.getElementById("lxc-add-loader");
|
||||
var div_create = document.getElementById("div_create");
|
||||
var bt_create = div_create.querySelector("#bt_create");
|
||||
|
||||
bt_create.disabled = true;
|
||||
info_message(output_add, "Template download in progress, please be patient!");
|
||||
|
||||
function lxc_create()
|
||||
{
|
||||
var lxc_name = div_create.querySelector("#tx_name").value.replace(/[\s!@#$%^&*()+=\[\]{};':"\\|,<>\/?]/g,'');
|
||||
var lxc_template = div_create.querySelector("#s_template").value;
|
||||
|
||||
if (t_lxc_list.querySelector("[data-id='" + lxc_name + "']") != null)
|
||||
{
|
||||
return info_message(output_add, "Container with that name already exists!", 2000);
|
||||
}
|
||||
|
||||
bt_create.disabled = true;
|
||||
output_add.innerHTML = '';
|
||||
|
||||
if (!lxc_template)
|
||||
{
|
||||
return set_no_template();
|
||||
}
|
||||
|
||||
if (!lxc_name || !lxc_name.length)
|
||||
{
|
||||
bt_create.disabled = false;
|
||||
return info_message(output_add, "The 'Name' field must not be empty!", 2000);
|
||||
}
|
||||
|
||||
loading(loader_add);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_create/' + '%h/%h'.format(lxc_name, lxc_template) , null,
|
||||
function(x)
|
||||
{
|
||||
bt_create.disabled = false;
|
||||
loading(loader_add, 0);
|
||||
|
||||
if (!x)
|
||||
{
|
||||
info_message(output_add, "Container creation failed!", 2000);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function lxc_create_template(lxc_name, lxc_state)
|
||||
{
|
||||
if (document.getElementById(lxc_name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info_message(output_list, "");
|
||||
var actions = '';
|
||||
actions += '<input type="button" onclick="action_handler(this)" data-action="start" value="<%:Start%>" class="cbi-button cbi-button-apply" />';
|
||||
actions += ' <input type="button" onclick="action_handler(this)" data-action="stop" value="<%:Stop%>" class="cbi-button cbi-button-reset" />';
|
||||
actions += ' <input type="button" onclick="action_handler(this)" data-action="destroy" value="<%:Delete%>" class="cbi-button cbi-button-remove" />';
|
||||
actions += ' <select class="cbi-input-select cbi-button" style="width:10em" onchange="action_more_handler(this)">\
|
||||
<option selected="selected" disabled="disabled">more</option>\
|
||||
<option>configure</option>\
|
||||
<option>freeze</option>\
|
||||
<option>unfreeze</option>\
|
||||
<option>reboot</option>\
|
||||
</select>';
|
||||
actions += '<span data-loader="" style="display:inline-block; width:16px; height:16px; margin:0 5px"></span>';
|
||||
|
||||
var div0 = document.createElement("div");
|
||||
div0.className = "tr cbi-section-table-row";
|
||||
div0.id = lxc_name;
|
||||
div0.setAttribute("data-id", lxc_name);
|
||||
|
||||
var div1 = document.createElement("div");
|
||||
div1.className = "td";
|
||||
div1.style.width = "30%";
|
||||
div1.innerHTML = '%q%h%q'.format("<strong>", lxc_name, "</strong>");
|
||||
|
||||
var div2 = document.createElement("div");
|
||||
div2.className = "td";
|
||||
div2.style.width = "20%";
|
||||
div2.innerHTML = "<img src='"+window.img[lxc_state]+"'/>";
|
||||
|
||||
var div3 = document.createElement("div");
|
||||
div3.className = "td";
|
||||
div3.style.width = "50%";
|
||||
div3.innerHTML = actions;
|
||||
|
||||
document.getElementById("t_lxc_list").appendChild(div0);
|
||||
div0.appendChild(div1);
|
||||
div0.appendChild(div2);
|
||||
div0.appendChild(div3);
|
||||
}
|
||||
|
||||
function action_handler(self)
|
||||
{
|
||||
var bt_action = self;
|
||||
var action = self.dataset['action'];
|
||||
var lxc_name = self.parentNode.parentNode.dataset['id'];
|
||||
var status_img = self.parentNode.parentNode.querySelector('img');
|
||||
var loader = self.parentNode.querySelector('[data-loader]');
|
||||
|
||||
bt_action.disabled = true;
|
||||
|
||||
if (action == "stop")
|
||||
{
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
|
||||
function(x, ec)
|
||||
{
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || ec)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
set_status(status_img, "red");
|
||||
});
|
||||
}
|
||||
else if (action == "start")
|
||||
{
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
|
||||
function(x, data)
|
||||
{
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || data)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
set_status(status_img, "green");
|
||||
});
|
||||
}
|
||||
else if (action == "destroy")
|
||||
{
|
||||
var div = self.parentNode.parentNode;
|
||||
var img = div.querySelector('img');
|
||||
|
||||
if (img.getAttribute('src') != window.img["red"])
|
||||
{
|
||||
bt_action.disabled = false;
|
||||
return info_message(output_list,"Container is still running!", 2000);
|
||||
}
|
||||
|
||||
if (!confirm("This will completely remove a stopped LXC container from disk. Are you sure?"))
|
||||
{
|
||||
bt_action.disabled = false;
|
||||
return;
|
||||
}
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(action, lxc_name), null,
|
||||
function(x, ec)
|
||||
{
|
||||
loading(loader, 0);
|
||||
bt_action.disabled = false;
|
||||
|
||||
if (!x || ec)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
var div = self.parentNode.parentNode;
|
||||
div.parentNode.removeChild(div);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function lxc_configure_handler(self)
|
||||
{
|
||||
var div = self.parentNode;
|
||||
var textarea = div.querySelector('[data-id]');
|
||||
var lxc_name = textarea.dataset['id'];
|
||||
var lxc_conf = textarea.value;
|
||||
|
||||
new XHR().post('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_set/' + lxc_name, {"lxc_conf": encodeURIComponent(lxc_conf)},
|
||||
function(x)
|
||||
{
|
||||
if (!x || x.responseText != "0")
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
info_message(output_list,"LXC configuration updated", 2000);
|
||||
var rmdiv = div.parentNode;
|
||||
rmdiv.parentNode.removeChild(rmdiv);
|
||||
})
|
||||
}
|
||||
|
||||
function lxc_configure_template(lxc_name, lxc_conf)
|
||||
{
|
||||
var h = '\
|
||||
<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> \
|
||||
<input data-id="bt_confirm" onclick="lxc_configure_handler(this)" type="button" class="cbi-button" value="Confirm" />';
|
||||
return h;
|
||||
}
|
||||
|
||||
function action_more_handler(self)
|
||||
{
|
||||
var lxc_name = self.parentNode.parentNode.dataset['id'];
|
||||
var loader = self.parentNode.querySelector('[data-loader]');
|
||||
var option = self.options[self.selectedIndex].text;
|
||||
self.value = "more";
|
||||
|
||||
switch(option)
|
||||
{
|
||||
case "configure":
|
||||
var div0 = document.createElement('div');
|
||||
var div1 = self.parentNode.parentNode;
|
||||
var next_div = div1.nextSibling;
|
||||
|
||||
if (next_div && next_div.dataset['action'] !== null)
|
||||
{
|
||||
div1.parentNode.removeChild(next_div);
|
||||
}
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_configuration_get/' + lxc_name, null,
|
||||
function(x)
|
||||
{
|
||||
div0.innerHTML="<div>" + lxc_configure_template(lxc_name, x.responseText) + "</div>";
|
||||
div0.setAttribute('data-action','');
|
||||
div1.parentNode.insertBefore(div0, div1.nextSibling);
|
||||
})
|
||||
break;
|
||||
|
||||
case "freeze":
|
||||
var img = self.parentNode.parentNode.querySelector('img');
|
||||
if(img.getAttribute('src') != window.img["green"])
|
||||
{
|
||||
return info_message(output_list,"Container is not running!", 2000);
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
|
||||
function(x, ec)
|
||||
{
|
||||
loading(loader, 0)
|
||||
if (!x || ec)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
set_status(img, "purple");
|
||||
})
|
||||
break;
|
||||
|
||||
case "unfreeze":
|
||||
var img = self.parentNode.parentNode.querySelector('img');
|
||||
if(img.getAttribute('src') != window.img["purple"])
|
||||
{
|
||||
return info_message(output_list,"Container is not frozen!", 2000);
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
|
||||
function(x, ec)
|
||||
{
|
||||
loading(loader, 0);
|
||||
if (!x || ec)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
set_status(img, "green");
|
||||
})
|
||||
break;
|
||||
|
||||
case "reboot":
|
||||
var img = self.parentNode.parentNode.querySelector('img');
|
||||
if(img.getAttribute('src') != window.img["green"])
|
||||
{
|
||||
return info_message(output_list,"Container is not running!", 2000);
|
||||
}
|
||||
|
||||
if (!confirm("Are you sure?"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
loading(loader);
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/' + '%h/%h'.format(option, lxc_name), null,
|
||||
function(x, ec)
|
||||
{
|
||||
loading(loader, 0)
|
||||
if (!x || ec)
|
||||
{
|
||||
return info_message(output_list,"Action failed!", 2000);
|
||||
}
|
||||
info_message(output_list,"LXC container rebooted", 2000);
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function set_no_container()
|
||||
{
|
||||
info_message(output_list, "There are no containers available yet.");
|
||||
}
|
||||
|
||||
function set_no_template()
|
||||
{
|
||||
bt_create.disabled = true;
|
||||
info_message(output_add, "There are no templates for your architecture (<%=target%>) available, please select another containers URL.");
|
||||
}
|
||||
|
||||
function lxc_list_update()
|
||||
{
|
||||
XHR.poll(4, '<%=luci.dispatcher.build_url("admin", "services")%>/lxc_action/list', null,
|
||||
function(x, data)
|
||||
{
|
||||
if (!x || !data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lxc_count = Object.keys(data).length;
|
||||
if (!lxc_count)
|
||||
{
|
||||
return set_no_container();
|
||||
}
|
||||
|
||||
var lxcs = t_lxc_list.querySelectorAll('td[data-id]');
|
||||
var lxc_name_div = {};
|
||||
for (var i = 0, len = lxcs.length; i < len; i++)
|
||||
{
|
||||
var lxc_name = lxcs[i].dataset['id'];
|
||||
if (!(lxc_name in data))
|
||||
{
|
||||
var div = t_lxc_list.querySelector("[data-id='" + lxc_name + "']").parentNode;
|
||||
div.parentNode.removeChild(div);
|
||||
continue;
|
||||
}
|
||||
lxc_name_div[lxc_name] = lxcs[i].parentNode.querySelector('img');
|
||||
}
|
||||
|
||||
for(var key in data)
|
||||
{
|
||||
var lxc_name = key;
|
||||
var state = window.states[data[key]];
|
||||
|
||||
if (!(lxc_name in lxc_name_div))
|
||||
{
|
||||
lxc_create_template(lxc_name, state);
|
||||
}
|
||||
else if (state != get_status(lxc_name_div[lxc_name]))
|
||||
{
|
||||
set_status(lxc_name_div[lxc_name], state);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loading(elem, state)
|
||||
{
|
||||
state = (typeof state === 'undefined') ? 1 : state;
|
||||
if (state === 1)
|
||||
{
|
||||
elem.innerHTML = loader_html;
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(function() { elem.innerHTML = ''}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function set_status(elem, state)
|
||||
{
|
||||
state = (typeof state === 'undefined') ? 1 : state;
|
||||
setTimeout(function() { elem.setAttribute('src', window.img[state])}, 300);
|
||||
}
|
||||
|
||||
function get_status(elem)
|
||||
{
|
||||
var src = elem.getAttribute('src');
|
||||
for (var i in img)
|
||||
{
|
||||
if (img[i] == src)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function info_message(output, msg, timeout)
|
||||
{
|
||||
timeout = timeout || 0;
|
||||
output.innerHTML = '<em>' + msg + '</em>';
|
||||
if (timeout > 0)
|
||||
{
|
||||
setTimeout(function(){ output.innerHTML=""}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
new XHR().get('<%=luci.dispatcher.build_url("admin", "services")%>/lxc_get_downloadable', null,
|
||||
function(x, data)
|
||||
{
|
||||
if (!x)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data)
|
||||
{
|
||||
return set_no_template();
|
||||
}
|
||||
|
||||
var lxc_count = Object.keys(data).length;
|
||||
if (!lxc_count)
|
||||
{
|
||||
return set_no_template();
|
||||
}
|
||||
|
||||
var select = document.getElementById("s_template");
|
||||
for(var key in data)
|
||||
{
|
||||
var option = document.createElement('option');
|
||||
option.value = data[key];
|
||||
option.text = data[key].replace(/[_:]/g, ' ');
|
||||
select.add(option, -1);
|
||||
}
|
||||
|
||||
info_message(output_add, "");
|
||||
bt_create.disabled = false;
|
||||
})
|
||||
|
||||
lxc_list_update();
|
||||
</script>
|
||||
@@ -14,7 +14,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Обърнете внимание:</b> LXC контейнерите изискват функции, които не са "
|
||||
"налични в изображенията на OpenWrt за устройства с малка флаш памет.<br />"
|
||||
"налични в изображенията на OpenWrt за устройства с малка флаш памет."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,11 +13,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Uvědomte si prosím:</b> LXC kontejnery vyžadují funkce, které nejsou k "
|
||||
"dispozici v obrazech OpenWrt, určených pro zařízení s malým úložištěm (flash "
|
||||
"paměť).<br />"
|
||||
"paměť)."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Bemærk venligst:</b> LXC Containers kræver funktioner, der ikke er "
|
||||
"tilgængelige på OpenWrt-aftryk for enheder med lille flash.<br />"
|
||||
"tilgængelige på OpenWrt-aftryk for enheder med lille flash."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Bitte beachten:</b> LXC-Container benötigen Funktionen, die bei OpenWrt-"
|
||||
"Images für Geräte mit kleinem Flashspeicher nicht verfügbar sind.<br />"
|
||||
"Images für Geräte mit kleinem Flashspeicher nicht verfügbar sind."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -16,11 +16,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Tenga en cuenta:</b> los contenedores LXC requieren funciones que no "
|
||||
"están disponibles en las imágenes OpenWrt para dispositivos con poco "
|
||||
"almacenamiento.<br />"
|
||||
"almacenamiento."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -10,7 +10,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>لطفاً توجه کنید:</b> کانتینرهای LXC به ویژگیهایی نیاز دارند که در ایمیج "
|
||||
"OpenWrt برای دستگاههای با حافظه فلش کوچک در دسترس نیستند.<br />"
|
||||
"OpenWrt برای دستگاههای با حافظه فلش کوچک در دسترس نیستند."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Huomaa:</b> LXC-kontit vaativat ominaisuuksia, jotka eivät ole "
|
||||
"käytettävissä OpenWrt-kuvissa laitteissa, joissa on pieni flash-muisti.<br />"
|
||||
"käytettävissä OpenWrt-kuvissa laitteissa, joissa on pieni flash-muisti."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,11 +13,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Note:</b> Les conteneurs LXC nécessitent des fonctionnalités non "
|
||||
"disponibles dans les images OpenWrt pour les matériels avec une mémoire "
|
||||
"flash limitée.<br />"
|
||||
"flash limitée."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,11 +14,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Tabhair faoi deara le do thoil:</b> Teastaíonn gnéithe nach bhfuil ar "
|
||||
"fáil ar íomhánna OpenWrt le haghaidh gléasanna le splanc bheag ó choimeádáin "
|
||||
"LXC.<br />"
|
||||
"LXC."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,7 +14,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Ne feledje:</b> az LXC-konténerek által igényelt funkciók nem érhetők el "
|
||||
"az OpenWrt lemezképeken a kis tárhellyel rendelkező eszközöknél.<br />"
|
||||
"az OpenWrt lemezképeken a kis tárhellyel rendelkező eszközöknél."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Nota:</b> I contenitori LXC richiedono funzionalità non disponibili nelle "
|
||||
"immagini OpenWrt per apparati con memoria limitata.<br />"
|
||||
"immagini OpenWrt per apparati con memoria limitata."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>注 :</b> LXC コンテナには、小さなフラッシュを搭載したデバイスの OpenWrt イ"
|
||||
"メージでは利用できない機能が必要です。<br />"
|
||||
"メージでは利用できない機能が必要です。"
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>참고:</b> 플래시 메모리 용량이 부족한 장치용 OpenWrt 이미지에서는 LXC "
|
||||
"컨테이너 기능을 사용할 수 없습니다.<br />"
|
||||
"컨테이너 기능을 사용할 수 없습니다."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -16,11 +16,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Pastaba:</b> „LXC“ konteineriai reikalauja funkcijų, kurie nėra "
|
||||
"pasiekiami ant „OpenWrt“ laikmenų, kurie skirti įrenginiams su mažos talpos "
|
||||
"apribojimu.<br />"
|
||||
"apribojimu."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Let op:</b> LXC-containers vereisen functies die niet beschikbaar zijn op "
|
||||
"OpenWrt-afbeeldingen voor apparaten met een kleine flitser.<br />"
|
||||
"OpenWrt-afbeeldingen voor apparaten met een kleine flitser."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,10 +14,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Zwróć uwagę:</b>Kontenery LXC wymagają funkcji niedostępnych w OpenWrt "
|
||||
"dla urządzeń z małą pamięcią flash.<br />"
|
||||
"dla urządzeń z małą pamięcią flash."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Por favor note:</b> LXC Containers requerem recursos não disponíveis nas "
|
||||
"imagens do OpenWrt para aparelhos com flash pequeno.<br />"
|
||||
"imagens do OpenWrt para aparelhos com flash pequeno."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,11 +13,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Atenção:</b> Os contêineres LXC requerem recursos não estão disponíveis "
|
||||
"nas imagens do OpenWrt para os dispositivos que tenham uma memória flash "
|
||||
"pequena.<br />"
|
||||
"pequena."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,10 +14,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Te rugăm să reții:</b> Containerele LXC necesită caracteristici care nu "
|
||||
"sunt disponibile pe imaginile OpenWrt pentru dispozitive cu flash mic.<br />"
|
||||
"sunt disponibile pe imaginile OpenWrt pentru dispozitive cu flash mic."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,10 +14,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Пожалуйста, обратите внимание:</b> LXC-контейнеры требуют функций, "
|
||||
"недоступных в образах OpenWrt для устройств с небольшой флэш-памятью.<br />"
|
||||
"недоступных в образах OpenWrt для устройств с небольшой флэш-памятью."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Uvedomte si prosím:</b> Kontajnery LXC vyžadujú funkcie, ktoré nie sú k "
|
||||
"dispozícii na obrazoch OpenWrt pre zariadenia s malým flash priestorom.<br />"
|
||||
"dispozícii na obrazoch OpenWrt pre zariadenia s malým flash priestorom."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,10 +14,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b> Obs! : </b> LXC-behållare kräver funktioner som inte är tillgängliga på "
|
||||
"OpenWrt-bilder för enheter med liten flash-minne. <br />"
|
||||
"OpenWrt-bilder för enheter med liten flash-minne. "
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b> தயவுசெய்து கவனிக்கவும்: </b> எல்எக்ச்சி கொள்கலன்களுக்கு சிறிய ஃபிளாச் கொண்ட "
|
||||
"சாதனங்களுக்கான ஓபன்ஆர்டி படங்களில் நற்பொருத்தங்கள் கிடைக்கவில்லை. <br />"
|
||||
"சாதனங்களுக்கான ஓபன்ஆர்டி படங்களில் நற்பொருத்தங்கள் கிடைக்கவில்லை. "
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b> Lütfen unutmayın: </b> LXC Kapsayıcıları, küçük flaşlı aygıtlar için "
|
||||
"OpenWrt görüntülerinde bulunmayan özellikler gerektirir. <br />"
|
||||
"OpenWrt görüntülerinde bulunmayan özellikler gerektirir. "
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,10 +14,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b> Завбачте: </b> Контейнери LXC вимагають функцій, недоступних в образах "
|
||||
"OpenWrt для пристроїв з малою флеш-пам'ятю. <br />"
|
||||
"OpenWrt для пристроїв з малою флеш-пам'ятю. "
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Xin lưu ý:</b> Bộ chứa LXC yêu cầu các tính năng không khả dụng trên ảnh "
|
||||
"OpenWrt dành cho thiết bị có đèn flash nhỏ.<br />"
|
||||
"OpenWrt dành cho thiết bị có đèn flash nhỏ."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,11 +13,11 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>Tenga en cuenta:</b> los contenedores LXC requieren funciones que no "
|
||||
"están disponibles en las imágenes OpenWrt para dispositivos con poco "
|
||||
"almacenamiento.<br />"
|
||||
"almacenamiento."
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
msgstr "<b>请注意: </b>LXC容器需要针对小闪存设备的OpenWrt镜像所不具备的功能。<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr "<b>请注意: </b>LXC容器需要针对小闪存设备的OpenWrt镜像所不具备的功能。"
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -13,10 +13,10 @@ msgstr ""
|
||||
#: applications/luci-app-lxc/luasrc/model/cbi/lxc.lua:18
|
||||
msgid ""
|
||||
"<b>Please note:</b> LXC Containers require features not available on OpenWrt "
|
||||
"images for devices with small flash.<br />"
|
||||
"images for devices with small flash."
|
||||
msgstr ""
|
||||
"<b>請注意:</b> 對於具有較小快閃記憶體的裝置,LXC 容器需要 OpenWrt 映像檔上不"
|
||||
"提供的功能。<br />"
|
||||
"提供的功能。"
|
||||
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:29
|
||||
#: applications/luci-app-lxc/luasrc/view/lxc.htm:47
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"admin/services/lxccm": {
|
||||
"title": "LXC Container mgr",
|
||||
"order": 80,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"uci": {
|
||||
"lxc": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxccm/overview": {
|
||||
"title": "Overview",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "lxc/overview"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-lxc" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxc/lxc_create/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.lxc",
|
||||
"function": "lxc_create"
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxc/lxc_action/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.lxc",
|
||||
"function": "lxc_action"
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxc/lxc_get_downloadable/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.lxc",
|
||||
"function": "lxc_get_downloadable"
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxc/lxc_configuration_get/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.lxc",
|
||||
"function": "lxc_configuration_get"
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
},
|
||||
|
||||
"admin/services/lxc/lxc_configuration_set/*": {
|
||||
"action": {
|
||||
"type": "function",
|
||||
"module": "luci.controller.lxc",
|
||||
"function": "lxc_configuration_set"
|
||||
},
|
||||
"auth": {
|
||||
"methods": [ "cookie:sysauth_https", "cookie:sysauth_http" ],
|
||||
"login": true
|
||||
}
|
||||
}
|
||||
}
|
||||
154
applications/luci-app-lxc/ucode/controller/lxc.uc
Normal file
154
applications/luci-app-lxc/ucode/controller/lxc.uc
Normal file
@@ -0,0 +1,154 @@
|
||||
// LXC management endpoint
|
||||
// Copyright 2026 Paul Donald <newtwen+github@gmail.com>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
// Built against the lxc API v6
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { connect } from 'ubus';
|
||||
const ctx = cursor();
|
||||
const LXC_URL = ctx.get('lxc', 'lxc', 'url');
|
||||
|
||||
function statfs(path) {
|
||||
let p = fs.popen('df -kP ' + path);
|
||||
p.read('line'); // header
|
||||
let line = p.read('line'); // data line
|
||||
p.close();
|
||||
|
||||
if (!line) return null;
|
||||
let cols = split(trim(line), /\s+/);
|
||||
// return {
|
||||
// filesystem: cols[0],
|
||||
// blocks_kb: int(cols[1],10),
|
||||
// used_kb: int(cols[2],10),
|
||||
// avail_kb: int(cols[3],10),
|
||||
// used_pct: cols[4],
|
||||
// mount: cols.slice(5).join(' ')
|
||||
// };
|
||||
return int(cols[3],10);
|
||||
}
|
||||
|
||||
const LXCController = {
|
||||
|
||||
lxc_get_downloadable: function() {
|
||||
let target = this.lxc_get_arch_target(LXC_URL);
|
||||
let templates = [];
|
||||
let content = fs.popen(`sh /usr/share/lxc/templates/lxc-download --list --server ${LXC_URL} 2>/dev/null`, 'r').read('all');
|
||||
content = split(content, '\n');
|
||||
for (let line in content) {
|
||||
let arr = match(line, /^(\S+)\s+(\S+)\s+(\S+)\s+default\s+(\S+)\s*$/);
|
||||
if(length(arr) < 3) continue;
|
||||
let dist = trim(arr[1]);
|
||||
let version = trim(arr[2]);
|
||||
let dist_target = trim(arr[3]);
|
||||
if (dist && version && dist_target && dist_target == target)
|
||||
push(templates, `${ dist }:${ version }`);
|
||||
}
|
||||
// content.close();
|
||||
|
||||
http.prepare_content('application/json');
|
||||
http.write_json(templates);
|
||||
},
|
||||
|
||||
lxc_create: function(lxc_name, lxc_template) {
|
||||
http.prepare_content('text/plain');
|
||||
let path = this.lxc_get_config_path();
|
||||
if (!path) return;
|
||||
let arr = match(lxc_template, /^(.+):(.+)$/);
|
||||
let lxc_dist = arr[1], lxc_release = arr[2];
|
||||
|
||||
system(`/usr/bin/lxc-create --quiet --name ${lxc_name} --bdev best --template download -- --dist ${lxc_dist} --release ${lxc_release} --arch ${this.lxc_get_arch_target(LXC_URL)} --server ${LXC_URL}`);
|
||||
|
||||
while (fs.access(path + lxc_name + '/partial')) {
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
http.write('0');
|
||||
},
|
||||
|
||||
lxc_action: function(lxc_action, lxc_name) {
|
||||
let ubus = connect();
|
||||
let data = ubus.call('lxc', lxc_action, { name: lxc_name });
|
||||
|
||||
http.prepare_content('application/json');
|
||||
http.write_json(data ? data : '');
|
||||
},
|
||||
|
||||
lxc_get_config_path: function() {
|
||||
let content = fs.readfile('/etc/lxc/lxc.conf');
|
||||
let ret = match(content, /^\s*lxc.lxcpath\s*=\s*(\S*)/);
|
||||
if (ret && length(ret) == 2) {
|
||||
if (fs.access(ret[1])) {
|
||||
let min_space = int(ctx.get('lxc', 'lxc', 'min_space')) || 100000;
|
||||
let free_space = statfs(ret[1]);
|
||||
if (free_space && free_space >= min_space) {
|
||||
let min_temp = int(ctx.get('lxc', 'lxc', 'min_temp')) || 100000;
|
||||
let free_temp = statfs('/tmp');
|
||||
if (free_temp && free_temp >= min_temp)
|
||||
return ret[1] + '/';
|
||||
else
|
||||
return 'lxc error: not enough temporary space (< ' + min_temp + ' KB)';
|
||||
}
|
||||
else
|
||||
return 'lxc error: not enough space (< ' + min_space + ' KB)';
|
||||
}
|
||||
else
|
||||
return 'lxc error: directory not found';
|
||||
}
|
||||
else
|
||||
return 'lxc error: config path is empty';
|
||||
},
|
||||
|
||||
lxc_configuration_get: function(lxc_name) {
|
||||
let content = fs.readfile(this.lxc_get_config_path() + lxc_name + '/config');
|
||||
|
||||
http.prepare_content('text/plain');
|
||||
http.write(content);
|
||||
},
|
||||
|
||||
lxc_configuration_set: function(lxc_name) {
|
||||
http.prepare_content('text/plain');
|
||||
|
||||
let lxc_configuration = http.formvalue('lxc_conf');
|
||||
lxc_configuration = http.urldecode(lxc_configuration, true);
|
||||
if (!lxc_configuration) {
|
||||
return 'lxc error: config formvalue is empty';
|
||||
}
|
||||
|
||||
fs.writefile(this.lxc_get_config_path() + lxc_name + '/config', lxc_configuration);
|
||||
|
||||
http.write('0');
|
||||
},
|
||||
|
||||
lxc_get_arch_target: function(url) {
|
||||
let target = split(fs.popen('uname -m', 'r').read('line'), '\n');
|
||||
if (url && match(url, /images.linuxcontainers.org/)) {
|
||||
let target_map = {
|
||||
armv5: 'armel',
|
||||
armv6: 'armel',
|
||||
armv7: 'armhf',
|
||||
armv8: 'arm64',
|
||||
aarch64:'arm64',
|
||||
i686 : 'i386',
|
||||
x86_64: 'amd64',
|
||||
};
|
||||
for (let k, v in target_map) {
|
||||
if (target[0] == k) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target[0];
|
||||
},
|
||||
};
|
||||
|
||||
// Export all handlers with automatic error wrapping
|
||||
let controller = LXCController;
|
||||
let exports = {};
|
||||
for (let k, v in controller) {
|
||||
if (type(v) == 'function')
|
||||
exports[k] = v;
|
||||
}
|
||||
|
||||
return exports;
|
||||
Reference in New Issue
Block a user