diff --git a/luci-app-alist/Makefile b/luci-app-alist/Makefile
index e23ae803..9edcd81d 100644
--- a/luci-app-alist/Makefile
+++ b/luci-app-alist/Makefile
@@ -6,11 +6,19 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-alist
-PKG_VERSION:=1.1.0
+PKG_VERSION:=1.0.13
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for alist
-LUCI_DEPENDS:=+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
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
deleted file mode 100644
index bb40c41c..00000000
--- a/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js
+++ /dev/null
@@ -1,199 +0,0 @@
-'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']['alist']['running'];
- } catch (e) { }
- return isRunning;
- });
-}
-
-function renderStatus(isRunning, protocol, webport) {
- var spanTemp = '%s %s';
- var renderHTML;
- if (isRunning) {
- var button = String.format('',
- _('Open Web Interface'), protocol, 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';
- var ssl = uci.get(data[0], '@alist[0]', 'ssl') || '0';
- var protocol;
- if (ssl === '0') {
- protocol = 'http:';
- } else if (ssl === '1') {
- protocol = 'https:';
- }
-
- 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, protocol, webport);
- });
- });
-
- return E('div', { class: 'cbi-section', id: 'status_bar' }, [
- E('p', { id: 'service_status' }, _('Collecting data...'))
- ]);
- }
-
- s = m.section(form.NamedSection, '@alist[0]', 'alist');
-
- 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.rmempty = false;
- o.depends('ssl', '1');
-
- o = s.option(form.Value, 'ssl_key', _('SSL key'),
- _('SSL key file path'));
- o.rmempty = false;
- 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
deleted file mode 100644
index 6077c3c3..00000000
--- a/luci-app-alist/htdocs/luci-static/resources/view/alist/logs.js
+++ /dev/null
@@ -1,76 +0,0 @@
-'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
new file mode 100644
index 00000000..e664683c
--- /dev/null
+++ b/luci-app-alist/luasrc/controller/alist.lua
@@ -0,0 +1,50 @@
+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 /var/log/alist.log"))
+end
+
+function clear_log()
+ luci.sys.call("cat /dev/null > /var/log/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
new file mode 100644
index 00000000..ec5587a8
--- /dev/null
+++ b/luci-app-alist/luasrc/model/cbi/alist/basic.lua
@@ -0,0 +1,113 @@
+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
new file mode 100644
index 00000000..4b6c36f8
--- /dev/null
+++ b/luci-app-alist/luasrc/model/cbi/alist/log.lua
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 00000000..ba34e91b
--- /dev/null
+++ b/luci-app-alist/luasrc/view/alist/admin_info.htm
@@ -0,0 +1,26 @@
+<%+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
new file mode 100644
index 00000000..5ec4a78b
--- /dev/null
+++ b/luci-app-alist/luasrc/view/alist/alist_log.htm
@@ -0,0 +1,35 @@
+
+