luci-app-alist: add new package
authorTianling Shen <[email protected]>
Tue, 26 Dec 2023 02:49:28 +0000 (10:49 +0800)
committerPaul Donald <[email protected]>
Sat, 30 Dec 2023 02:45:13 +0000 (02:45 +0000)
Add LuCI interface for the AList package.

Signed-off-by: Tianling Shen <[email protected]>
applications/luci-app-alist/Makefile [new file with mode: 0644]
applications/luci-app-alist/htdocs/luci-static/resources/view/alist/config.js [new file with mode: 0644]
applications/luci-app-alist/htdocs/luci-static/resources/view/alist/log.js [new file with mode: 0644]
applications/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json [new file with mode: 0644]
applications/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json [new file with mode: 0644]

diff --git a/applications/luci-app-alist/Makefile b/applications/luci-app-alist/Makefile
new file mode 100644 (file)
index 0000000..3c66083
--- /dev/null
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: Apache-2.0
+#
+# Copyright (C) 2023 ImmortalWrt.org
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI app for AList
+LUCI_DEPENDS:=+alist
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-alist/htdocs/luci-static/resources/view/alist/config.js b/applications/luci-app-alist/htdocs/luci-static/resources/view/alist/config.js
new file mode 100644 (file)
index 0000000..25fde44
--- /dev/null
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: Apache-2.0
+
+'use strict';
+'require form';
+'require poll';
+'require rpc';
+'require uci';
+'require validation';
+'require view';
+
+var callServiceList = rpc.declare({
+       object: 'service',
+       method: 'list',
+       params: ['name'],
+       expect: { '': {} }
+});
+
+function getServiceStatus() {
+       return L.resolveDefault(callServiceList('alist'), {}).then(function (res) {
+               var isRunning = false;
+               try {
+                       isRunning = res['alist']['instances']['instance1']['running'];
+               } catch (e) { }
+               return isRunning;
+       });
+}
+
+function renderStatus(isRunning, port) {
+       var spanTemp = '<span style="color:%s"><strong>%s %s</strong></span>';
+       var renderHTML;
+       if (isRunning) {
+               var button = String.format('&#160;<a class="btn cbi-button" href="http://%s:%s" target="_blank" rel="noreferrer noopener">%s</a>',
+                       window.location.hostname, port, _('Open Web Interface'));
+               renderHTML = spanTemp.format('green', _('AList'), _('RUNNING')) + button;
+       } else {
+               renderHTML = spanTemp.format('red', _('AList'), _('NOT RUNNING'));
+       }
+
+       return renderHTML;
+}
+
+var stubValidator = {
+       factory: validation,
+       apply: function(type, value, args) {
+               if (value != null)
+                       this.value = value;
+
+               return validation.types[type].apply(this, args);
+       },
+       assert: function(condition) {
+               return !!condition;
+       }
+};
+
+return view.extend({
+       load: function() {
+               return Promise.all([
+                       uci.load('alist')
+               ]);
+       },
+
+       render: function(data) {
+               var m, s, o;
+               var webport = uci.get(data[0], 'config', 'listen_http_port') || '5244';
+
+               m = new form.Map('alist', _('AList'),
+                       _('A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.') + '<br />' +
+                       _('Default webUI/WebDAV login username is %s and password is %s.').format('<code>admin</code>', '<code>password</code>'));
+
+               s = m.section(form.TypedSection);
+               s.anonymous = true;
+               s.render = function () {
+                       poll.add(function () {
+                               return L.resolveDefault(getServiceStatus()).then(function (res) {
+                                       var view = document.getElementById('service_status');
+                                       view.innerHTML = renderStatus(res, webport);
+                               });
+                       });
+
+                       return E('div', { class: 'cbi-section', id: 'status_bar' }, [
+                                       E('p', { id: 'service_status' }, _('Collecting data...'))
+                       ]);
+               }
+
+               s = m.section(form.NamedSection, 'config', 'alist');
+
+               o = s.option(form.Flag, 'enabled', _('Enable'));
+               o.default = o.disabled;
+               o.rmempty = false;
+
+               o = s.option(form.Value, 'listen_addr', _('Listen address'));
+               o.placeholder = '0.0.0.0';
+               o.validate = function(section_id, value) {
+                       if (section_id && value) {
+                               var m4 = value.match(/^([^\[\]:]+)$/),
+                                   m6 = value.match(/^\[(.+)\]$/ );
+
+                               if ((!m4 && !m6) || !stubValidator.apply('ipaddr', m4 ? m4[1] : m6[1]))
+                                       return _('Expecting: %s').format(_('valid IP address'));
+                       }
+                       return true;
+               }
+
+               o = s.option(form.Value, 'listen_http_port', _('Listen port'));
+               o.datatype = 'port';
+               o.placeholder = '5244';
+
+               o = s.option(form.Value, 'site_login_expire', _('Login expiration time'),
+                       _('User login expiration time (in hours).'));
+               o.datatype = 'uinteger';
+               o.placeholder = '48';
+
+               o = s.option(form.Value, 'site_max_connections', _('Max connections'),
+                       _('The maximum number of concurrent connections at the same time (0 = unlimited).'));
+               o.datatype = 'uinteger';
+               o.placeholder = '0';
+
+               o = s.option(form.Flag, 'site_tls_insecure', _('Allow insecure connection'),
+                       _('Allow connection even if the remote TLS certificate is invalid (<strong>not recommended</strong>).'));
+
+               o = s.option(form.Flag, 'log_enable', _('Enable logging'));
+               o.default = o.enabled;
+
+               o = s.option(form.Value, 'log_max_size', _('Max log size'),
+                       _('The maximum size in megabytes of the log file before it gets rotated.'));
+               o.datatype = 'uinteger';
+               o.placeholder = '5';
+               o.depends('log_enable', '1');
+
+               o = s.option(form.Value, 'log_max_backups', _('Max log backups'),
+                       _('The maximum number of old log files to retain.'));
+               o.datatype = 'uinteger';
+               o.placeholder = '1';
+               o.depends('log_enable', '1');
+
+               o = s.option(form.Value, 'log_max_age', _('Max log age'),
+                       _('The maximum days of the log file to retain.'));
+               o.datatype = 'uinteger';
+               o.placeholder = '15';
+               o.depends('log_enable', '1');
+
+               return m.render();
+       }
+});
diff --git a/applications/luci-app-alist/htdocs/luci-static/resources/view/alist/log.js b/applications/luci-app-alist/htdocs/luci-static/resources/view/alist/log.js
new file mode 100644 (file)
index 0000000..f300abe
--- /dev/null
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: Apache-2.0
+
+'use strict';
+'require dom';
+'require fs';
+'require poll';
+'require uci';
+'require view';
+
+return view.extend({
+       render: function() {
+               /* Thanks to luci-app-aria2 */
+               var css = '                                     \
+                       #log_textarea {                         \
+                               padding: 10px;                  \
+                               text-align: left;               \
+                       }                                       \
+                       #log_textarea pre {                     \
+                               padding: .5rem;                 \
+                               word-break: break-all;          \
+                               margin: 0;                      \
+                       }                                       \
+                       .description {                          \
+                               background-color: #33ccff;      \
+                       }';
+
+               var log_textarea = E('div', { 'id': 'log_textarea' },
+                       E('img', {
+                               'src': L.resource(['icons/loading.gif']),
+                               'alt': _('Loading...'),
+                               'style': 'vertical-align:middle'
+                       }, _('Collecting data...'))
+               );
+
+               poll.add(L.bind(function() {
+                       return fs.read_direct('/var/run/alist/log/alist.log', 'text')
+                       .then(function(res) {
+                               var log = E('pre', { 'wrap': 'pre' }, [
+                                       res.trim() || _('Log is empty.')
+                               ]);
+
+                               dom.content(log_textarea, log);
+                       }).catch(function(err) {
+                               var log;
+
+                               if (err.toString().includes('NotFoundError'))
+                                       log = E('pre', { 'wrap': 'pre' }, [
+                                               _('Log file does not exist.')
+                                       ]);
+                               else
+                                       log = E('pre', { 'wrap': 'pre' }, [
+                                               _('Unknown error: %s').format(err)
+                                       ]);
+
+                               dom.content(log_textarea, log);
+                       });
+               }));
+
+               return E([
+                       E('style', [ css ]),
+                       E('div', {'class': 'cbi-map'}, [
+                               E('div', {'class': 'cbi-section'}, [
+                                       log_textarea,
+                                       E('div', {'style': 'text-align:right'},
+                                       E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
+                                       )
+                               ])
+                       ])
+               ]);
+       },
+
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null
+});
diff --git a/applications/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json b/applications/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json
new file mode 100644 (file)
index 0000000..084085a
--- /dev/null
@@ -0,0 +1,28 @@
+{
+       "admin/services/alist": {
+               "title": "AList",
+               "action": {
+                       "type": "firstchild"
+               },
+               "depends": {
+                       "acl": [ "luci-app-alist" ],
+                       "uci": { "alist": true }
+               }
+       },
+       "admin/services/alist/config": {
+               "title": "Settings",
+               "order": 10,
+               "action": {
+                       "type": "view",
+                       "path": "alist/config"
+               }
+       },
+       "admin/services/alist/log": {
+               "title": "Log",
+               "order": 20,
+               "action": {
+                       "type": "view",
+                       "path": "alist/log"
+               }
+       }
+}
diff --git a/applications/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json b/applications/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json
new file mode 100644 (file)
index 0000000..991dfa0
--- /dev/null
@@ -0,0 +1,17 @@
+{
+       "luci-app-alist": {
+               "description": "Grant UCI access for luci-app-alist",
+               "read": {
+                       "file": {
+                               "/var/run/alist/log/alist.log": [ "read" ]
+                       },
+                       "ubus": {
+                               "service": [ "list" ]
+                       },
+                       "uci": [ "alist" ]
+               },
+               "write": {
+                       "uci": [ "alist" ]
+               }
+       }
+}