OpenWrt procd service script and LuCI JavaScript interface for copyparty (https://github.com/9001/copyparty), a self-hosted file sharing server. Features: - procd service with UCI-driven volumes, accounts, and TLS cert - LuCI view: settings, volume/account grids, live status + start/stop - ACME TLS support via combined key+fullchain PEM (--cert flag) - Auto-respawn and reload-on-config-change via service_triggers
135 lines
3.9 KiB
JavaScript
135 lines
3.9 KiB
JavaScript
'use strict';
|
||
'require form';
|
||
'require rpc';
|
||
'require ui';
|
||
'require view';
|
||
|
||
var callInitAction = rpc.declare({
|
||
object: 'rc',
|
||
method: 'init',
|
||
params: ['name', 'action'],
|
||
expect: { result: false }
|
||
});
|
||
|
||
var callServiceList = rpc.declare({
|
||
object: 'service',
|
||
method: 'list',
|
||
params: ['name'],
|
||
expect: { '': {} }
|
||
});
|
||
|
||
return view.extend({
|
||
handleStart: function() {
|
||
return callInitAction('copyparty', 'start').then(function() {
|
||
return window.location.reload();
|
||
});
|
||
},
|
||
|
||
handleStop: function() {
|
||
return callInitAction('copyparty', 'stop').then(function() {
|
||
return window.location.reload();
|
||
});
|
||
},
|
||
|
||
render: function() {
|
||
var self = this;
|
||
|
||
return callServiceList('copyparty').then(function(res) {
|
||
var instances = ((res.copyparty || {}).instances) || {};
|
||
var running = Object.keys(instances).some(function(k) {
|
||
return instances[k].running;
|
||
});
|
||
|
||
var m, s, o;
|
||
|
||
m = new form.Map('copyparty', _('Copyparty'),
|
||
_('Self-hosted file sharing server. Web UI at <a href="https://%s:3923" target="_blank">port 3923</a>.').format(window.location.hostname));
|
||
|
||
/* ── Global settings ── */
|
||
s = m.section(form.NamedSection, 'config', 'copyparty', _('Settings'));
|
||
s.addremove = false;
|
||
|
||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Value, 'port', _('Port'));
|
||
o.datatype = 'port';
|
||
o.default = '3923';
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Value, 'name', _('Server name'));
|
||
o.default = 'OWRT-NAS';
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Value, 'script', _('Script path'));
|
||
o.default = '/mnt/raid/copyparty-en.py';
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Flag, 'usernames', _('Require usernames'),
|
||
_('If enabled, clients must supply a username in addition to password.'));
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Value, 'tls_cert', _('TLS certificate'),
|
||
_('Path to a PEM file containing the private key followed by the full certificate chain. Leave empty for HTTP.'));
|
||
o.placeholder = '/etc/acme/owrt-nas.lan.1qaz.ca_ecc/copyparty.pem';
|
||
o.rmempty = true;
|
||
|
||
/* ── Volumes ── */
|
||
s = m.section(form.GridSection, 'volume', _('Volumes'),
|
||
_('Each volume maps a filesystem path to a URL path. Flags: r=read, w=write, m=move, d=delete, g=get.'));
|
||
s.addremove = true;
|
||
s.anonymous = true;
|
||
s.addbtntitle = _('Add volume...');
|
||
|
||
o = s.option(form.Value, 'src', _('Source path'));
|
||
o.rmempty = false;
|
||
o.placeholder = '/mnt/raid/firstshare';
|
||
|
||
o = s.option(form.Value, 'dst', _('URL path'));
|
||
o.rmempty = false;
|
||
o.placeholder = '/files';
|
||
|
||
o = s.option(form.ListValue, 'flags', _('Access'));
|
||
o.value('r', _('Read-only'));
|
||
o.value('rw', _('Read/write'));
|
||
o.value('rwmd', _('Read/write/move/delete'));
|
||
o.default = 'r';
|
||
|
||
/* ── Accounts ── */
|
||
s = m.section(form.GridSection, 'account', _('Accounts'),
|
||
_('Leave empty to allow anonymous access. Passwords are stored in plaintext in UCI.'));
|
||
s.addremove = true;
|
||
s.anonymous = true;
|
||
s.addbtntitle = _('Add account...');
|
||
|
||
o = s.option(form.Value, 'user', _('Username'));
|
||
o.rmempty = false;
|
||
|
||
o = s.option(form.Value, 'pass', _('Password'));
|
||
o.rmempty = false;
|
||
o.password = true;
|
||
|
||
return m.render().then(function(node) {
|
||
var statusBadge = E('span', {
|
||
style: 'font-weight:bold; color:' + (running ? '#2bab2b' : '#cc0000')
|
||
}, running ? _('Running') : _('Stopped'));
|
||
|
||
var btn = E('button', {
|
||
class: 'cbi-button cbi-button-' + (running ? 'negative' : 'apply'),
|
||
click: ui.createHandlerFn(self, running ? 'handleStop' : 'handleStart')
|
||
}, running ? _('Stop') : _('Start'));
|
||
|
||
var statusDiv = E('div', { class: 'cbi-section' }, [
|
||
E('h3', {}, _('Status')),
|
||
E('div', { class: 'cbi-section-node' }, [
|
||
E('p', { style: 'margin:0' }, [ statusBadge, ' ', btn ])
|
||
])
|
||
]);
|
||
|
||
node.insertBefore(statusDiv, node.querySelector('.cbi-section'));
|
||
return node;
|
||
});
|
||
});
|
||
}
|
||
});
|