luci-base: use cgi-io and rpcd-mod-file to handle file upload and browsing
Remove the old server side support for file browsing and file uploading and switch to a client side widget instead which uses XMLHTTPRequests to upload files via cgi-io and RPC calls for file listing and status queries. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
1fcf34510a
commit
3f93650901
8 changed files with 462 additions and 169 deletions
|
@ -12,7 +12,7 @@ LUCI_TYPE:=mod
|
||||||
LUCI_BASENAME:=base
|
LUCI_BASENAME:=base
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI core libraries
|
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
|
PKG_SOURCE:=v1.0.0.tar.gz
|
||||||
|
|
|
@ -298,8 +298,6 @@ function cbi_init() {
|
||||||
node.getAttribute('data-type'));
|
node.getAttribute('data-type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('[data-browser]').forEach(cbi_browser_init);
|
|
||||||
|
|
||||||
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
|
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
|
||||||
s.parentNode.classList.add('cbi-tooltip-container');
|
s.parentNode.classList.add('cbi-tooltip-container');
|
||||||
});
|
});
|
||||||
|
@ -330,29 +328,6 @@ function cbi_init() {
|
||||||
cbi_d_update();
|
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)
|
function cbi_validate_form(form, errmsg)
|
||||||
{
|
{
|
||||||
/* if triggered by a section removal or addition, don't validate */
|
/* if triggered by a section removal or addition, don't validate */
|
||||||
|
|
|
@ -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({
|
var CBISectionValue = CBIValue.extend({
|
||||||
__name__: 'CBI.ContainerValue',
|
__name__: 'CBI.ContainerValue',
|
||||||
__init__: function(map, section, option, cbiClass /*, ... */) {
|
__init__: function(map, section, option, cbiClass /*, ... */) {
|
||||||
|
@ -1726,5 +1752,6 @@ return L.Class.extend({
|
||||||
DummyValue: CBIDummyValue,
|
DummyValue: CBIDummyValue,
|
||||||
Button: CBIButtonValue,
|
Button: CBIButtonValue,
|
||||||
HiddenValue: CBIHiddenValue,
|
HiddenValue: CBIHiddenValue,
|
||||||
|
FileUpload: CBIFileUpload,
|
||||||
SectionValue: CBISectionValue
|
SectionValue: CBISectionValue
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
'require rpc';
|
||||||
'require uci';
|
'require uci';
|
||||||
'require validation';
|
'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({
|
return L.Class.extend({
|
||||||
__init__: function() {
|
__init__: function() {
|
||||||
|
@ -2173,5 +2585,6 @@ return L.Class.extend({
|
||||||
Dropdown: UIDropdown,
|
Dropdown: UIDropdown,
|
||||||
DynamicList: UIDynamicList,
|
DynamicList: UIDynamicList,
|
||||||
Combobox: UICombobox,
|
Combobox: UICombobox,
|
||||||
Hiddenfield: UIHiddenfield
|
Hiddenfield: UIHiddenfield,
|
||||||
|
FileUpload: UIFileUpload
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
|
|
@ -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%>
|
<%+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 %>
|
<div<%=attr("data-ui-widget", luci.util.serialize_json({
|
||||||
<input type="hidden"<%= attr("value", v) .. attr("name", "cbi.rlf." .. section .. "." .. self.option) .. attr("id", "cbi.rlf." .. section .. "." .. self.option) %> />
|
"FileUpload", self:cfgvalue(section) or self.default, {
|
||||||
<% end %>
|
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%>
|
<%+cbi/valuefooter%>
|
||||||
|
|
|
@ -20,7 +20,12 @@
|
||||||
"luci-access": {
|
"luci-access": {
|
||||||
"description": "Grant access to basic LuCI procedures",
|
"description": "Grant access to basic LuCI procedures",
|
||||||
"read": {
|
"read": {
|
||||||
|
"file": {
|
||||||
|
"/": [ "list" ],
|
||||||
|
"/*": [ "list" ]
|
||||||
|
},
|
||||||
"ubus": {
|
"ubus": {
|
||||||
|
"file": [ "list", "stat" ],
|
||||||
"iwinfo": [ "assoclist" ],
|
"iwinfo": [ "assoclist" ],
|
||||||
"luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices", "getWirelessDevices" ],
|
"luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices", "getWirelessDevices" ],
|
||||||
"network.device": [ "status" ],
|
"network.device": [ "status" ],
|
||||||
|
@ -32,7 +37,12 @@
|
||||||
"uci": [ "*" ]
|
"uci": [ "*" ]
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
|
"cgi-io": [ "upload", "/etc/luci-uploads/*" ],
|
||||||
|
"file": {
|
||||||
|
"/etc/luci-uploads/*": [ "write" ]
|
||||||
|
},
|
||||||
"ubus": {
|
"ubus": {
|
||||||
|
"file": [ "remove" ],
|
||||||
"iwinfo": [ "scan" ],
|
"iwinfo": [ "scan" ],
|
||||||
"luci": [ "setInitAction", "setLocaltime" ],
|
"luci": [ "setInitAction", "setLocaltime" ],
|
||||||
"uci": [ "add", "apply", "confirm", "delete", "order", "set" ]
|
"uci": [ "add", "apply", "confirm", "delete", "order", "set" ]
|
||||||
|
|
|
@ -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
|
|
Loading…
Reference in a new issue