summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--modules/luci-base/Makefile2
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/cbi.js25
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/form.js27
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js415
-rw-r--r--modules/luci-base/luasrc/view/cbi/filebrowser.htm113
-rw-r--r--modules/luci-base/luasrc/view/cbi/upload.htm30
-rw-r--r--modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json10
-rw-r--r--modules/luci-mod-admin-full/luasrc/controller/admin/filebrowser.lua9
8 files changed, 462 insertions, 169 deletions
diff --git a/modules/luci-base/Makefile b/modules/luci-base/Makefile
index 9bc8ec17a..92da37a22 100644
--- a/modules/luci-base/Makefile
+++ b/modules/luci-base/Makefile
@@ -12,7 +12,7 @@ LUCI_TYPE:=mod
LUCI_BASENAME:=base
LUCI_TITLE:=LuCI core libraries
-LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua
+LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua +rpcd-mod-file +cgi-io
PKG_SOURCE:=v1.0.0.tar.gz
diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js
index 5aa687b67..9144fbaf6 100644
--- a/modules/luci-base/htdocs/luci-static/resources/cbi.js
+++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js
@@ -298,8 +298,6 @@ function cbi_init() {
node.getAttribute('data-type'));
}
- document.querySelectorAll('[data-browser]').forEach(cbi_browser_init);
-
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
s.parentNode.classList.add('cbi-tooltip-container');
});
@@ -330,29 +328,6 @@ function cbi_init() {
cbi_d_update();
}
-function cbi_filebrowser(id, defpath) {
- var field = L.dom.elem(id) ? id : document.getElementById(id);
- var browser = window.open(
- cbi_strings.path.browser + (field.value || defpath || '') + '?field=' + field.id,
- "luci_filebrowser", "width=300,height=400,left=100,top=200,scrollbars=yes"
- );
-
- browser.focus();
-}
-
-function cbi_browser_init(field)
-{
- field.parentNode.insertBefore(
- E('img', {
- 'src': L.resource('cbi/folder.gif'),
- 'class': 'cbi-image-button',
- 'click': function(ev) {
- cbi_filebrowser(field, field.getAttribute('data-browser'));
- ev.preventDefault();
- }
- }), field.nextSibling);
-}
-
function cbi_validate_form(form, errmsg)
{
/* if triggered by a section removal or addition, don't validate */
diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js
index be9b53a88..6832a1646 100644
--- a/modules/luci-base/htdocs/luci-static/resources/form.js
+++ b/modules/luci-base/htdocs/luci-static/resources/form.js
@@ -1673,6 +1673,32 @@ var CBIHiddenValue = CBIValue.extend({
}
});
+var CBIFileUpload = CBIValue.extend({
+ __name__: 'CBI.FileSelect',
+
+ __init__: function(/* ... */) {
+ this.super('__init__', arguments);
+
+ this.show_hidden = false;
+ this.enable_upload = true;
+ this.enable_remove = true;
+ this.root_directory = '/etc/luci-uploads';
+ },
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ var browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
+ id: this.cbid(section_id),
+ name: this.cbid(section_id),
+ show_hidden: this.show_hidden,
+ enable_upload: this.enable_upload,
+ enable_remove: this.enable_remove,
+ root_directory: this.root_directory
+ });
+
+ return browserEl.render();
+ }
+});
+
var CBISectionValue = CBIValue.extend({
__name__: 'CBI.ContainerValue',
__init__: function(map, section, option, cbiClass /*, ... */) {
@@ -1726,5 +1752,6 @@ return L.Class.extend({
DummyValue: CBIDummyValue,
Button: CBIButtonValue,
HiddenValue: CBIHiddenValue,
+ FileUpload: CBIFileUpload,
SectionValue: CBISectionValue
});
diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js
index 3444549e0..c27dd7ebf 100644
--- a/modules/luci-base/htdocs/luci-static/resources/ui.js
+++ b/modules/luci-base/htdocs/luci-static/resources/ui.js
@@ -1,4 +1,5 @@
'use strict';
+'require rpc';
'require uci';
'require validation';
@@ -1453,6 +1454,417 @@ var UIHiddenfield = UIElement.extend({
}
});
+var UIFileUpload = UIElement.extend({
+ __init__: function(value, options) {
+ this.value = value;
+ this.options = Object.assign({
+ show_hidden: false,
+ enable_upload: true,
+ enable_remove: true,
+ root_directory: '/etc/luci-uploads'
+ }, options);
+ },
+
+ callFileStat: rpc.declare({
+ 'object': 'file',
+ 'method': 'stat',
+ 'params': [ 'path' ],
+ 'expect': { '': {} }
+ }),
+
+ callFileList: rpc.declare({
+ 'object': 'file',
+ 'method': 'list',
+ 'params': [ 'path' ],
+ 'expect': { 'entries': [] }
+ }),
+
+ callFileRemove: rpc.declare({
+ 'object': 'file',
+ 'method': 'remove',
+ 'params': [ 'path' ]
+ }),
+
+ bind: function(browserEl) {
+ this.node = browserEl;
+
+ this.setUpdateEvents(browserEl, 'cbi-fileupload-select', 'cbi-fileupload-cancel');
+ this.setChangeEvents(browserEl, 'cbi-fileupload-select', 'cbi-fileupload-cancel');
+
+ L.dom.bindClassInstance(browserEl, this);
+
+ return browserEl;
+ },
+
+ render: function() {
+ return Promise.resolve(this.value != null ? this.callFileStat(this.value) : null).then(L.bind(function(stat) {
+ var label;
+
+ if (L.isObject(stat) && stat.type != 'directory')
+ this.stat = stat;
+
+ if (this.stat != null)
+ label = [ this.iconForType(this.stat.type), ' %s (%1000mB)'.format(this.truncatePath(this.stat.path), this.stat.size) ];
+ else if (this.value != null)
+ label = [ this.iconForType('file'), ' %s (%s)'.format(this.truncatePath(this.value), _('File not accessible')) ];
+ else
+ label = _('Select file…');
+
+ return this.bind(E('div', { 'id': this.options.id }, [
+ E('button', {
+ 'class': 'btn',
+ 'click': L.ui.createHandlerFn(this, 'handleFileBrowser')
+ }, label),
+ E('div', {
+ 'class': 'cbi-filebrowser'
+ }),
+ E('input', {
+ 'type': 'hidden',
+ 'name': this.options.name,
+ 'value': this.value
+ })
+ ]));
+ }, this));
+ },
+
+ truncatePath: function(path) {
+ if (path.length > 50)
+ path = path.substring(0, 25) + '…' + path.substring(path.length - 25);
+
+ return path;
+ },
+
+ iconForType: function(type) {
+ switch (type) {
+ case 'symlink':
+ return E('img', {
+ 'src': L.resource('cbi/link.gif'),
+ 'title': _('Symbolic link'),
+ 'class': 'middle'
+ });
+
+ case 'directory':
+ return E('img', {
+ 'src': L.resource('cbi/folder.gif'),
+ 'title': _('Directory'),
+ 'class': 'middle'
+ });
+
+ default:
+ return E('img', {
+ 'src': L.resource('cbi/file.gif'),
+ 'title': _('File'),
+ 'class': 'middle'
+ });
+ }
+ },
+
+ canonicalizePath: function(path) {
+ return path.replace(/\/{2,}/, '/')
+ .replace(/\/\.(\/|$)/g, '/')
+ .replace(/[^\/]+\/\.\.(\/|$)/g, '/')
+ .replace(/\/$/, '');
+ },
+
+ splitPath: function(path) {
+ var croot = this.canonicalizePath(this.options.root_directory || '/'),
+ cpath = this.canonicalizePath(path || '/');
+
+ if (cpath.length <= croot.length)
+ return [ croot ];
+
+ if (cpath.charAt(croot.length) != '/')
+ return [ croot ];
+
+ var parts = cpath.substring(croot.length + 1).split(/\//);
+
+ parts.unshift(croot);
+
+ return parts;
+ },
+
+ handleUpload: function(path, list, ev) {
+ var form = ev.target.parentNode,
+ fileinput = form.querySelector('input[type="file"]'),
+ nameinput = form.querySelector('input[type="text"]'),
+ filename = (nameinput.value != null ? nameinput.value : '').trim();
+
+ ev.preventDefault();
+
+ if (filename == '' || filename.match(/\//) || fileinput.files[0] == null)
+ return;
+
+ var existing = list.filter(function(e) { return e.name == filename })[0];
+
+ if (existing != null && existing.type == 'directory')
+ return alert(_('A directory with the same name already exists.'));
+ else if (existing != null && !confirm(_('Overwrite existing file "%s" ?').format(filename)))
+ return;
+
+ var data = new FormData();
+
+ data.append('sessionid', L.env.sessionid);
+ data.append('filename', path + '/' + filename);
+ data.append('filedata', fileinput.files[0]);
+
+ return L.Request.post('/cgi-bin/cgi-upload', data, {
+ progress: L.bind(function(btn, ev) {
+ btn.firstChild.data = '%.2f%%'.format((ev.loaded / ev.total) * 100);
+ }, this, ev.target)
+ }).then(L.bind(function(path, ev, res) {
+ var reply = res.json();
+
+ if (L.isObject(reply) && reply.failure)
+ alert(_('Upload request failed: %s').format(reply.message));
+
+ return this.handleSelect(path, null, ev);
+ }, this, path, ev));
+ },
+
+ handleDelete: function(path, fileStat, ev) {
+ var parent = path.replace(/\/[^\/]+$/, '') || '/',
+ name = path.replace(/^.+\//, ''),
+ msg;
+
+ ev.preventDefault();
+
+ if (fileStat.type == 'directory')
+ msg = _('Do you really want to recursively delete the directory "%s" ?').format(name);
+ else
+ msg = _('Do you really want to delete "%s" ?').format(name);
+
+ if (confirm(msg)) {
+ var button = this.node.firstElementChild,
+ hidden = this.node.lastElementChild;
+
+ if (path == hidden.value) {
+ L.dom.content(button, _('Select file…'));
+ hidden.value = '';
+ }
+
+ return this.callFileRemove(path).then(L.bind(function(parent, ev, rc) {
+ if (rc == 0)
+ return this.handleSelect(parent, null, ev);
+ else if (rc == 6)
+ alert(_('Delete permission denied'));
+ else
+ alert(_('Delete request failed: %d %s').format(rc, rpc.getStatusText(rc)));
+
+ }, this, parent, ev));
+ }
+ },
+
+ renderUpload: function(path, list) {
+ if (!this.options.enable_upload)
+ return E([]);
+
+ return E([
+ E('a', {
+ 'href': '#',
+ 'class': 'btn cbi-button-positive',
+ 'click': function(ev) {
+ var uploadForm = ev.target.nextElementSibling,
+ fileInput = uploadForm.querySelector('input[type="file"]');
+
+ ev.target.style.display = 'none';
+ uploadForm.style.display = '';
+ fileInput.click();
+ }
+ }, _('Upload file…')),
+ E('div', { 'class': 'upload', 'style': 'display:none' }, [
+ E('input', {
+ 'type': 'file',
+ 'style': 'display:none',
+ 'change': function(ev) {
+ var nameinput = ev.target.parentNode.querySelector('input[type="text"]'),
+ uploadbtn = ev.target.parentNode.querySelector('button.cbi-button-save');
+
+ nameinput.value = ev.target.value.replace(/^.+[\/\\]/, '');
+ uploadbtn.disabled = false;
+ }
+ }),
+ E('button', {
+ 'class': 'btn',
+ 'click': function(ev) {
+ ev.preventDefault();
+ ev.target.previousElementSibling.click();
+ }
+ }, _('Browse…')),
+ E('div', {}, E('input', { 'type': 'text', 'placeholder': _('Filename') })),
+ E('button', {
+ 'class': 'btn cbi-button-save',
+ 'click': L.ui.createHandlerFn(this, 'handleUpload', path, list),
+ 'disabled': true
+ }, _('Upload file'))
+ ])
+ ]);
+ },
+
+ renderListing: function(container, path, list) {
+ var breadcrumb = E('p'),
+ rows = E('ul');
+
+ list.sort(function(a, b) {
+ var isDirA = (a.type == 'directory'),
+ isDirB = (b.type == 'directory');
+
+ if (isDirA != isDirB)
+ return isDirA < isDirB;
+
+ return a.name > b.name;
+ });
+
+ for (var i = 0; i < list.length; i++) {
+ if (!this.options.show_hidden && list[i].name.charAt(0) == '.')
+ continue;
+
+ var entrypath = this.canonicalizePath(path + '/' + list[i].name),
+ selected = (entrypath == this.node.lastElementChild.value),
+ mtime = new Date(list[i].mtime * 1000);
+
+ rows.appendChild(E('li', [
+ E('div', { 'class': 'name' }, [
+ this.iconForType(list[i].type),
+ ' ',
+ E('a', {
+ 'href': '#',
+ 'style': selected ? 'font-weight:bold' : null,
+ 'click': L.ui.createHandlerFn(this, 'handleSelect',
+ entrypath, list[i].type != 'directory' ? list[i] : null)
+ }, '%h'.format(list[i].name))
+ ]),
+ E('div', { 'class': 'mtime hide-xs' }, [
+ ' %04d-%02d-%02d %02d:%02d:%02d '.format(
+ mtime.getFullYear(),
+ mtime.getMonth() + 1,
+ mtime.getDate(),
+ mtime.getHours(),
+ mtime.getMinutes(),
+ mtime.getSeconds())
+ ]),
+ E('div', [
+ selected ? E('button', {
+ 'class': 'btn',
+ 'click': L.ui.createHandlerFn(this, 'handleReset')
+ }, _('Deselect')) : '',
+ this.options.enable_remove ? E('button', {
+ 'class': 'btn cbi-button-negative',
+ 'click': L.ui.createHandlerFn(this, 'handleDelete', entrypath, list[i])
+ }, _('Delete')) : ''
+ ])
+ ]));
+ }
+
+ if (!rows.firstElementChild)
+ rows.appendChild(E('em', _('No entries in this directory')));
+
+ var dirs = this.splitPath(path),
+ cur = '';
+
+ for (var i = 0; i < dirs.length; i++) {
+ cur = cur ? cur + '/' + dirs[i] : dirs[i];
+ L.dom.append(breadcrumb, [
+ i ? ' » ' : '',
+ E('a', {
+ 'href': '#',
+ 'click': L.ui.createHandlerFn(this, 'handleSelect', cur || '/', null)
+ }, dirs[i] != '' ? '%h'.format(dirs[i]) : E('em', '(root)')),
+ ]);
+ }
+
+ L.dom.content(container, [
+ breadcrumb,
+ rows,
+ E('div', { 'class': 'right' }, [
+ this.renderUpload(path, list),
+ E('a', {
+ 'href': '#',
+ 'class': 'btn',
+ 'click': L.ui.createHandlerFn(this, 'handleCancel')
+ }, _('Cancel'))
+ ]),
+ ]);
+ },
+
+ handleCancel: function(ev) {
+ var button = this.node.firstElementChild,
+ browser = button.nextElementSibling;
+
+ browser.classList.remove('open');
+ button.style.display = '';
+
+ this.node.dispatchEvent(new CustomEvent('cbi-fileupload-cancel', {}));
+ },
+
+ handleReset: function(ev) {
+ var button = this.node.firstElementChild,
+ hidden = this.node.lastElementChild;
+
+ hidden.value = '';
+ L.dom.content(button, _('Select file…'));
+
+ this.handleCancel(ev);
+ },
+
+ handleSelect: function(path, fileStat, ev) {
+ var browser = L.dom.parent(ev.target, '.cbi-filebrowser'),
+ ul = browser.querySelector('ul');
+
+ if (fileStat == null) {
+ L.dom.content(ul, E('em', { 'class': 'spinning' }, _('Loading directory contents…')));
+ this.callFileList(path).then(L.bind(this.renderListing, this, browser, path));
+ }
+ else {
+ var button = this.node.firstElementChild,
+ hidden = this.node.lastElementChild;
+
+ path = this.canonicalizePath(path);
+
+ L.dom.content(button, [
+ this.iconForType(fileStat.type),
+ ' %s (%1000mB)'.format(this.truncatePath(path), fileStat.size)
+ ]);
+
+ browser.classList.remove('open');
+ button.style.display = '';
+ hidden.value = path;
+
+ this.stat = Object.assign({ path: path }, fileStat);
+ this.node.dispatchEvent(new CustomEvent('cbi-fileupload-select', { detail: this.stat }));
+ }
+ },
+
+ handleFileBrowser: function(ev) {
+ var button = ev.target,
+ browser = button.nextElementSibling,
+ path = this.stat ? this.stat.path.replace(/\/[^\/]+$/, '') : this.options.root_directory;
+
+ if (this.options.root_directory.indexOf(path) != 0)
+ path = this.options.root_directory;
+
+ ev.preventDefault();
+
+ return this.callFileList(path).then(L.bind(function(button, browser, path, list) {
+ document.querySelectorAll('.cbi-filebrowser.open').forEach(function(browserEl) {
+ L.dom.findClassInstance(browserEl).handleCancel(ev);
+ });
+
+ button.style.display = 'none';
+ browser.classList.add('open');
+
+ return this.renderListing(browser, path, list);
+ }, this, button, browser, path));
+ },
+
+ getValue: function() {
+ return this.node.lastElementChild.value;
+ },
+
+ setValue: function(value) {
+ this.node.lastElementChild.value = value;
+ }
+});
+
return L.Class.extend({
__init__: function() {
@@ -2173,5 +2585,6 @@ return L.Class.extend({
Dropdown: UIDropdown,
DynamicList: UIDynamicList,
Combobox: UICombobox,
- Hiddenfield: UIHiddenfield
+ Hiddenfield: UIHiddenfield,
+ FileUpload: UIFileUpload
});
diff --git a/modules/luci-base/luasrc/view/cbi/filebrowser.htm b/modules/luci-base/luasrc/view/cbi/filebrowser.htm
deleted file mode 100644
index 806b1b5f4..000000000
--- a/modules/luci-base/luasrc/view/cbi/filebrowser.htm
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
- <title>Filebrowser - LuCI</title>
- <style type="text/css">
- #path, #listing {
- font-size: 85%;
- }
-
- ul {
- padding-left: 0;
- list-style-type: none;
- }
-
- li img {
- vertical-align: bottom;
- margin-right: 0.2em;
- }
- </style>
-
- <script type="text/javascript">
- function callback(path) {
- if( window.opener ) {
- var input = window.opener.document.getElementById(decodeURIComponent('<%=luci.http.urlencode(luci.http.formvalue('field'))%>'));
- if( input ) {
- input.value = decodeURIComponent(path);
- window.close();
- }
- }
- }
- </script>
-</head>
-<body>
- <%
- require("nixio.fs")
- require("nixio.util")
- require("luci.http")
- require("luci.dispatcher")
-
- local field = luci.http.formvalue('field')
- local request = luci.dispatcher.context.args
- local path = { '' }
-
- for i = 1, #request do
- if request[i] ~= '..' and #request[i] > 0 then
- path[#path+1] = request[i]
- end
- end
-
- local filestat = nixio.fs.stat(table.concat(path, '/'))
- local baseurl = { 'admin', 'filebrowser' }
-
- if filestat and filestat.type == "reg" then
- path[#path] = ''
- elseif not (filestat and filestat.type == "dir") then
- path = { '', '' }
- else
- path[#path+1] = ''
- end
-
- filepath = table.concat(path, '/')
-
- local entries = {}
- local _, e
- for _, e in luci.util.vspairs(nixio.util.consume((nixio.fs.dir(filepath)))) do
- local p = filepath .. e
- local s = nixio.fs.stat(p)
- if s then
- entries[#entries+1] = {
- name = e,
- path = p,
- type = s.type
- }
- end
- end
- -%>
- <div id="path">
- Location:
- <% for i, dir in ipairs(path) do %>
- <% if i == 1 then %>
- <a href="<%=url(unpack(baseurl))%>?field=<%=luci.http.urlencode(field)%>">(root)</a>
- <% elseif next(path, i) then %>
- <% baseurl[#baseurl+1] = luci.http.urlencode(dir) %>
- / <a href="<%=url(unpack(baseurl))%>?field=<%=luci.http.urlencode(field)%>"><%=pcdata(dir)%></a>
- <% else %>
- <% baseurl[#baseurl+1] = luci.http.urlencode(dir) %>
- / <%=pcdata(dir)%>
- <% end %>
- <% end %>
- </div>
-
- <hr />
-
- <div id="listing">
- <ul>
- <% for _, e in ipairs(entries) do if e.type == 'dir' then -%>
- <li class="dir">
- <img src="<%=resource%>/cbi/folder.gif" alt="<%:Directory%>" />
- <a href="<%=url(unpack(baseurl))%>/<%=luci.http.urlencode(e.name)%>?field=<%=luci.http.urlencode(field)%>"><%=pcdata(e.name)%>/</a>
- </li>
- <% end end -%>
-
- <% for _, e in ipairs(entries) do if e.type ~= 'dir' then -%>
- <li class="file">
- <img src="<%=resource%>/cbi/file.gif" alt="<%:File%>" />
- <a href="#" onclick="callback('<%=luci.http.urlencode(e.path)%>')"><%=pcdata(e.name)%></a>
- </li>
- <% end end -%>
- </ul>
- </div>
-</body>
-</html>
diff --git a/modules/luci-base/luasrc/view/cbi/upload.htm b/modules/luci-base/luasrc/view/cbi/upload.htm
index 3c3d82b65..e61049538 100644
--- a/modules/luci-base/luasrc/view/cbi/upload.htm
+++ b/modules/luci-base/luasrc/view/cbi/upload.htm
@@ -1,24 +1,14 @@
-<%
- local t = require("luci.tools.webadmin")
- local v = self:cfgvalue(section)
- local s = v and nixio.fs.stat(v)
--%>
<%+cbi/valueheader%>
- <% if s then %>
- <%:Uploaded File%> (<%=t.byte_format(s.size)%>)
- <% if self.unsafeupload then %>
- <input type="hidden"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> />
- <input class="cbi-button cbi-button-image" type="image" value="<%:Replace entry%>" name="cbi.rlf.<%=section .. "." .. self.option%>" alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" />
- <% end %>
- <% end %>
- <% if not self.unsafeupload then %>
- <input type="hidden"<%= attr("value", v) .. attr("name", "cbi.rlf." .. section .. "." .. self.option) .. attr("id", "cbi.rlf." .. section .. "." .. self.option) %> />
- <% end %>
+<div<%=attr("data-ui-widget", luci.util.serialize_json({
+ "FileUpload", self:cfgvalue(section) or self.default, {
+ id = cbid,
+ name = cbid,
+ show_hidden = self.show_hidden,
+ enable_remove = self.enable_remove,
+ enable_upload = self.enable_upload,
+ root_directory = "/" --self.root_directory
+ }
+}))%>></div>
- <% if (not s) or (s and not self.unsafeupload) then %>
- <input class="cbi-input-file" type="file"<%= attr("name", cbid) .. attr("id", cbid) %> />
- <% end %>
- <input type="text" class="cbi-input-text" data-update="change"<%=
- attr("name", cbid .. ".textbox") .. attr("id", cbid .. ".textbox") .. attr("value", luci.cbi.AbstractValue.cfgvalue(self, section) or self.default) .. ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder") .. ifattr(self.readonly, "readonly") .. ifattr(self.maxlength, "maxlength") %> />
<%+cbi/valuefooter%>
diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
index 2804cc700..57e0ae384 100644
--- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
+++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
@@ -20,7 +20,12 @@
"luci-access": {
"description": "Grant access to basic LuCI procedures",
"read": {
+ "file": {
+ "/": [ "list" ],
+ "/*": [ "list" ]
+ },
"ubus": {
+ "file": [ "list", "stat" ],
"iwinfo": [ "assoclist" ],
"luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices", "getWirelessDevices" ],
"network.device": [ "status" ],
@@ -32,7 +37,12 @@
"uci": [ "*" ]
},
"write": {
+ "cgi-io": [ "upload", "/etc/luci-uploads/*" ],
+ "file": {
+ "/etc/luci-uploads/*": [ "write" ]
+ },
"ubus": {
+ "file": [ "remove" ],
"iwinfo": [ "scan" ],
"luci": [ "setInitAction", "setLocaltime" ],
"uci": [ "add", "apply", "confirm", "delete", "order", "set" ]
diff --git a/modules/luci-mod-admin-full/luasrc/controller/admin/filebrowser.lua b/modules/luci-mod-admin-full/luasrc/controller/admin/filebrowser.lua
deleted file mode 100644
index 257261513..000000000
--- a/modules/luci-mod-admin-full/luasrc/controller/admin/filebrowser.lua
+++ /dev/null
@@ -1,9 +0,0 @@
--- Copyright 2008 Steven Barth <steven@midlink.org>
--- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
--- Licensed to the public under the Apache License 2.0.
-
-module("luci.controller.admin.filebrowser", package.seeall)
-
-function index()
- entry( {"admin", "filebrowser"}, template("cbi/filebrowser") ).leaf = true
-end