From 9391fcb990abc1051f89cfe4c1f035360d0f949b Mon Sep 17 00:00:00 2001 From: sbwml Date: Sun, 15 Jun 2025 02:51:13 +0800 Subject: [PATCH] luci-app-openlist: new package Signed-off-by: sbwml --- luci-app-openlist/Makefile | 17 + .../resources/view/openlist/basic.js | 384 ++++++++++++++++++ .../resources/view/openlist/logs.js | 77 ++++ luci-app-openlist/po/zh_Hans/openlist.po | 267 ++++++++++++ .../root/etc/uci-defaults/luci-app-openlist | 13 + .../share/luci/menu.d/luci-app-openlist.json | 28 ++ .../share/rpcd/acl.d/luci-app-openlist.json | 21 + .../usr/share/ucitrack/luci-app-openlist.json | 4 + 8 files changed, 811 insertions(+) create mode 100644 luci-app-openlist/Makefile create mode 100644 luci-app-openlist/htdocs/luci-static/resources/view/openlist/basic.js create mode 100644 luci-app-openlist/htdocs/luci-static/resources/view/openlist/logs.js create mode 100644 luci-app-openlist/po/zh_Hans/openlist.po create mode 100755 luci-app-openlist/root/etc/uci-defaults/luci-app-openlist create mode 100644 luci-app-openlist/root/usr/share/luci/menu.d/luci-app-openlist.json create mode 100644 luci-app-openlist/root/usr/share/rpcd/acl.d/luci-app-openlist.json create mode 100644 luci-app-openlist/root/usr/share/ucitrack/luci-app-openlist.json diff --git a/luci-app-openlist/Makefile b/luci-app-openlist/Makefile new file mode 100644 index 0000000..181bccc --- /dev/null +++ b/luci-app-openlist/Makefile @@ -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 diff --git a/luci-app-openlist/htdocs/luci-static/resources/view/openlist/basic.js b/luci-app-openlist/htdocs/luci-static/resources/view/openlist/basic.js new file mode 100644 index 0000000..401f3c3 --- /dev/null +++ b/luci-app-openlist/htdocs/luci-static/resources/view/openlist/basic.js @@ -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 = '%s %s'; + var renderHTML; + if (isRunning) { + var button = String.format('', + _('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(); + } +}); diff --git a/luci-app-openlist/htdocs/luci-static/resources/view/openlist/logs.js b/luci-app-openlist/htdocs/luci-static/resources/view/openlist/logs.js new file mode 100644 index 0000000..3895258 --- /dev/null +++ b/luci-app-openlist/htdocs/luci-static/resources/view/openlist/logs.js @@ -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 +}); diff --git a/luci-app-openlist/po/zh_Hans/openlist.po b/luci-app-openlist/po/zh_Hans/openlist.po new file mode 100644 index 0000000..e03487d --- /dev/null +++ b/luci-app-openlist/po/zh_Hans/openlist.po @@ -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 端口" diff --git a/luci-app-openlist/root/etc/uci-defaults/luci-app-openlist b/luci-app-openlist/root/etc/uci-defaults/luci-app-openlist new file mode 100755 index 0000000..73bd43f --- /dev/null +++ b/luci-app-openlist/root/etc/uci-defaults/luci-app-openlist @@ -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 diff --git a/luci-app-openlist/root/usr/share/luci/menu.d/luci-app-openlist.json b/luci-app-openlist/root/usr/share/luci/menu.d/luci-app-openlist.json new file mode 100644 index 0000000..63c3ef5 --- /dev/null +++ b/luci-app-openlist/root/usr/share/luci/menu.d/luci-app-openlist.json @@ -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" + } + } +} diff --git a/luci-app-openlist/root/usr/share/rpcd/acl.d/luci-app-openlist.json b/luci-app-openlist/root/usr/share/rpcd/acl.d/luci-app-openlist.json new file mode 100644 index 0000000..f024065 --- /dev/null +++ b/luci-app-openlist/root/usr/share/rpcd/acl.d/luci-app-openlist.json @@ -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" ] + } + } +} diff --git a/luci-app-openlist/root/usr/share/ucitrack/luci-app-openlist.json b/luci-app-openlist/root/usr/share/ucitrack/luci-app-openlist.json new file mode 100644 index 0000000..1dda9de --- /dev/null +++ b/luci-app-openlist/root/usr/share/ucitrack/luci-app-openlist.json @@ -0,0 +1,4 @@ +{ + "config": "openlist", + "init": "openlist" +}