From b8b5ba4367403893eac0cf248e7b0f4c9bdd51a5 Mon Sep 17 00:00:00 2001 From: sbwml Date: Fri, 6 Sep 2024 14:54:06 +0800 Subject: [PATCH] alist/luci-app-alist: add full configuration settings Signed-off-by: sbwml --- alist/files/alist.config | 39 ++- alist/files/alist.init | 118 +++++--- .../luci-static/resources/view/alist/basic.js | 258 ++++++++++++++---- luci-app-alist/po/zh_Hans/alist.po | 99 ++++++- .../usr/share/luci/menu.d/luci-app-alist.json | 72 ++--- 5 files changed, 439 insertions(+), 147 deletions(-) diff --git a/alist/files/alist.config b/alist/files/alist.config index 75aa0b3..2ec1ed9 100644 --- a/alist/files/alist.config +++ b/alist/files/alist.config @@ -1,9 +1,32 @@ + config alist - option 'enabled' '0' - option 'port' '5244' - option 'temp_dir' '/tmp/alist' - option 'ssl' '0' - option 'token_expires_in' '48' - option 'max_connections' '0' - option 'site_url' '' - option 'delayed_start' '0' + option enabled '0' + option port '5244' + option delayed_start '0' + option allow_wan '0' + option force '1' + option token_expires_in '48' + option max_connections '0' + option tls_insecure_skip_verify '1' + option data_dir '/etc/alist' + option temp_dir '/tmp/alist' + option log '1' + option log_max_size '10' + option log_max_backups '5' + option log_max_age '28' + option log_compress '0' + option database_type 'sqlite3' + option ssl '0' + option download_workers '5' + option download_max_retry '1' + option transfer_workers '5' + option transfer_max_retry '2' + option upload_workers '5' + option upload_max_retry '0' + option copy_workers '5' + option copy_max_retry '2' + option cors_allow_origins '*' + option cors_allow_methods '*' + option cors_allow_headers '*' + option s3 '0' + diff --git a/alist/files/alist.init b/alist/files/alist.init index 157aa47..3553102 100755 --- a/alist/files/alist.init +++ b/alist/files/alist.init @@ -10,21 +10,34 @@ LOG_FILE=/var/log/alist.log get_config() { config_get_bool enabled $1 enabled 1 config_get port $1 port 5244 - config_get log $1 log 1 - config_get site_url $1 site_url "" - config_get data_dir $1 data_dir "/etc/alist" - config_get temp_dir $1 temp_dir "/tmp/alist" - config_get ssl $1 ssl 0 - config_get ssl_cert $1 ssl_cert "" - config_get ssl_key $1 ssl_key "" - config_get token_expires_in $1 token_expires_in 48 config_get allow_wan $1 allow_wan 0 - config_get max_connections $1 max_connections 0 config_get delayed_start $1 delayed_start 0 - # mysql - config_get mysql $1 mysql 0 - config_get mysql_type $1 mysql_type "mysql" + config_get force $1 force 1 + config_get site_url $1 site_url "" + config_get cdn $1 cdn "" + config_get jwt_secret $1 jwt_secret "" + config_get data_dir $1 data_dir "/etc/alist" + config_get temp_dir $1 temp_dir "/tmp/alist" + config_get token_expires_in $1 token_expires_in 48 + config_get max_connections $1 max_connections 0 + config_get tls_insecure_skip_verify $1 tls_insecure_skip_verify 1 + + # log + config_get log $1 log 1 + config_get log_max_size $1 log_max_size 10 + config_get log_max_backups $1 log_max_backups 5 + config_get log_max_age $1 log_max_age 28 + config_get log_compress $1 log_compress 0 + + # scheme + config_get ssl $1 ssl 0 + config_get force_https $1 force_https 0 + config_get ssl_cert $1 ssl_cert "" + config_get ssl_key $1 ssl_key "" + + # database + config_get database_type $1 database_type "sqlite3" config_get mysql_host $1 mysql_host "" config_get mysql_port $1 mysql_port "3306" config_get mysql_username $1 mysql_username "" @@ -34,6 +47,26 @@ get_config() { config_get mysql_ssl_mode $1 mysql_ssl_mode "" config_get mysql_dsn $1 mysql_dsn "" + # tasks + config_get download_workers $1 download_workers 5 + config_get download_max_retry $1 download_max_retry 1 + config_get transfer_workers $1 transfer_workers 5 + config_get transfer_max_retry $1 transfer_max_retry 2 + config_get upload_workers $1 upload_workers 5 + config_get upload_max_retry $1 upload_max_retry 0 + config_get copy_workers $1 copy_workers 5 + config_get copy_max_retry $1 copy_max_retry 2 + + # cors + config_get cors_allow_origins $1 cors_allow_origins '*' + config_get cors_allow_methods $1 cors_allow_methods '*' + config_get cors_allow_headers $1 cors_allow_headers '*' + + # s3 + config_get s3 $1 s3 0 + config_get s3_port $1 s3_port 5246 + config_get s3_ssl $1 s3_ssl 0 + config_load network config_get lan_addr lan ipaddr "0.0.0.0" if echo "${lan_addr}" | grep -Fq ' '; then @@ -41,6 +74,11 @@ get_config() { else lan_addr=${lan_addr%%/*} fi + + # init jwt_secret + [ -z "$jwt_secret" ] && jwt_secret=$(tr -cd "a-zA-Z0-9" < "/dev/urandom" | head -c16) + uci -q set alist.@alist[0].jwt_secret="$jwt_secret" + uci commit alist } set_firewall() { @@ -81,23 +119,20 @@ start_service() { external_access="deny" fi - # mysql - [ "$mysql" -eq 1 ] && database=$mysql_type || database=sqlite3 - set_firewall true > $LOG_FILE # init config json_init - json_add_boolean "force" "1" + json_add_boolean "force" "$force" json_add_string "site_url" "$site_url" - json_add_string "cdn" "" - json_add_string "jwt_secret" "" + json_add_string "cdn" "$cdn" + json_add_string "jwt_secret" "$jwt_secret" json_add_int "token_expires_in" "$token_expires_in" # database json_add_object 'database' - json_add_string "type" "$database" + json_add_string "type" "$database_type" json_add_string "host" "$mysql_host" json_add_int "port" "$mysql_port" json_add_string "user" "$mysql_username" @@ -121,7 +156,7 @@ start_service() { json_add_string "address" "$listen_addr" json_add_int "http_port" "$http_port" json_add_int "https_port" "$https_port" - json_add_boolean "force_https" "0" + json_add_boolean "force_https" "$force_https" json_add_string "cert_file" "$ssl_cert" json_add_string "key_file" "$ssl_key" json_add_string "unix_file" "" @@ -136,61 +171,62 @@ start_service() { json_add_object "log" json_add_boolean "enable" "$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" - json_add_boolean "compress" "0" + json_add_int "max_size" "$log_max_size" + json_add_int "max_backups" "$log_max_backups" + json_add_int "max_age" "$log_max_age" + json_add_boolean "compress" "$log_compress" json_close_object json_add_int "delayed_start" "$delayed_start" json_add_int "max_connections" "$max_connections" - json_add_boolean "tls_insecure_skip_verify" "1" + json_add_boolean "tls_insecure_skip_verify" "$tls_insecure_skip_verify" # tasks json_add_object "tasks" json_add_object "download" - json_add_int "workers" "5" - json_add_int "max_retry" "1" + json_add_int "workers" "$download_workers" + json_add_int "max_retry" "$download_max_retry" json_close_object json_add_object "transfer" - json_add_int "workers" "5" - json_add_int "max_retry" "2" + json_add_int "workers" "$transfer_workers" + json_add_int "max_retry" "$transfer_max_retry" json_close_object json_add_object "upload" - json_add_int "workers" "5" - json_add_int "max_retry" "0" + json_add_int "workers" "$upload_workers" + json_add_int "max_retry" "$upload_max_retry" json_close_object json_add_object "copy" - json_add_int "workers" "5" - json_add_int "max_retry" "2" + json_add_int "workers" "$copy_workers" + json_add_int "max_retry" "$copy_max_retry" json_close_object json_close_object # cors json_add_object "cors" json_add_array "allow_origins" - json_add_string "" "*" + json_add_string "" "$cors_allow_origins" json_close_array json_add_array "allow_methods" - json_add_string "" "*" + json_add_string "" "$cors_allow_methods" json_close_array json_add_array "allow_headers" - json_add_string "" "*" + json_add_string "" "$cors_allow_headers" json_close_array json_close_object # s3 json_add_object "s3" - json_add_boolean "enable" "0" - json_add_int "port" "5246" - json_add_boolean "ssl" "0" + json_add_boolean "enable" "$s3" + json_add_int "port" "$s3_port" + json_add_boolean "ssl" "$s3_ssl" json_close_object json_dump > $data_dir/config.json procd_open_instance alist procd_set_param command $PROG - procd_append_param command server --data $data_dir + procd_append_param command server + procd_append_param command --data $data_dir procd_set_param stdout 0 procd_set_param stderr 0 procd_set_param respawn @@ -201,7 +237,7 @@ start_service() { reload_service() { stop - sleep 3 + sleep 2 start } 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 index bb40c41..ea98a25 100644 --- a/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js +++ b/luci-app-alist/htdocs/luci-static/resources/view/alist/basic.js @@ -95,7 +95,9 @@ return view.extend({ ]); } - s = m.section(form.NamedSection, '@alist[0]', 'alist'); + // init + s = m.section(form.TypedSection, 'alist', _('Basic Settings')); + s.anonymous = true; o = s.option(form.Flag, 'enabled', _('Enabled')); o.default = o.disabled; @@ -106,13 +108,147 @@ return view.extend({ o.default = '5244'; 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.Flag, 'allow_wan', _('Allow Access From Internet')); + 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); + + // global + s = m.section(form.TypedSection, 'alist', _('Global Settings')); + s.anonymous = true; + + o = s.option(form.Flag, 'force', _('Force read config'), + _('By default AList reads the configuration from environment variables, set this field to true to force AList to read config from the configuration file.')); + o.default = true; + 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, 'cdn', _('CDN URL')); + o.default = ''; + + o = s.option(form.Value, 'jwt_secret', _('JWT Key')); + o.default = ''; + + 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, '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.Flag, 'tls_insecure_skip_verify', _('Disable TLS Verify')); + o.default = true; + 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; + + // Logs + s = m.section(form.TypedSection, 'alist', _('Logs')); + s.anonymous = true; + o = s.option(form.Flag, 'log', _('Enable Logs')); o.default = 1; o.rmempty = false; + o = s.option(form.Value, 'log_max_size', _('Max Size')); + o.datatype = 'uinteger'; + o.default = '10'; + o.rmempty = false; + o.depends('log', '1'); + + o = s.option(form.Value, 'log_max_backups', _('Max backups')); + o.datatype = 'uinteger'; + o.default = '5'; + o.rmempty = false; + o.depends('log', '1'); + + o = s.option(form.Value, 'log_max_age', _('Max age')); + o.datatype = 'uinteger'; + o.default = '28'; + o.rmempty = false; + o.depends('log', '1'); + + o = s.option(form.Flag, 'log_compress', _('Log Compress')); + o.default = 'false'; + o.rmempty = false; + o.depends('log', '1'); + + // database + s = m.section(form.TypedSection, 'alist', _('Database')); + s.anonymous = true; + + o = s.option(form.ListValue, 'database_type', _('Database Type')); + o.default = 'sqlite3'; + o.value('sqlite3', _('SQLite')); + o.value('mysql', _('MySQL')); + o.value('postgres', _('PostgreSQL')); + + o = s.option(form.Value, 'mysql_host', _('Database Host')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_port', _('Database Port')); + o.datatype = 'port'; + o.default = '3306'; + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_username', _('Database Username')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_password', _('Database Password')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_database', _('Database Name')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_table_prefix', _('Database Table Prefix')); + o.default = 'x_'; + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_ssl_mode', _('Database SSL Mode')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + o = s.option(form.Value, 'mysql_dsn', _('Database DSN')); + o.depends('database_type','mysql'); + o.depends('database_type','postgres'); + + // scheme + s = m.section(form.TypedSection, 'alist', _('Web Protocol')); + s.anonymous = true; + o = s.option(form.Flag, 'ssl', _('Enable SSL')); o.rmempty = false; + o = s.option(form.Flag, 'force_https', _('Force HTTPS')); + o.rmempty = false; + o.depends('ssl', '1'); + o = s.option(form.Value, 'ssl_cert', _('SSL cert'), _('SSL certificate file path')); o.rmempty = false; @@ -123,76 +259,82 @@ return view.extend({ o.rmempty = false; o.depends('ssl', '1'); - o = s.option(form.Flag, 'mysql', _('Enable Database')); - o.rmempty = false; + // tasks + s = m.section(form.TypedSection, 'alist', _('Task threads')); + s.anonymous = true; - 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 = s.option(form.Value, 'download_workers', _('Download Workers')); o.datatype = 'uinteger'; + o.default = '5'; o.rmempty = false; - o = s.option(form.Value, 'token_expires_in', _('Login Validity Period (hours)')); + o = s.option(form.Value, 'download_max_retry', _('Download Max Retry')); o.datatype = 'uinteger'; - o.default = '48'; + o.default = '1'; o.rmempty = false; - o = s.option(form.Value, 'delayed_start', _('Delayed Start (seconds)')); + o = s.option(form.Value, 'transfer_workers', _('Transfer Workers')); + o.datatype = 'uinteger'; + o.default = '5'; + o.rmempty = false; + + o = s.option(form.Value, 'transfer_max_retry', _('Transfer Max Retry')); + o.datatype = 'uinteger'; + o.default = '2'; + o.rmempty = false; + + o = s.option(form.Value, 'upload_workers', _('Upload Workers')); + o.datatype = 'uinteger'; + o.default = '5'; + o.rmempty = false; + + o = s.option(form.Value, 'upload_max_retry', _('Upload Max Retry')); 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 = s.option(form.Value, 'copy_workers', _('Copy Workers')); + o.datatype = 'uinteger'; + o.default = '5'; 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); + o = s.option(form.Value, 'copy_max_retry', _('Copy Max Retry')); + o.datatype = 'uinteger'; + o.default = '2'; + o.rmempty = false; + + // cors + s = m.section(form.TypedSection, 'alist', _('CORS Settings')); + s.anonymous = true; + + o = s.option(form.Value, 'cors_allow_origins', _('Allow Origins')); + o.default = '*'; + o.rmempty = false; + + o = s.option(form.Value, 'cors_allow_methods', _('Allow Methods')); + o.default = '*'; + o.rmempty = false; + + o = s.option(form.Value, 'cors_allow_headers', _('Allow Headers')); + o.default = '*'; + o.rmempty = false; + + // s3 + s = m.section(form.TypedSection, 'alist', _('Object Storage')); + s.anonymous = true; + + o = s.option(form.Flag, 's3', _('Enabled S3')); + o.rmempty = false; + + o = s.option(form.Value, 's3_port', _('Port')); + o.datatype = 'and(port,min(1))'; + o.default = 5246; + o.rmempty = false; + o.depends('s3', '1'); + + o = s.option(form.Flag, 's3_ssl', _('Enable SSL')); + o.rmempty = false; + o.depends('s3', '1'); return m.render(); } diff --git a/luci-app-alist/po/zh_Hans/alist.po b/luci-app-alist/po/zh_Hans/alist.po index c01cf1b..ef9fa3b 100644 --- a/luci-app-alist/po/zh_Hans/alist.po +++ b/luci-app-alist/po/zh_Hans/alist.po @@ -1,3 +1,13 @@ +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 "Alist" msgstr "Alist 文件列表" @@ -7,6 +17,9 @@ msgstr "打开 Web 界面" msgid "A file list program that supports multiple storage." msgstr "一款支持多种存储的目录文件列表程序。" +msgid "Setting" +msgstr "设置" + msgid "Basic Setting" msgstr "基本设置" @@ -19,9 +32,15 @@ msgstr "启用" msgid "Port" msgstr "端口" +msgid "Web Protocol" +msgstr "Web 协议" + msgid "Enable SSL" msgstr "启用 SSL" +msgid "Force HTTPS" +msgstr "强制 HTTPS" + msgid "SSL cert" msgstr "SSL 证书" @@ -61,6 +80,18 @@ msgstr "允许从外网访问" msgid "Enable Logs" msgstr "启用日志" +msgid "Max Size" +msgstr "日志文大小" + +msgid "Max backups" +msgstr "日志备份数量" + +msgid "Max age" +msgstr "日志保存天数" + +msgid "Log Compress" +msgstr "日志压缩" + msgid "Clear logs" msgstr "清空日志" @@ -79,6 +110,12 @@ 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 "登录有效期(小时)" @@ -89,7 +126,16 @@ msgid "0 is unlimited, It is recommend to set a low number of concurrency (10-20 msgstr "默认0不限制,低性能设备建议设置较低的并发数(10-20)" msgid "Site URL" -msgstr "站点 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 结尾请勿携带 '/'" @@ -97,11 +143,11 @@ msgstr "Web 被反向代理到二级目录时,必须填写该选项以确保 W msgid "Delayed Start (seconds)" msgstr "开机延时启动(秒)" -msgid "Enable Database" -msgstr "启用远程数据库" +msgid "Database" +msgstr "数据库" msgid "Database Type" -msgstr "类型" +msgstr "数据库类型" msgid "Database Host" msgstr "主机" @@ -127,3 +173,48 @@ 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" + 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 index 295910a..dd5238d 100644 --- 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 @@ -1,36 +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" - } - } -} +{ + "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": "Setting", + "order": 30, + "action": { + "type": "view", + "path": "alist/basic" + } + }, + "admin/nas/alist/logs": { + "title": "Logs", + "order": 40, + "action": { + "type": "view", + "path": "alist/logs" + } + } +}