luci-app-openlist: new package

Signed-off-by: sbwml <admin@cooluc.com>
This commit is contained in:
sbwml 2025-06-15 02:51:13 +08:00
parent 19e2f6b047
commit 9391fcb990
8 changed files with 811 additions and 0 deletions

View File

@ -0,0 +1,17 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openlist
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for openlist
LUCI_DEPENDS:=+openlist
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,384 @@
'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('openlist'), {}).then(function (res) {
var isRunning = false;
try {
isRunning = res['openlist']['instances']['openlist']['running'];
} catch (e) { }
return isRunning;
});
}
function renderStatus(isRunning, protocol, webport) {
var spanTemp = '<em><span style="color:%s"><strong>%s %s</strong></span></em>';
var renderHTML;
if (isRunning) {
var button = String.format('<input class="cbi-button-reload" type="button" style="margin-left: 50px" value="%s" onclick="window.open(\'%s//%s:%s/\')">',
_('Open Web Interface'), protocol, window.location.hostname, webport);
renderHTML = spanTemp.format('green', 'OpenList', _('RUNNING')) + button;
} else {
renderHTML = spanTemp.format('red', 'OpenList', _('NOT RUNNING'));
}
return renderHTML;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('openlist')
]);
},
handleResetPassword: async function (data) {
var data_dir = uci.get(data[0], '@openlist[0]', 'data_dir') || '/etc/openlist';
try {
var newpassword = await fs.exec('/usr/bin/openlist', ['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], '@openlist[0]', 'port') || '5244';
var ssl = uci.get(data[0], '@openlist[0]', 'ssl') || '0';
var protocol;
if (ssl === '0') {
protocol = 'http:';
} else if (ssl === '1') {
protocol = 'https:';
}
m = new form.Map('openlist', _('OpenList'),
_('A file list program that supports multiple storage.'));
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, '@openlist[0]', 'openlist');
s.tab('basic', _('Basic Settings'));
s.tab('global', _('Global Settings'));
s.tab("log", _("Logs"));
s.tab("database", _("Database"));
s.tab("scheme", _("Web Protocol"));
s.tab('tasks', _('Task threads'));
s.tab('cors', _('CORS Settings'));
s.tab('s3', _('Object Storage'));
s.tab('ftp', _('FTP'));
s.tab('sftp', _('SFTP'));
// init
o = s.taboption('basic', form.Flag, 'enabled', _('Enabled'));
o.default = o.disabled;
o.rmempty = false;
o = s.taboption('basic', form.Value, 'port', _('Port'));
o.datatype = 'and(port,min(1))';
o.default = '5244';
o.rmempty = false;
o = s.taboption('basic', form.Value, 'delayed_start', _('Delayed Start (seconds)'));
o.datatype = 'uinteger';
o.default = '0';
o.rmempty = false;
o = s.taboption('basic', form.Flag, 'allow_wan', _('Open firewall port'));
o.rmempty = false;
o = s.taboption('basic', form.Value, 'data_dir', _('Data directory'));
o.default = '/etc/openlist';
o = s.taboption('basic', form.Value, 'temp_dir', _('Cache directory'));
o.default = '/tmp/openlist';
o.rmempty = false;
o = s.taboption('basic', 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);
// global
o = s.taboption('global', form.Flag, 'force', _('Force read config'),
_('Setting this to true will force the program to read the configuration file, ignoring environment variables.'));
o.default = true;
o.rmempty = false;
o = s.taboption('global', 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.taboption('global', form.Value, 'cdn', _('CDN URL'));
o.default = '';
o = s.taboption('global', form.Value, 'jwt_secret', _('JWT Key'));
o.default = '';
o = s.taboption('global', form.Value, 'token_expires_in', _('Login Validity Period (hours)'));
o.datatype = 'uinteger';
o.default = '48';
o.rmempty = false;
o = s.taboption('global', 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.taboption('global', form.Value, 'max_concurrency', _('Max concurrency of local proxies'),
_('0 is unlimited, Limit the maximum concurrency of local agents. The default value is 64'));
o.default = '0';
o.datatype = 'uinteger';
o.rmempty = false;
o = s.taboption('global', form.Flag, 'tls_insecure_skip_verify', _('Disable TLS Verify'));
o.default = true;
o.rmempty = false;
// Logs
o = s.taboption('log', form.Flag, 'log', _('Enable Logs'));
o.default = 1;
o.rmempty = false;
o = s.taboption('log', form.Value, 'log_path', _('Log path'));
o.default = '/var/log/openlist.log';
o.rmempty = false;
o.depends('log', '1');
o = s.taboption('log', form.Value, 'log_max_size', _('Max Size (MB)'));
o.datatype = 'uinteger';
o.default = '10';
o.rmempty = false;
o.depends('log', '1');
o = s.taboption('log', form.Value, 'log_max_backups', _('Max backups'));
o.datatype = 'uinteger';
o.default = '5';
o.rmempty = false;
o.depends('log', '1');
o = s.taboption('log', form.Value, 'log_max_age', _('Max age'));
o.datatype = 'uinteger';
o.default = '28';
o.rmempty = false;
o.depends('log', '1');
o = s.taboption('log', form.Flag, 'log_compress', _('Log Compress'));
o.default = 'false';
o.rmempty = false;
o.depends('log', '1');
// database
o = s.taboption('database', form.ListValue, 'database_type', _('Database Type'));
o.default = 'sqlite3';
o.value('sqlite3', _('SQLite'));
o.value('mysql', _('MySQL'));
o.value('postgres', _('PostgreSQL'));
o = s.taboption('database', form.Value, 'mysql_host', _('Database Host'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_port', _('Database Port'));
o.datatype = 'port';
o.default = '3306';
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_username', _('Database Username'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_password', _('Database Password'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_database', _('Database Name'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_table_prefix', _('Database Table Prefix'));
o.default = 'x_';
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_ssl_mode', _('Database SSL Mode'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
o = s.taboption('database', form.Value, 'mysql_dsn', _('Database DSN'));
o.depends('database_type','mysql');
o.depends('database_type','postgres');
// scheme
o = s.taboption('scheme', form.Flag, 'ssl', _('Enable SSL'));
o.rmempty = false;
o = s.taboption('scheme', form.Flag, 'force_https', _('Force HTTPS'));
o.rmempty = false;
o.depends('ssl', '1');
o = s.taboption('scheme', form.Value, 'ssl_cert', _('SSL cert'),
_('SSL certificate file path'));
o.rmempty = false;
o.depends('ssl', '1');
o = s.taboption('scheme', form.Value, 'ssl_key', _('SSL key'),
_('SSL key file path'));
o.rmempty = false;
o.depends('ssl', '1');
// tasks
o = s.taboption('tasks', form.Value, 'download_workers', _('Download Workers'));
o.datatype = 'uinteger';
o.default = '5';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'download_max_retry', _('Download Max Retry'));
o.datatype = 'uinteger';
o.default = '1';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'transfer_workers', _('Transfer Workers'));
o.datatype = 'uinteger';
o.default = '5';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'transfer_max_retry', _('Transfer Max Retry'));
o.datatype = 'uinteger';
o.default = '2';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'upload_workers', _('Upload Workers'));
o.datatype = 'uinteger';
o.default = '5';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'upload_max_retry', _('Upload Max Retry'));
o.datatype = 'uinteger';
o.default = '0';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'copy_workers', _('Copy Workers'));
o.datatype = 'uinteger';
o.default = '5';
o.rmempty = false;
o = s.taboption('tasks', form.Value, 'copy_max_retry', _('Copy Max Retry'));
o.datatype = 'uinteger';
o.default = '2';
o.rmempty = false;
// cors
o = s.taboption('cors', form.Value, 'cors_allow_origins', _('Allow Origins'));
o.default = '*';
o.rmempty = false;
o = s.taboption('cors', form.Value, 'cors_allow_methods', _('Allow Methods'));
o.default = '*';
o.rmempty = false;
o = s.taboption('cors', form.Value, 'cors_allow_headers', _('Allow Headers'));
o.default = '*';
o.rmempty = false;
// s3
o = s.taboption('s3', form.Flag, 's3', _('Enabled S3'));
o.rmempty = false;
o = s.taboption('s3', form.Value, 's3_port', _('Port'));
o.datatype = 'and(port,min(1))';
o.default = 5246;
o.rmempty = false;
o = s.taboption('s3', form.Flag, 's3_ssl', _('Enable SSL'));
o.rmempty = false;
// ftp
o = s.taboption('ftp', form.Flag, 'ftp', _('Enabled FTP'));
o.rmempty = false;
o = s.taboption('ftp', form.Value, 'ftp_port', _('FTP Port'));
o.datatype = 'and(port,min(1))';
o.default = 5221;
o.rmempty = false;
o = s.taboption('ftp', form.Value, 'find_pasv_port_attempts', _('Max retries on port conflict during passive transfer'));
o.datatype = 'uinteger';
o.default = '50';
o.rmempty = false;
o = s.taboption('ftp', form.Flag, 'active_transfer_port_non_20', _('Enable non-20 port for active transfer'));
o.rmempty = false;
o = s.taboption('ftp', form.Value, 'idle_timeout', _('Client idle timeout (seconds)'));
o.datatype = 'uinteger';
o.default = '900';
o.rmempty = false;
o = s.taboption('ftp', form.Value, 'connection_timeout', _('Connection timeout (seconds)'));
o.datatype = 'uinteger';
o.default = '900';
o.rmempty = false;
o = s.taboption('ftp', form.Flag, 'disable_active_mode', _('Disable active transfer mode'));
o.rmempty = false;
o = s.taboption('ftp', form.Flag, 'default_transfer_binary', _('Enable binary transfer mode'));
o.rmempty = false;
o = s.taboption('ftp', form.Flag, 'enable_active_conn_ip_check', _('Client IP check in active transfer mode'));
o.rmempty = false;
o = s.taboption('ftp', form.Flag, 'enable_pasv_conn_ip_check', _('Client IP check in passive transfer mode'));
o.rmempty = false;
// sftp
o = s.taboption('sftp', form.Flag, 'sftp', _('Enabled SFTP'));
o.rmempty = false;
o = s.taboption('sftp', form.Value, 'sftp_port', _('SFTP Port'));
o.datatype = 'and(port,min(1))';
o.default = 5222;
o.rmempty = false;
return m.render();
}
});

View File

@ -0,0 +1,77 @@
'use strict';
'require dom';
'require fs';
'require poll';
'require uci';
'require view';
var scrollPosition = 0;
var userScrolled = false;
var logTextarea;
var log_path;
uci.load('openlist').then(function() {
log_path = uci.get('openlist', '@openlist[0]', 'log_path') || '/var/log/openlist.log';
});
function pollLog() {
return Promise.all([
fs.read_direct(log_path, 'text').then(function (res) {
return res.trim().split(/\n/).join('\n').replace(/\u001b\[33mWARN\u001b\[0m/g, '').replace(/\u001b\[36mINFO\u001b\[0m/g, '').replace(/\u001b\[31mERRO\u001b\[0m/g, '');
}),
]).then(function (data) {
logTextarea.value = data[0] || _('No log data.');
if (!userScrolled) {
logTextarea.scrollTop = logTextarea.scrollHeight;
} else {
logTextarea.scrollTop = scrollPosition;
}
});
};
return view.extend({
handleCleanLogs: function () {
return fs.write(log_path, '')
.catch(function (e) { ui.addNotification(null, E('p', e.message)) });
},
render: function () {
logTextarea = E('textarea', {
'class': 'cbi-input-textarea',
'wrap': 'off',
'readonly': 'readonly',
'style': 'width: calc(100% - 20px);height: 535px;margin: 10px;overflow-y: scroll;',
});
logTextarea.addEventListener('scroll', function () {
userScrolled = true;
scrollPosition = logTextarea.scrollTop;
});
var log_textarea_wrapper = E('div', { 'id': 'log_textarea' }, logTextarea);
setTimeout(function () {
poll.add(pollLog);
}, 100);
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_wrapper,
E('div', { 'style': 'text-align:right' },
E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
)
])
])
]);
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});

View File

@ -0,0 +1,267 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "OpenList"
msgstr "OpenList"
msgid "Open Web Interface"
msgstr "打开 Web 界面"
msgid "A file list program that supports multiple storage."
msgstr "一款支持多种存储的目录文件列表程序。"
msgid "Setting"
msgstr "设置"
msgid "Basic Setting"
msgstr "基本设置"
msgid "Global Settings"
msgstr "全局设置"
msgid "Logs"
msgstr "日志"
msgid "Enabled"
msgstr "启用"
msgid "Port"
msgstr "端口"
msgid "Web Protocol"
msgstr "Web 协议"
msgid "Enable SSL"
msgstr "启用 SSL"
msgid "Force HTTPS"
msgstr "强制 HTTPS"
msgid "SSL cert"
msgstr "SSL 证书"
msgid "SSL certificate file path"
msgstr "SSL 证书文件路径"
msgid "SSL key"
msgstr "SSL 密钥"
msgid "SSL key file path"
msgstr "SSL 密钥文件路径"
msgid "Data directory"
msgstr "数据目录"
msgid "Cache directory"
msgstr "缓存目录"
msgid "RUNNING"
msgstr "运行中"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "Collecting data..."
msgstr "收集数据..."
msgid "NAS"
msgstr "网络存储"
msgid "User Manual"
msgstr "用户手册"
msgid "Open firewall port"
msgstr "打开防火墙端口"
msgid "Enable Logs"
msgstr "启用日志"
msgid "Log path"
msgstr "日志文件路径"
msgid "Max Size (MB)"
msgstr "日志文件大小MB"
msgid "Max backups"
msgstr "日志备份数量"
msgid "Max age"
msgstr "日志保存天数"
msgid "Log Compress"
msgstr "日志压缩"
msgid "Clear logs"
msgstr "清空日志"
msgid "Reset Password"
msgstr "重置密码"
msgid "Generate a new random password."
msgstr "随机生成一个新密码。"
msgid "Username:"
msgstr "用户名:"
msgid "New Password:"
msgstr "新密码:"
msgid "New password has been copied to clipboard."
msgstr "新密码已复制到剪贴板。"
msgid "Force read config"
msgstr "强制读取配置"
msgid "Setting this to true will force the program to read the configuration file, ignoring environment variables."
msgstr "将此设置为 true 可以强制程序读取配置文件,而忽略环境变量"
msgid "Login Validity Period (hours)"
msgstr "登录有效期(小时)"
msgid "Max Connections"
msgstr "最大并发连接数"
msgid "0 is unlimited, It is recommend to set a low number of concurrency (10-20) for poor performance device"
msgstr "默认 0 不限制低性能设备建议设置较低的并发数10-20"
msgid "Max concurrency of local proxies"
msgstr "本地代理最大并发数"
msgid "0 is unlimited, Limit the maximum concurrency of local agents. The default value is 64"
msgstr "限制本地代理最大并发数,值为 0 表示不限制,默认值 64"
msgid "Site URL"
msgstr "站点地址"
msgid "CDN URL"
msgstr "CDN 地址"
msgid "JWT Key"
msgstr "JWT 密钥"
msgid "Disable TLS Verify"
msgstr "禁用 TLS 验证"
msgid "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"
msgstr "Web 被反向代理到二级目录时,必须填写该选项以确保 Web 正常工作。URL 结尾请勿携带 '/'"
msgid "Delayed Start (seconds)"
msgstr "开机延时启动(秒)"
msgid "Database"
msgstr "数据库"
msgid "Database Type"
msgstr "数据库类型"
msgid "Database Host"
msgstr "主机"
msgid "Database Port"
msgstr "端口"
msgid "Database Username"
msgstr "用户名"
msgid "Database Password"
msgstr "密码"
msgid "Database Name"
msgstr "数据库名"
msgid "Database Table Prefix"
msgstr "数据库表前缀"
msgid "Database SSL Mode"
msgstr "SSL模式"
msgid "Database DSN"
msgstr "DSN"
msgid "Task threads"
msgstr "任务线程"
msgid "Download Workers"
msgstr "下载任务线程数"
msgid "Download Max Retry"
msgstr "下载任务重试次数"
msgid "Transfer Workers"
msgstr "中转任务线程数"
msgid "Transfer Max Retry"
msgstr "中转任务下载重试次数"
msgid "Upload Workers"
msgstr "上传任务线程数"
msgid "Upload Max Retry"
msgstr "上传任务重试次数"
msgid "Copy Workers"
msgstr "复制任务线程数"
msgid "Copy Max Retry"
msgstr "复制任务重试次数"
msgid "CORS Settings"
msgstr "跨域设置"
msgid "Allow Origins"
msgstr "允许来源Origins"
msgid "Allow Methods"
msgstr "允许方法Methods"
msgid "Allow Headers"
msgstr "允许标头Headers"
msgid "Object Storage"
msgstr "对象存储"
msgid "Enabled S3"
msgstr "启用 S3"
msgid "Enabled FTP"
msgstr "启用 FTP"
msgid "FTP Port"
msgstr "FTP 端口"
msgid "Max retries on port conflict during passive transfer"
msgstr "被动传输端口冲突时最大重试次数"
msgid "Enable non-20 port for active transfer"
msgstr "启用非 20 端口进行主动传输"
msgid "Client idle timeout (seconds)"
msgstr "客户端空闲超时(秒)"
msgid "Connection timeout (seconds)"
msgstr "连接超时(秒)"
msgid "Disable active transfer mode"
msgstr "禁用主动传输模式"
msgid "Enable binary transfer mode"
msgstr "启用二进制传输模式"
msgid "Client IP check in active transfer mode"
msgstr "主动传输模式下对客户端进行 IP 检查"
msgid "Client IP check in passive transfer mode"
msgstr "被动传输模式下对客户端进行 IP 检查"
msgid "Enabled SFTP"
msgstr "启用 SFTP"
msgid "SFTP Port"
msgstr "SFTP 端口"

View File

@ -0,0 +1,13 @@
#!/bin/sh
[ -f "/etc/config/ucitrack" ] && {
uci -q batch <<-EOF >/dev/null
delete ucitrack.@openlist[-1]
add ucitrack openlist
set ucitrack.@openlist[-1].init=openlist
commit ucitrack
EOF
}
rm -rf /tmp/luci-*
exit 0

View File

@ -0,0 +1,28 @@
{
"admin/services/openlist": {
"title": "OpenList",
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-openlist" ],
"uci": { "openlist": true }
}
},
"admin/services/openlist/basic": {
"title": "Setting",
"order": 10,
"action": {
"type": "view",
"path": "openlist/basic"
}
},
"admin/services/openlist/logs": {
"title": "Logs",
"order": 20,
"action": {
"type": "view",
"path": "openlist/logs"
}
}
}

View File

@ -0,0 +1,21 @@
{
"luci-app-openlist": {
"description": "Grant UCI access for luci-app-openlist",
"read": {
"file": {
"/usr/bin/openlist": [ "exec" ],
"/*": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "openlist" ]
},
"write": {
"file": {
"/*": [ "write" ]
},
"uci": [ "openlist" ]
}
}
}

View File

@ -0,0 +1,4 @@
{
"config": "openlist",
"init": "openlist"
}