diff --git a/alist/files/alist.init b/alist/files/alist.init index 9cde3298..a1703bb2 100755 --- a/alist/files/alist.init +++ b/alist/files/alist.init @@ -5,6 +5,7 @@ START=99 USE_PROCD=1 PROG=/usr/bin/alist +LOG_FILE=/var/log/alist.log get_config() { config_get_bool enabled $1 enabled 1 @@ -84,7 +85,7 @@ start_service() { [ "$mysql" -eq 1 ] && database=$mysql_type || database=sqlite3 set_firewall - true > $temp_dir/alist.log + true > $LOG_FILE # init config json_init @@ -134,7 +135,7 @@ start_service() { # log json_add_object "log" json_add_boolean "enable" "$log" - json_add_string "name" "$temp_dir/alist.log" + json_add_string "name" "$LOG_FILE" json_add_int "max_size" "10" json_add_int "max_backups" "5" json_add_int "max_age" "28" diff --git a/luci-app-alist/Makefile b/luci-app-alist/Makefile index 9edcd81d..e23ae803 100644 --- a/luci-app-alist/Makefile +++ b/luci-app-alist/Makefile @@ -6,19 +6,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-alist -PKG_VERSION:=1.0.13 +PKG_VERSION:=1.1.0 PKG_RELEASE:=1 LUCI_TITLE:=LuCI support for alist -LUCI_DEPENDS:=+alist +luci-compat - -define Package/$(PKG_NAME)/postinst -#!/bin/sh -[ -n "${IPKG_INSTROOT}" ] || { - ( . /etc/uci-defaults/50-luci-alist ) && rm -f /etc/uci-defaults/50-luci-alist - exit 0 -} -endef +LUCI_DEPENDS:=+alist include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js b/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js new file mode 100644 index 00000000..23c2416b --- /dev/null +++ b/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js @@ -0,0 +1,190 @@ +'use strict'; +'require form'; +'require fs'; +'require poll'; +'require rpc'; +'require uci'; +'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, webport) { + var spanTemp = '%s %s'; + var renderHTML; + if (isRunning) { + var button = String.format('', + _('Open Web Interface'), window.location.hostname, webport); + renderHTML = spanTemp.format('green', 'Alist', _('RUNNING')) + button; + } else { + renderHTML = spanTemp.format('red', 'Alist', _('NOT RUNNING')); + } + + return renderHTML; +} + +return view.extend({ + load: function () { + return Promise.all([ + uci.load('alist') + ]); + }, + + handleResetPassword: async function (data) { + var data_dir = uci.get(data[0], '@alist[0]', 'data_dir') || '/etc/alist'; + try { + var newpassword = await fs.exec('/usr/bin/alist', ['admin', 'random', '--data', data_dir]); + var new_password = newpassword.stderr.match(/password:\s*(\S+)/)[1]; + const textArea = document.createElement('textarea'); + textArea.value = new_password; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + alert(_('Username:') + 'admin\n' + _('New Password:') + new_password + '\n\n' + _('New password has been copied to clipboard.')); + } catch (error) { + console.error('Failed to reset password: ', error); + } + }, + + render: function (data) { + var m, s, o; + var webport = uci.get(data[0], '@alist[0]', 'port') || '5244'; + + m = new form.Map('alist', _('Alist'), + _('A file list program that supports multiple storage.') + + '
' + + _('User Manual') + + ''); + + s = m.section(form.TypedSection); + s.anonymous = true; + s.addremove = false; + + 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.TypedSection); + + o = s.option(form.Flag, 'enabled', _('Enabled')); + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Value, 'port', _('Port')); + o.datatype = 'and(port,min(1))'; + o.default = '5244'; + o.rmempty = false; + + o = s.option(form.Flag, 'log', _('Enable Logs')); + o.default = 1; + o.rmempty = false; + + o = s.option(form.Flag, 'ssl', _('Enable SSL')); + o.rmempty = false; + + o = s.option(form.Value, 'ssl_cert', _('SSL cert'), + _('SSL certificate file path')); + o.depends('ssl', '1'); + + o = s.option(form.Value, 'ssl_key', _('SSL key'), + _('SSL key file path')); + o.depends('ssl', '1'); + + o = s.option(form.Flag, 'mysql', _('Enable Database')); + o.rmempty = false; + + o = s.option(form.ListValue, 'mysql_type', _('Database Type')); + o.default = 'mysql'; + o.depends('mysql', '1'); + o.value('mysql', _('MySQL')); + o.value('postgres', _('PostgreSQL')); + + o = s.option(form.Value, 'mysql_host', _('Database Host')); + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_port', _('Database Port')); + o.datatype = 'port'; + o.default = '3306'; + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_username', _('Database Username')); + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_password', _('Database Password')); + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_database', _('Database Name')); + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_table_prefix', _('Database Table Prefix')); + o.default = 'x_'; + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_ssl_mode', _('Database SSL Mode')); + o.depends('mysql', '1'); + + o = s.option(form.Value, 'mysql_dsn', _('Database DSN')); + o.depends('mysql', '1'); + + o = s.option(form.Flag, 'allow_wan', _('Allow Access From Internet')); + o.rmempty = false; + + o = s.option(form.Value, 'site_url', _('Site URL'), + _('When the web is reverse proxied to a subdirectory, this option must be filled out to ensure proper functioning of the web. Do not include \'/\' at the end of the URL')); + + o = s.option(form.Value, 'max_connections', _('Max Connections'), + _('0 is unlimited, It is recommend to set a low number of concurrency (10-20) for poor performance device')); + o.default = '0'; + o.datatype = 'uinteger'; + o.rmempty = false; + + o = s.option(form.Value, 'token_expires_in', _('Login Validity Period (hours)')); + o.datatype = 'uinteger'; + o.default = '48'; + o.rmempty = false; + + o = s.option(form.Value, 'delayed_start', _('Delayed Start (seconds)')); + o.datatype = 'uinteger'; + o.default = '0'; + o.rmempty = false; + + o = s.option(form.Value, 'data_dir', _('Data directory')); + o.default = '/etc/alist'; + + o = s.option(form.Value, 'temp_dir', _('Cache directory')); + o.default = '/tmp/alist'; + o.rmempty = false; + + o = s.option(form.Button, '_newpassword', _('Reset Password'), + _('Generate a new random password.')); + o.inputtitle = _('Reset Password'); + o.inputstyle = 'apply'; + o.onclick = L.bind(this.handleResetPassword, this, data); + + return m.render(); + } +}); diff --git a/luci-app-alist/htdocs/luci-static/resources/view/alist/logs.js b/luci-app-alist/htdocs/luci-static/resources/view/alist/logs.js new file mode 100644 index 00000000..6077c3c3 --- /dev/null +++ b/luci-app-alist/htdocs/luci-static/resources/view/alist/logs.js @@ -0,0 +1,76 @@ +'use strict'; +'require dom'; +'require fs'; +'require poll'; +'require view'; + +function pollLog(e) { + return Promise.all([ + fs.read_direct('/var/log/alist.log', 'text').then(function (res) { + return res.trim().split(/\n/).join('\n').replace(/\u001b\[33mWARN\u001b\[0m/g, '').replace(/\u001b\[36mINFO\u001b\[0m/g, ''); + }), + ]).then(function (data) { + var logTextarea = E('textarea', { 'class': 'cbi-input-textarea', 'wrap': 'off', 'readonly': 'readonly', 'style': 'width: calc(100% - 20px);height: 500px;margin: 10px;overflow-y: scroll;' }, [ + data[0] || _('No log data.') + ]); + + // Store the current scroll position + var storedScrollTop = e.querySelector('textarea') ? e.querySelector('textarea').scrollTop : null; + + dom.content(e, logTextarea); + + // If the storedScrollTop is not null, it means we have a previous scroll position + if (storedScrollTop !== null) { + logTextarea.scrollTop = storedScrollTop; + } + + // Add event listener to save the scroll position when scrolling stops + var timer; + logTextarea.addEventListener('scroll', function () { + clearTimeout(timer); + timer = setTimeout(function () { + storeScrollPosition(logTextarea.scrollTop); + }, 150); + }); + + function storeScrollPosition(scrollPos) { + localStorage.setItem("scrollPosition", JSON.stringify({ "log": scrollPos })); + } + + }); +}; + +return view.extend({ + handleCleanLogs: function () { + return fs.write('/var/log/alist.log', '') + .catch(function (e) { ui.addNotification(null, E('p', e.message)) }); + }, + + render: function () { + 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(pollLog.bind(this, log_textarea)); + var clear_logs_button = E('input', { 'class': 'btn cbi-button-action', 'type': 'button', 'style': 'margin-left: 10px; margin-top: 10px;', 'value': _('Clear logs') }); + clear_logs_button.addEventListener('click', this.handleCleanLogs.bind(this)); + return E([ + E('div', { 'class': 'cbi-map' }, [ + E('div', { 'class': 'cbi-section' }, [ + clear_logs_button, + log_textarea, + E('div', { 'style': 'text-align:right' }, + E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval)) + ) + ])]) + ]); + }, + + handleSave: null, + handleSaveApply: null, + handleReset: null +}); diff --git a/luci-app-alist/luasrc/controller/alist.lua b/luci-app-alist/luasrc/controller/alist.lua deleted file mode 100644 index d611d5cf..00000000 --- a/luci-app-alist/luasrc/controller/alist.lua +++ /dev/null @@ -1,50 +0,0 @@ -module("luci.controller.alist", package.seeall) - -function index() - if not nixio.fs.access("/etc/config/alist") then - return - end - - local page = entry({"admin", "nas", "alist"}, alias("admin", "nas", "alist", "basic"), _("Alist"), 20) - page.dependent = true - page.acl_depends = { "luci-app-alist" } - - entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false - entry({"admin", "nas", "alist", "basic"}, cbi("alist/basic"), _("Basic Setting"), 1).leaf = true - entry({"admin", "nas", "alist", "log"}, cbi("alist/log"), _("Logs"), 2).leaf = true - entry({"admin", "nas", "alist", "alist_status"}, call("alist_status")).leaf = true - entry({"admin", "nas", "alist", "get_log"}, call("get_log")).leaf = true - entry({"admin", "nas", "alist", "clear_log"}, call("clear_log")).leaf = true - entry({"admin", "nas", "alist", "admin_info"}, call("admin_info")).leaf = true -end - -function alist_status() - local sys = require "luci.sys" - local uci = require "luci.model.uci".cursor() - local port = tonumber(uci:get_first("alist", "alist", "port")) - - local status = { - running = (sys.call("pidof alist >/dev/null") == 0), - port = (port or 5244) - } - - luci.http.prepare_content("application/json") - luci.http.write_json(status) -end - -function get_log() - luci.http.write(luci.sys.exec("cat $(uci -q get alist.@alist[0].temp_dir)/alist.log")) -end - -function clear_log() - luci.sys.call("cat /dev/null > $(uci -q get alist.@alist[0].temp_dir)/alist.log") -end - -function admin_info() - local random = luci.sys.exec("/usr/bin/alist --data $(uci -q get alist.@alist[0].data_dir) admin random 2>&1") - local username = string.match(random, "username: (%S+)") - local password = string.match(random, "password: (%S+)") - - luci.http.prepare_content("application/json") - luci.http.write_json({username = username, password = password}) -end diff --git a/luci-app-alist/luasrc/model/cbi/alist/basic.lua b/luci-app-alist/luasrc/model/cbi/alist/basic.lua deleted file mode 100644 index ec5587a8..00000000 --- a/luci-app-alist/luasrc/model/cbi/alist/basic.lua +++ /dev/null @@ -1,113 +0,0 @@ -local m, s - -m = Map("alist", translate("Alist"), translate("A file list program that supports multiple storage.") .. "
" .. [[]] .. translate("User Manual") .. [[]]) - -m:section(SimpleSection).template = "alist/alist_status" - -s = m:section(TypedSection, "alist") -s.addremove = false -s.anonymous = true - -o = s:option(Flag, "enabled", translate("Enabled")) -o.rmempty = false - -o = s:option(Value, "port", translate("Port")) -o.datatype = "and(port,min(1))" -o.rmempty = false -o.default = "5244" - -o = s:option(Flag, "log", translate("Enable Logs")) -o.default = 1 -o.rmempty = false - -o = s:option(Flag, "ssl", translate("Enable SSL")) -o.rmempty=false - -o = s:option(Value,"ssl_cert", translate("SSL cert"), translate("SSL certificate file path")) -o.datatype = "file" -o:depends("ssl", "1") - -o = s:option(Value,"ssl_key", translate("SSL key"), translate("SSL key file path")) -o.datatype = "file" -o:depends("ssl", "1") - -o = s:option(Flag, "mysql", translate("Enable Database")) -o.rmempty=false - -o = s:option(ListValue, "mysql_type", translate("Database Type")) -o.datatype = "string" -o:value("mysql", translate("MySQL")) -o:value("postgres", translate("PostgreSQL")) -o.default = "mysql" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_host", translate("Database Host")) -o.datatype = "string" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_port", translate("Database Port")) -o.datatype = "and(port,min(1))" -o.default = "3306" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_username", translate("Database Username")) -o.datatype = "string" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_password", translate("Database Password")) -o.datatype = "string" -o.password = true -o:depends("mysql", "1") - -o = s:option(Value,"mysql_database", translate("Database Name")) -o.datatype = "string" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_table_prefix", translate("Database Table Prefix")) -o.datatype = "string" -o.default = "x_" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_ssl_mode", translate("Database SSL Mode")) -o.datatype = "string" -o:depends("mysql", "1") - -o = s:option(Value,"mysql_dsn", translate("Database DSN")) -o.datatype = "string" -o:depends("mysql", "1") - -o = s:option(Flag, "allow_wan", translate("Allow Access From Internet")) -o.rmempty = false - -o = s:option(Value, "site_url", translate("Site URL"), translate("When the web is reverse proxied to a subdirectory, this option must be filled out to ensure proper functioning of the web. Do not include '/' at the end of the URL")) -o.datatype = "string" - -o = s:option(Value, "max_connections", translate("Max Connections"), translate("0 is unlimited, It is recommend to set a low number of concurrency (10-20) for poor performance device")) -o.datatype = "and(uinteger,min(0))" -o.default = "0" -o.rmempty = false - -o = s:option(Value, "token_expires_in", translate("Login Validity Period (hours)")) -o.datatype = "and(uinteger,min(1))" -o.default = "48" -o.rmempty = false - -o = s:option(Value, "delayed_start", translate("Delayed Start (seconds)")) -o.datatype = "and(uinteger,min(0))" -o.default = "0" -o.rmempty = false - -o = s:option(Value, "data_dir", translate("Data directory")) -o.datatype = "string" -o.default = "/etc/alist" - -o = s:option(Value, "temp_dir", translate("Cache directory")) -o.datatype = "string" -o.default = "/tmp/alist" -o.rmempty = false - -o = s:option(Button, "admin_info", translate("Reset Password")) -o.rawhtml = true -o.template = "alist/admin_info" - -return m diff --git a/luci-app-alist/luasrc/model/cbi/alist/log.lua b/luci-app-alist/luasrc/model/cbi/alist/log.lua deleted file mode 100644 index 4b6c36f8..00000000 --- a/luci-app-alist/luasrc/model/cbi/alist/log.lua +++ /dev/null @@ -1,5 +0,0 @@ -m = Map("alist") - -m:append(Template("alist/alist_log")) - -return m diff --git a/luci-app-alist/luasrc/view/alist/admin_info.htm b/luci-app-alist/luasrc/view/alist/admin_info.htm deleted file mode 100644 index ba34e91b..00000000 --- a/luci-app-alist/luasrc/view/alist/admin_info.htm +++ /dev/null @@ -1,26 +0,0 @@ -<%+cbi/valueheader%> - - -<%=self.value%> -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-alist/luasrc/view/alist/alist_log.htm b/luci-app-alist/luasrc/view/alist/alist_log.htm deleted file mode 100644 index 5ec4a78b..00000000 --- a/luci-app-alist/luasrc/view/alist/alist_log.htm +++ /dev/null @@ -1,35 +0,0 @@ - -
- - -
diff --git a/luci-app-alist/luasrc/view/alist/alist_status.htm b/luci-app-alist/luasrc/view/alist/alist_status.htm deleted file mode 100644 index 6be654ea..00000000 --- a/luci-app-alist/luasrc/view/alist/alist_status.htm +++ /dev/null @@ -1,36 +0,0 @@ -<% - local uci = require 'luci.model.uci'.cursor() - ssl = uci:get_first('alist', 'alist', 'ssl') - if ssl == '1' then - protocol="https://" - else - protocol="http://" - end -%> - - - - -
-

- <%:Collecting data...%> -

-
diff --git a/luci-app-alist/po/zh-cn b/luci-app-alist/po/zh-cn new file mode 120000 index 00000000..8d69574d --- /dev/null +++ b/luci-app-alist/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/luci-app-alist/po/zh_Hans b/luci-app-alist/po/zh_Hans deleted file mode 120000 index 41451e4a..00000000 --- a/luci-app-alist/po/zh_Hans +++ /dev/null @@ -1 +0,0 @@ -zh-cn \ No newline at end of file diff --git a/luci-app-alist/po/zh-cn/alist.po b/luci-app-alist/po/zh_Hans/alist.po similarity index 91% rename from luci-app-alist/po/zh-cn/alist.po rename to luci-app-alist/po/zh_Hans/alist.po index 8f7ea132..ec63260c 100644 --- a/luci-app-alist/po/zh-cn/alist.po +++ b/luci-app-alist/po/zh_Hans/alist.po @@ -67,14 +67,17 @@ msgstr "清空日志" msgid "Reset Password" msgstr "重置密码" -msgid "Reset" -msgstr "重置" +msgid "Generate a new random password." +msgstr "随机生成一个新密码。" msgid "Username:" msgstr "用户名:" -msgid "Password:" -msgstr "密码:" +msgstr "New Password:" +msgstr "新密码:" + +msgstr "New password has been copied to clipboard." +msgstr "新密码已复制到剪贴板。" msgid "Login Validity Period (hours)" msgstr "登录有效期(小时)" diff --git a/luci-app-alist/root/etc/uci-defaults/50-luci-alist b/luci-app-alist/root/etc/uci-defaults/luci-app-alist similarity index 100% rename from luci-app-alist/root/etc/uci-defaults/50-luci-alist rename to luci-app-alist/root/etc/uci-defaults/luci-app-alist diff --git a/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json b/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json new file mode 100644 index 00000000..295910a9 --- /dev/null +++ b/luci-app-alist/root/usr/share/luci/menu.d/luci-app-alist.json @@ -0,0 +1,36 @@ +{ + "admin/nas": { + "title": "NAS", + "order": 44, + "action": { + "type": "firstchild" + } + }, + "admin/nas/alist": { + "title": "Alist", + "order": 20, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": [ "luci-app-alist" ], + "uci": { "alist": true } + } + }, + "admin/nas/alist/basic": { + "title": "Basic Setting", + "order": 30, + "action": { + "type": "view", + "path": "alist/basic" + } + }, + "admin/nas/alist/logs": { + "title": "Logs", + "order": 40, + "action": { + "type": "view", + "path": "alist/logs" + } + } +} diff --git a/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json b/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json index 98ba9422..46d12af5 100644 --- a/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json +++ b/luci-app-alist/root/usr/share/rpcd/acl.d/luci-app-alist.json @@ -2,9 +2,19 @@ "luci-app-alist": { "description": "Grant UCI access for luci-app-alist", "read": { + "file": { + "/usr/bin/alist": [ "exec" ], + "/var/log/alist.log": [ "read" ] + }, + "ubus": { + "service": [ "list" ] + }, "uci": [ "alist" ] }, "write": { + "file": { + "/var/log/alist.log": [ "write" ] + }, "uci": [ "alist" ] } }