Files
openwrt-luci-app-copyparty/www/luci-static/resources/view/copyparty.js
Matt 617276b285 Initial release: procd service and LuCI app for copyparty
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
2026-05-16 03:07:37 -04:00

135 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;
});
});
}
});