diff options
| author | Paul Donald | 2026-02-19 01:00:14 +0000 |
|---|---|---|
| committer | Paul Donald | 2026-02-19 01:00:14 +0000 |
| commit | 8a6df88443132cb254e89d828f187d4bfb2908d2 (patch) | |
| tree | 254f6cacb6d59db343eda9331673f3ca95bb06dd | |
| parent | c1f55dd43ae476f17cbdb8615349a4c90d640590 (diff) | |
| download | luci-8a6df88443132cb254e89d828f187d4bfb2908d2.tar.gz | |
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>
50 files changed, 830 insertions, 801 deletions
diff --git a/applications/luci-app-lxc/Makefile b/applications/luci-app-lxc/Makefile index 0955e518ba..91745b596f 100644 --- a/applications/luci-app-lxc/Makefile +++ b/applications/luci-app-lxc/Makefile @@ -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 diff --git a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/green.gif b/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/green.gif Binary files differdeleted file mode 100644 index d09febf127..0000000000 --- a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/green.gif +++ /dev/null diff --git a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/purple.gif b/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/purple.gif Binary files differdeleted file mode 100644 index f0d68cc8b2..0000000000 --- a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/purple.gif +++ /dev/null diff --git a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/red.gif b/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/red.gif Binary files differdeleted file mode 100644 index c1b39bbedb..0000000000 --- a/applications/luci-app-lxc/htdocs/luci-static/resources/cbi/red.gif +++ /dev/null diff --git a/applications/luci-app-lxc/htdocs/luci-static/resources/view/lxc/overview.js b/applications/luci-app-lxc/htdocs/luci-static/resources/view/lxc/overview.js new file mode 100644 index 0000000000..4ffbd4b45d --- /dev/null +++ b/applications/luci-app-lxc/htdocs/luci-static/resources/view/lxc/overview.js @@ -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(); + }); + }, +}); diff --git a/applications/luci-app-lxc/luasrc/controller/lxc.lua b/applications/luci-app-lxc/luasrc/controller/lxc.lua deleted file mode 100644 index c1777a03d1..0000000000 --- a/applications/luci-app-lxc/luasrc/controller/lxc.lua +++ /dev/null @@ -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 diff --git a/applications/luci-app-lxc/luasrc/model/cbi/lxc.lua b/applications/luci-app-lxc/luasrc/model/cbi/lxc.lua deleted file mode 100644 index df3f79ec48..0000000000 --- a/applications/luci-app-lxc/luasrc/model/cbi/lxc.lua +++ /dev/null @@ -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 diff --git a/applications/luci-app-lxc/luasrc/view/lxc.htm b/applications/luci-app-lxc/luasrc/view/lxc.htm deleted file mode 100644 index 390631e4af..0000000000 --- a/applications/luci-app-lxc/luasrc/view/lxc.htm +++ /dev/null @@ -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> diff --git a/applications/luci-app-lxc/po/ar/lxc.po b/applications/luci-app-lxc/po/ar/lxc.po index 7e6f8b51fe..eaaee36f4e 100644 --- a/applications/luci-app-lxc/po/ar/lxc.po +++ b/applications/luci-app-lxc/po/ar/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/bg/lxc.po b/applications/luci-app-lxc/po/bg/lxc.po index a587b91405..40e32af384 100644 --- a/applications/luci-app-lxc/po/bg/lxc.po +++ b/applications/luci-app-lxc/po/bg/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/bn_BD/lxc.po b/applications/luci-app-lxc/po/bn_BD/lxc.po index bd04da4550..3d947e3090 100644 --- a/applications/luci-app-lxc/po/bn_BD/lxc.po +++ b/applications/luci-app-lxc/po/bn_BD/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ca/lxc.po b/applications/luci-app-lxc/po/ca/lxc.po index 44bd1209f9..d82f77d99f 100644 --- a/applications/luci-app-lxc/po/ca/lxc.po +++ b/applications/luci-app-lxc/po/ca/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/cs/lxc.po b/applications/luci-app-lxc/po/cs/lxc.po index 1ab2abd71b..b1de8660be 100644 --- a/applications/luci-app-lxc/po/cs/lxc.po +++ b/applications/luci-app-lxc/po/cs/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/da/lxc.po b/applications/luci-app-lxc/po/da/lxc.po index 71ef422d26..53672db4de 100644 --- a/applications/luci-app-lxc/po/da/lxc.po +++ b/applications/luci-app-lxc/po/da/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/de/lxc.po b/applications/luci-app-lxc/po/de/lxc.po index c4df21010b..bfa110042c 100644 --- a/applications/luci-app-lxc/po/de/lxc.po +++ b/applications/luci-app-lxc/po/de/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/el/lxc.po b/applications/luci-app-lxc/po/el/lxc.po index 18499938bb..546f2e3673 100644 --- a/applications/luci-app-lxc/po/el/lxc.po +++ b/applications/luci-app-lxc/po/el/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/es/lxc.po b/applications/luci-app-lxc/po/es/lxc.po index de7813ee4b..b0be758848 100644 --- a/applications/luci-app-lxc/po/es/lxc.po +++ b/applications/luci-app-lxc/po/es/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/et/lxc.po b/applications/luci-app-lxc/po/et/lxc.po index 5285c0f453..a3f6b281d1 100644 --- a/applications/luci-app-lxc/po/et/lxc.po +++ b/applications/luci-app-lxc/po/et/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/fa/lxc.po b/applications/luci-app-lxc/po/fa/lxc.po index c428ea108b..fc928f9520 100644 --- a/applications/luci-app-lxc/po/fa/lxc.po +++ b/applications/luci-app-lxc/po/fa/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/fi/lxc.po b/applications/luci-app-lxc/po/fi/lxc.po index 310b99b942..037595d15a 100644 --- a/applications/luci-app-lxc/po/fi/lxc.po +++ b/applications/luci-app-lxc/po/fi/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/fr/lxc.po b/applications/luci-app-lxc/po/fr/lxc.po index 7f81fd31ef..4bb4650ef2 100644 --- a/applications/luci-app-lxc/po/fr/lxc.po +++ b/applications/luci-app-lxc/po/fr/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ga/lxc.po b/applications/luci-app-lxc/po/ga/lxc.po index 24794291e1..c16e992202 100644 --- a/applications/luci-app-lxc/po/ga/lxc.po +++ b/applications/luci-app-lxc/po/ga/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/he/lxc.po b/applications/luci-app-lxc/po/he/lxc.po index e4851c4d4d..15ea961c1e 100644 --- a/applications/luci-app-lxc/po/he/lxc.po +++ b/applications/luci-app-lxc/po/he/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/hi/lxc.po b/applications/luci-app-lxc/po/hi/lxc.po index 49de57930d..f031b68e3f 100644 --- a/applications/luci-app-lxc/po/hi/lxc.po +++ b/applications/luci-app-lxc/po/hi/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/hu/lxc.po b/applications/luci-app-lxc/po/hu/lxc.po index 388747eb5c..7194dcd040 100644 --- a/applications/luci-app-lxc/po/hu/lxc.po +++ b/applications/luci-app-lxc/po/hu/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/it/lxc.po b/applications/luci-app-lxc/po/it/lxc.po index b1da30a6cb..7b5b04ae41 100644 --- a/applications/luci-app-lxc/po/it/lxc.po +++ b/applications/luci-app-lxc/po/it/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ja/lxc.po b/applications/luci-app-lxc/po/ja/lxc.po index 552f885e24..7f50212227 100644 --- a/applications/luci-app-lxc/po/ja/lxc.po +++ b/applications/luci-app-lxc/po/ja/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ko/lxc.po b/applications/luci-app-lxc/po/ko/lxc.po index 24b7b5881f..bbebd0e88d 100644 --- a/applications/luci-app-lxc/po/ko/lxc.po +++ b/applications/luci-app-lxc/po/ko/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/lt/lxc.po b/applications/luci-app-lxc/po/lt/lxc.po index 3ad7741934..64567cc4a0 100644 --- a/applications/luci-app-lxc/po/lt/lxc.po +++ b/applications/luci-app-lxc/po/lt/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/mr/lxc.po b/applications/luci-app-lxc/po/mr/lxc.po index 3fb72208fa..5277ae9572 100644 --- a/applications/luci-app-lxc/po/mr/lxc.po +++ b/applications/luci-app-lxc/po/mr/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ms/lxc.po b/applications/luci-app-lxc/po/ms/lxc.po index e651c39479..5c150dd6c3 100644 --- a/applications/luci-app-lxc/po/ms/lxc.po +++ b/applications/luci-app-lxc/po/ms/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/nb_NO/lxc.po b/applications/luci-app-lxc/po/nb_NO/lxc.po index 3d266e3959..9649d4871f 100644 --- a/applications/luci-app-lxc/po/nb_NO/lxc.po +++ b/applications/luci-app-lxc/po/nb_NO/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/nl/lxc.po b/applications/luci-app-lxc/po/nl/lxc.po index 794c0d745c..d0ad0a2a21 100644 --- a/applications/luci-app-lxc/po/nl/lxc.po +++ b/applications/luci-app-lxc/po/nl/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/pl/lxc.po b/applications/luci-app-lxc/po/pl/lxc.po index 9df6a1983e..01352adb70 100644 --- a/applications/luci-app-lxc/po/pl/lxc.po +++ b/applications/luci-app-lxc/po/pl/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/pt/lxc.po b/applications/luci-app-lxc/po/pt/lxc.po index 993d37bc96..4f1f312293 100644 --- a/applications/luci-app-lxc/po/pt/lxc.po +++ b/applications/luci-app-lxc/po/pt/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/pt_BR/lxc.po b/applications/luci-app-lxc/po/pt_BR/lxc.po index 192d365649..8a5faea253 100644 --- a/applications/luci-app-lxc/po/pt_BR/lxc.po +++ b/applications/luci-app-lxc/po/pt_BR/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ro/lxc.po b/applications/luci-app-lxc/po/ro/lxc.po index 7c2416af9b..19f2adb9ad 100644 --- a/applications/luci-app-lxc/po/ro/lxc.po +++ b/applications/luci-app-lxc/po/ro/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ru/lxc.po b/applications/luci-app-lxc/po/ru/lxc.po index e0e97f4e78..2d9be9ae12 100644 --- a/applications/luci-app-lxc/po/ru/lxc.po +++ b/applications/luci-app-lxc/po/ru/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/sk/lxc.po b/applications/luci-app-lxc/po/sk/lxc.po index 32a272f424..6ea3d097c4 100644 --- a/applications/luci-app-lxc/po/sk/lxc.po +++ b/applications/luci-app-lxc/po/sk/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/sv/lxc.po b/applications/luci-app-lxc/po/sv/lxc.po index 613e28edc2..2334a1d7f3 100644 --- a/applications/luci-app-lxc/po/sv/lxc.po +++ b/applications/luci-app-lxc/po/sv/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/ta/lxc.po b/applications/luci-app-lxc/po/ta/lxc.po index a6dbe5a3e0..49729ac2e1 100644 --- a/applications/luci-app-lxc/po/ta/lxc.po +++ b/applications/luci-app-lxc/po/ta/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/templates/lxc.pot b/applications/luci-app-lxc/po/templates/lxc.pot index a660af5462..19f03f65d4 100644 --- a/applications/luci-app-lxc/po/templates/lxc.pot +++ b/applications/luci-app-lxc/po/templates/lxc.pot @@ -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 diff --git a/applications/luci-app-lxc/po/tr/lxc.po b/applications/luci-app-lxc/po/tr/lxc.po index 4ed5c8ea03..de1e949a63 100644 --- a/applications/luci-app-lxc/po/tr/lxc.po +++ b/applications/luci-app-lxc/po/tr/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/uk/lxc.po b/applications/luci-app-lxc/po/uk/lxc.po index 36f144892f..2d23649639 100644 --- a/applications/luci-app-lxc/po/uk/lxc.po +++ b/applications/luci-app-lxc/po/uk/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/vi/lxc.po b/applications/luci-app-lxc/po/vi/lxc.po index d66cd297ea..33ad384f2d 100644 --- a/applications/luci-app-lxc/po/vi/lxc.po +++ b/applications/luci-app-lxc/po/vi/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/yua/lxc.po b/applications/luci-app-lxc/po/yua/lxc.po index 2b1cfbbe7b..52509a4808 100644 --- a/applications/luci-app-lxc/po/yua/lxc.po +++ b/applications/luci-app-lxc/po/yua/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/zh_Hans/lxc.po b/applications/luci-app-lxc/po/zh_Hans/lxc.po index 468261ceee..ffc8a30e68 100644 --- a/applications/luci-app-lxc/po/zh_Hans/lxc.po +++ b/applications/luci-app-lxc/po/zh_Hans/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/po/zh_Hant/lxc.po b/applications/luci-app-lxc/po/zh_Hant/lxc.po index 1ea475f58a..05442ca0b2 100644 --- a/applications/luci-app-lxc/po/zh_Hant/lxc.po +++ b/applications/luci-app-lxc/po/zh_Hant/lxc.po @@ -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 diff --git a/applications/luci-app-lxc/root/usr/share/luci/menu.d/luci-app-lxc.json b/applications/luci-app-lxc/root/usr/share/luci/menu.d/luci-app-lxc.json new file mode 100644 index 0000000000..0c1f9516c7 --- /dev/null +++ b/applications/luci-app-lxc/root/usr/share/luci/menu.d/luci-app-lxc.json @@ -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 + } + } +} diff --git a/applications/luci-app-lxc/ucode/controller/lxc.uc b/applications/luci-app-lxc/ucode/controller/lxc.uc new file mode 100644 index 0000000000..879ee42b85 --- /dev/null +++ b/applications/luci-app-lxc/ucode/controller/lxc.uc @@ -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; |