secubox-openwrt/docs-zh/luci-development-reference.md
CyberMind-FR ccfb58124c docs: Add trilingual documentation (French and Chinese translations)
Add complete French (fr) and Chinese (zh) translations for all documentation:

- Root files: README, CHANGELOG, SECURITY, BETA-RELEASE
- docs/: All 16 core documentation files
- DOCS/: All 19 deep-dive documents including embedded/ and archive/
- package/secubox/: All 123+ package READMEs
- Misc: secubox-tools/, scripts/, EXAMPLES/, config-backups/, streamlit-apps/

Total: 346 translation files created

Each file includes language switcher links for easy navigation between
English, French, and Chinese versions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-20 10:00:18 +01:00

29 KiB
Raw Blame History

LuCI 开发参考指南

语言: English | Francais | 中文

版本: 1.0.0 最后更新: 2025-12-28 状态: 活跃 基于: luci-app-secubox 和 luci-app-system-hub 实现 目标受众: Claude.ai 和从事 OpenWrt LuCI 应用开发的开发人员


另请参阅

本文档记录了在开发 SecuBox LuCI 应用程序过程中发现的关键模式、最佳实践和常见陷阱。请将此作为所有未来 LuCI 应用开发的验证参考。


目录

  1. ubus 和 RPC 基础
  2. RPCD 后端模式
  3. LuCI API 模块模式
  4. LuCI 视图导入模式
  5. ACL 权限结构
  6. 数据结构约定
  7. 常见错误和解决方案
  8. 验证清单
  9. 测试和部署

ubus 和 RPC 基础

什么是 ubus

ubusOpenWrt 微总线架构)是 OpenWrt 的进程间通信IPC系统。它支持

  • 进程间的 RPC远程过程调用
  • Web 界面LuCI与后端服务的通信
  • 通过 ubus call 进行命令行交互

ubus 对象命名约定

关键规则:所有 LuCI 应用程序的 ubus 对象必须使用 luci. 前缀。

// ✅ 正确
object: 'luci.system-hub'
object: 'luci.cdn-cache'
object: 'luci.wireguard-dashboard'

// ❌ 错误
object: 'system-hub'
object: 'systemhub'
object: 'cdn-cache'

为什么? LuCI 期望 Web 应用程序的对象在 luci.* 命名空间下。如果没有此前缀:

  • ACL 权限将不匹配
  • RPCD 将无法正确路由调用
  • 浏览器控制台显示:RPC call to system-hub/status failed with error -32000: Object not found

RPCD 脚本名称必须与 ubus 对象匹配

RPCD 脚本文件名必须与 ubus 对象名称完全匹配:

# 如果 JavaScript 声明:
# object: 'luci.system-hub'

# 那么 RPCD 脚本必须命名为:
/usr/libexec/rpcd/luci.system-hub

# 而不是:
/usr/libexec/rpcd/system-hub
/usr/libexec/rpcd/luci-system-hub

验证命令

# 检查 JavaScript 文件中的 ubus 对象名称
grep -r "object:" luci-app-*/htdocs --include="*.js"

# 验证 RPCD 脚本是否存在且名称匹配
ls luci-app-*/root/usr/libexec/rpcd/

ubus 调用类型

读操作(类似 GET

  • status - 获取当前状态
  • get_* - 检索数据(例如 get_healthget_settings
  • list_* - 枚举项目(例如 list_services

写操作(类似 POST

  • save_* - 持久化配置(例如 save_settings
  • *_action - 执行操作(例如 service_action
  • backuprestorereboot - 系统修改

ACL 映射

  • 读操作 → ACL 中的 "read" 部分
  • 写操作 → ACL 中的 "write" 部分

RPCD 后端模式

Shell 脚本结构

RPCD 后端是可执行的 shell 脚本,它们:

  1. 解析 $1 获取操作(listcall
  2. 解析 $2 获取方法名称(如果是 call
  3. 从 stdin 读取 JSON 输入(用于带参数的方法)
  4. 将 JSON 输出到 stdout
  5. 成功时退出状态为 0失败时为非零

标准模板

#!/bin/sh
# RPCD 后端luci.system-hub
# 版本0.1.0

# 加载 JSON shell 助手
. /usr/share/libubox/jshn.sh

case "$1" in
    list)
        # 列出所有可用方法及其参数
        echo '{
            "status": {},
            "get_health": {},
            "service_action": { "service": "string", "action": "string" },
            "save_settings": {
                "auto_refresh": 0,
                "health_check": 0,
                "refresh_interval": 0
            }
        }'
        ;;
    call)
        case "$2" in
            status)
                status
                ;;
            get_health)
                get_health
                ;;
            service_action)
                # 从 stdin 读取 JSON 输入
                read -r input
                json_load "$input"
                json_get_var service service
                json_get_var action action
                service_action "$service" "$action"
                ;;
            save_settings)
                read -r input
                json_load "$input"
                json_get_var auto_refresh auto_refresh
                json_get_var health_check health_check
                json_get_var refresh_interval refresh_interval
                save_settings "$auto_refresh" "$health_check" "$refresh_interval"
                ;;
            *)
                echo '{"error": "Method not found"}'
                exit 1
                ;;
        esac
        ;;
esac

使用 jshn.sh 输出 JSON

jshn.sh 提供了用于 JSON 操作的 shell 函数:

# 初始化 JSON 对象
json_init

# 添加简单值
json_add_string "hostname" "openwrt"
json_add_int "uptime" 86400
json_add_boolean "running" 1

# 添加嵌套对象
json_add_object "cpu"
json_add_int "usage" 25
json_add_string "status" "ok"
json_close_object

# 添加数组
json_add_array "services"
json_add_string "" "network"
json_add_string "" "firewall"
json_close_array

# 将 JSON 输出到 stdout
json_dump

常用函数

  • json_init - 开始新的 JSON 对象
  • json_add_string "key" "value" - 添加字符串
  • json_add_int "key" 123 - 添加整数
  • json_add_boolean "key" 1 - 添加布尔值0 或 1
  • json_add_object "key" - 开始嵌套对象
  • json_close_object - 结束嵌套对象
  • json_add_array "key" - 开始数组
  • json_close_array - 结束数组
  • json_dump - 将 JSON 输出到 stdout

错误处理

始终验证输入并返回有意义的错误:

service_action() {
    local service="$1"
    local action="$2"

    # 验证服务名称
    if [ -z "$service" ]; then
        json_init
        json_add_boolean "success" 0
        json_add_string "error" "Service name is required"
        json_dump
        return 1
    fi

    # 验证操作
    case "$action" in
        start|stop|restart|enable|disable)
            ;;
        *)
            json_init
            json_add_boolean "success" 0
            json_add_string "error" "Invalid action: $action"
            json_dump
            return 1
            ;;
    esac

    # 执行操作
    /etc/init.d/"$service" "$action" >/dev/null 2>&1

    if [ $? -eq 0 ]; then
        json_init
        json_add_boolean "success" 1
        json_add_string "message" "Service $service $action successful"
        json_dump
    else
        json_init
        json_add_boolean "success" 0
        json_add_string "error" "Service $service $action failed"
        json_dump
        return 1
    fi
}

UCI 集成

对于持久化配置,使用 UCI统一配置接口

save_settings() {
    local auto_refresh="$1"
    local health_check="$2"
    local refresh_interval="$3"

    # 创建/更新 UCI 配置
    uci set system-hub.general=general
    uci set system-hub.general.auto_refresh="$auto_refresh"
    uci set system-hub.general.health_check="$health_check"
    uci set system-hub.general.refresh_interval="$refresh_interval"
    uci commit system-hub

    json_init
    json_add_boolean "success" 1
    json_add_string "message" "Settings saved successfully"
    json_dump
}

get_settings() {
    # 加载 UCI 配置
    if [ -f "/etc/config/system-hub" ]; then
        . /lib/functions.sh
        config_load system-hub
    fi

    json_init
    json_add_object "general"

    # 获取值或使用默认值
    config_get auto_refresh general auto_refresh "1"
    json_add_boolean "auto_refresh" "${auto_refresh:-1}"

    config_get refresh_interval general refresh_interval "30"
    json_add_int "refresh_interval" "${refresh_interval:-30}"

    json_close_object
    json_dump
}

性能提示

  1. 缓存昂贵的操作:不要多次重复读取 /proc 文件
  2. 高效使用命令替换
    # 好
    uptime=$(cat /proc/uptime | cut -d' ' -f1)
    
    # 更好
    read uptime _ < /proc/uptime
    uptime=${uptime%.*}
    
  3. 尽可能避免外部命令
    # 慢
    count=$(ls /etc/init.d | wc -l)
    
    # 快
    count=0
    for file in /etc/init.d/*; do
        [ -f "$file" ] && count=$((count + 1))
    done
    

LuCI API 模块模式

关键:使用 baseclass.extend()

规则LuCI API 模块必须使用 baseclass.extend() 模式。

'use strict';
'require baseclass';
'require rpc';

// 声明 RPC 方法
var callStatus = rpc.declare({
    object: 'luci.system-hub',
    method: 'status',
    expect: {}
});

var callGetHealth = rpc.declare({
    object: 'luci.system-hub',
    method: 'get_health',
    expect: {}
});

var callSaveSettings = rpc.declare({
    object: 'luci.system-hub',
    method: 'save_settings',
    params: ['auto_refresh', 'health_check', 'refresh_interval'],
    expect: {}
});

// ✅ 正确:使用 baseclass.extend()
return baseclass.extend({
    getStatus: callStatus,
    getHealth: callGetHealth,
    saveSettings: callSaveSettings
});

// ❌ 错误:不要使用这些模式
return baseclass.singleton({...});  // 会破坏一切!
return {...};  // 普通对象不起作用

为什么使用 baseclass.extend()

  • LuCI 的模块系统期望基于类的模块
  • 视图使用 'require module/api as API' 导入,会自动实例化
  • baseclass.extend() 创建正确的类构造函数
  • baseclass.singleton() 会破坏实例化机制
  • 普通对象不支持 LuCI 的模块生命周期

rpc.declare() 参数

var callMethodName = rpc.declare({
    object: 'luci.module-name',     // ubus 对象名称(必须以 luci. 开头)
    method: 'method_name',          // RPCD 方法名称
    params: ['param1', 'param2'],   // 可选:参数名称(顺序很重要!)
    expect: {}                      // 期望的返回结构(或 { key: [] } 用于数组)
});

参数顺序很重要

// RPCD 期望参数按此确切顺序
var callSaveSettings = rpc.declare({
    object: 'luci.system-hub',
    method: 'save_settings',
    params: ['auto_refresh', 'health_check', 'debug_mode', 'refresh_interval'],
    expect: {}
});

// JavaScript 调用必须按相同顺序传递参数
API.saveSettings(1, 1, 0, 30);  // auto_refresh=1, health_check=1, debug_mode=0, refresh_interval=30

expect 参数模式

// 方法返回单个对象
expect: {}

// 方法在顶层返回数组
expect: { services: [] }

// 方法返回特定结构
expect: {
    services: [],
    count: 0
}

API 模块中的错误处理

API 方法返回 Promise。在视图中处理错误

return API.getHealth().then(function(data) {
    if (!data || typeof data !== 'object') {
        console.error('Invalid health data:', data);
        return null;
    }
    return data;
}).catch(function(err) {
    console.error('Failed to load health data:', err);
    ui.addNotification(null, E('p', {}, 'Failed to load health data'), 'error');
    return null;
});

LuCI 视图导入模式

关键:为 API 使用 'require ... as VAR'

规则:导入 API 模块时,在文件顶部使用 'require ... as VAR' 模式。

// ✅ 正确:自动实例化类
'require system-hub/api as API';

return L.view.extend({
    load: function() {
        return API.getHealth();  // API 已经实例化
    }
});

// ❌ 错误:返回类构造函数,而不是实例
var api = L.require('system-hub.api');
api.getHealth();  // 错误api.getHealth is not a function

为什么?

  • 'require module/path as VAR'(使用斜杠)自动实例化类
  • L.require('module.path')(使用点)返回原始类构造函数
  • API 模块扩展 baseclass,需要实例化
  • 使用 as VAR 模式时LuCI 的模块加载器处理实例化

标准视图结构

'use strict';
'require view';
'require form';
'require ui';
'require system-hub/api as API';

return L.view.extend({
    load: function() {
        // 加载渲染所需的数据
        return Promise.all([
            API.getHealth(),
            API.getStatus()
        ]);
    },

    render: function(data) {
        var health = data[0];
        var status = data[1];

        // 创建 UI 元素
        var container = E('div', { 'class': 'cbi-map' }, [
            E('h2', {}, 'Dashboard'),
            // ... 更多元素
        ]);

        return container;
    },

    handleSave: null,  // 禁用保存按钮
    handleSaveApply: null,  // 禁用保存并应用按钮
    handleReset: null  // 禁用重置按钮
});

导入模式总结

// 核心 LuCI 模块(始终使用引号)
'require view';
'require form';
'require ui';
'require rpc';
'require baseclass';

// 自定义 API 模块(使用 'as VAR' 进行自动实例化)
'require system-hub/api as API';
'require cdn-cache/api as CdnAPI';

// 访问全局 L 对象(无需 require
L.resolveDefault(...)
L.Poll.add(...)
L.ui.addNotification(...)

ACL 权限结构

文件位置

ACL 文件位于:

/usr/share/rpcd/acl.d/luci-app-<模块名称>.json

在源代码树中:

luci-app-<模块名称>/root/usr/share/rpcd/acl.d/luci-app-<模块名称>.json

标准 ACL 模板

{
    "luci-app-module-name": {
        "description": "Module Name - Description",
        "read": {
            "ubus": {
                "luci.module-name": [
                    "status",
                    "get_system_info",
                    "get_health",
                    "list_services",
                    "get_logs",
                    "get_storage",
                    "get_settings"
                ]
            }
        },
        "write": {
            "ubus": {
                "luci.module-name": [
                    "service_action",
                    "backup_config",
                    "restore_config",
                    "reboot",
                    "save_settings"
                ]
            }
        }
    }
}

读取与写入分类

读操作(不修改系统):

  • status - 获取当前状态
  • get_* - 检索数据(系统信息、健康状况、设置、日志、存储)
  • list_* - 枚举项目(服务、接口等)

写操作(修改系统状态):

  • *_action - 执行操作(启动/停止服务等)
  • save_* - 持久化配置更改
  • backuprestore - 系统备份/恢复
  • rebootshutdown - 系统控制

常见 ACL 错误

错误Access denied 或 RPC 错误 -32002

原因:方法未在 ACL 文件中列出,或列在错误的部分(读取与写入)

解决方案

  1. 确定方法是读操作还是写操作
  2. 将方法名称添加到 ACL 的正确部分
  3. 重启 RPCD/etc/init.d/rpcd restart

验证

# 检查 ACL 文件是否为有效 JSON
jsonlint /usr/share/rpcd/acl.d/luci-app-system-hub.json

# 列出所有 ubus 对象和方法
ubus list luci.system-hub

# 使用 ubus call 测试方法
ubus call luci.system-hub get_health

数据结构约定

健康指标结构system-hub v0.1.0

基于大量迭代,此结构提供清晰性和一致性:

{
    "cpu": {
        "usage": 25,
        "status": "ok",
        "load_1m": "0.25",
        "load_5m": "0.30",
        "load_15m": "0.28",
        "cores": 4
    },
    "memory": {
        "total_kb": 4096000,
        "free_kb": 2048000,
        "available_kb": 3072000,
        "used_kb": 1024000,
        "buffers_kb": 512000,
        "cached_kb": 1536000,
        "usage": 25,
        "status": "ok"
    },
    "disk": {
        "total_kb": 30408704,
        "used_kb": 5447680,
        "free_kb": 24961024,
        "usage": 19,
        "status": "ok"
    },
    "temperature": {
        "value": 45,
        "status": "ok"
    },
    "network": {
        "wan_up": true,
        "status": "ok"
    },
    "services": {
        "running": 35,
        "failed": 2
    },
    "score": 92,
    "timestamp": "2025-12-26 10:30:00",
    "recommendations": [
        "2 service(s) enabled but not running. Check service status."
    ]
}

关键原则

  1. 嵌套对象用于相关指标cpu、memory、disk 等)
  2. 一致的结构:每个指标都有 usage(百分比)和 statusok/warning/critical
  3. 原始值 + 计算值:同时提供(例如 used_kbusage 百分比)
  4. 状态阈值ok< warning、warningwarning-critical、critical>= critical
  5. 总体评分:仪表板的单一 0-100 健康评分
  6. 动态建议:基于阈值的可操作警报数组

状态值

对所有指标使用一致的状态字符串:

  • "ok" - 正常运行(绿色)
  • "warning" - 接近阈值(橙色)
  • "critical" - 超过阈值(红色)
  • "error" - 无法检索指标
  • "unknown" - 指标不可用

时间戳格式

使用 ISO 8601 或一致的本地格式:

timestamp="$(date '+%Y-%m-%d %H:%M:%S')"  # 2025-12-26 10:30:00

JSON 中的布尔值

在使用 jshn.sh 的 shell 脚本中:

json_add_boolean "wan_up" 1  # true
json_add_boolean "wan_up" 0  # false

在 JavaScript 中:

if (health.network.wan_up) {
    // WAN 已连接
}

数组与单值

对以下情况使用数组

  • 相同类型的多个项目(服务、接口、挂载点)
  • 可变长度数据

对以下情况使用单值

  • 系统范围的指标CPU、内存、磁盘
  • 主要/聚合值(总体温度、总运行时间)

示例 - 存储

// 多个挂载点 - 使用数组
"storage": [
    {
        "mount": "/",
        "total_kb": 30408704,
        "used_kb": 5447680,
        "usage": 19
    },
    {
        "mount": "/mnt/usb",
        "total_kb": 128000000,
        "used_kb": 64000000,
        "usage": 50
    }
]

// 仅根文件系统 - 使用对象
"disk": {
    "total_kb": 30408704,
    "used_kb": 5447680,
    "usage": 19,
    "status": "ok"
}

常见错误和解决方案

1. RPC 错误:"Object not found" (-32000)

错误消息

RPC call to system-hub/status failed with error -32000: Object not found

原因RPCD 脚本名称与 JavaScript 中的 ubus 对象名称不匹配

解决方案

  1. 检查 JavaScript 中的对象名称:

    grep -r "object:" luci-app-system-hub/htdocs --include="*.js"
    

    输出:object: 'luci.system-hub'

  2. 重命名 RPCD 脚本以完全匹配:

    mv root/usr/libexec/rpcd/system-hub root/usr/libexec/rpcd/luci.system-hub
    
  3. 确保脚本可执行:

    chmod +x root/usr/libexec/rpcd/luci.system-hub
    
  4. 重启 RPCD

    /etc/init.d/rpcd restart
    

2. JavaScript 错误:"api.methodName is not a function"

错误消息

Uncaught TypeError: api.getHealth is not a function
    at view.load (health.js:12)

原因:错误的导入模式 - 导入了类构造函数而不是实例

解决方案 从:

var api = L.require('system-hub.api');  // ❌ 错误

改为:

'require system-hub/api as API';  // ✅ 正确

为什么L.require('module.path') 返回原始类,'require module/path as VAR' 自动实例化。

3. RPC 错误:"Access denied" (-32002)

错误消息

RPC call to luci.system-hub/get_settings failed with error -32002: Access denied

原因:方法未在 ACL 文件中列出,或在错误的部分(读取与写入)

解决方案

  1. 打开 ACL 文件:root/usr/share/rpcd/acl.d/luci-app-system-hub.json

  2. 将方法添加到适当的部分:

    "read": {
        "ubus": {
            "luci.system-hub": [
                "get_settings"
            ]
        }
    }
    
  3. 部署并重启 RPCD

    scp luci-app-system-hub/root/usr/share/rpcd/acl.d/*.json router:/usr/share/rpcd/acl.d/
    ssh router "/etc/init.d/rpcd restart"
    

4. 显示错误:"NaN%" 或未定义值

错误:仪表板显示 "NaN%"、"undefined" 或空值

原因:前端使用了错误的数据结构键(后端更改后已过时)

解决方案

  1. 检查后端输出:

    ubus call luci.system-hub get_health
    
  2. 更新前端以匹配结构:

    // ❌ 旧结构
    var cpuPercent = health.load / health.cores * 100;
    var memPercent = health.memory.percent;
    
    // ✅ 新结构
    var cpuPercent = health.cpu ? health.cpu.usage : 0;
    var memPercent = health.memory ? health.memory.usage : 0;
    
  3. 添加 null/undefined 检查:

    var temp = health.temperature?.value || 0;
    var loadAvg = health.cpu?.load_1m || '0.00';
    

5. HTTP 404找不到视图文件

错误消息

HTTP error 404 while loading class file '/luci-static/resources/view/netifyd/overview.js'

原因:菜单路径与实际视图文件位置不匹配

解决方案

  1. 检查菜单 JSON

    cat root/usr/share/luci/menu.d/luci-app-netifyd-dashboard.json
    

    查找:"path": "netifyd/overview"

  2. 检查实际文件位置:

    ls htdocs/luci-static/resources/view/
    

    文件位于:view/netifyd-dashboard/overview.js

  3. 修复菜单路径或文件位置:

    // 选项 1更新菜单路径以匹配文件
    "path": "netifyd-dashboard/overview"
    
    // 选项 2移动文件以匹配菜单
    mv view/netifyd-dashboard/ view/netifyd/
    

6. 构建错误:"factory yields invalid constructor"

错误消息

/luci-static/resources/system-hub/api.js: factory yields invalid constructor

原因:在 API 模块中使用了错误的模式singleton、普通对象等

解决方案 始终使用 baseclass.extend()

return baseclass.extend({
    getStatus: callStatus,
    getHealth: callGetHealth,
    // ... 更多方法
});

不要使用:

  • baseclass.singleton({...})
  • 普通对象:return {...}
  • baseclass.prototype

7. 更改后 RPCD 无响应

症状:对 RPCD 脚本的更改不生效

解决方案

  1. 验证脚本已部署:

    ssh router "ls -la /usr/libexec/rpcd/"
    
  2. 检查脚本是否可执行:

    ssh router "chmod +x /usr/libexec/rpcd/luci.system-hub"
    
  3. 重启 RPCD

    ssh router "/etc/init.d/rpcd restart"
    
  4. 清除浏览器缓存Ctrl+Shift+R

  5. 检查 RPCD 日志:

    ssh router "logread | grep rpcd"
    

验证清单

在部署前使用此清单:

文件结构

  • RPCD 脚本存在:/usr/libexec/rpcd/luci.<模块名称>
  • RPCD 脚本可执行:chmod +x
  • 菜单 JSON 存在:/usr/share/luci/menu.d/luci-app-<模块>.json
  • ACL JSON 存在:/usr/share/rpcd/acl.d/luci-app-<模块>.json
  • API 模块存在:htdocs/luci-static/resources/<模块>/api.js
  • 视图存在:htdocs/luci-static/resources/view/<模块>/*.js

命名约定

  • RPCD 脚本名称与 JavaScript 中的 ubus 对象匹配(包括 luci. 前缀)
  • 菜单路径与视图文件目录结构匹配
  • 所有 ubus 对象以 luci. 开头
  • ACL 键与包名称匹配:"luci-app-<模块>"

代码验证

  • API 模块使用 baseclass.extend() 模式
  • 视图使用 'require <模块>/api as API' 模式导入 API
  • 所有 rpc.declare() 调用包含正确的 objectmethodparamsexpect
  • RPCD 脚本输出有效 JSON使用 ubus call 测试)
  • 菜单 JSON 有效(使用 jsonlint 测试)
  • ACL JSON 有效(使用 jsonlint 测试)

权限

  • 所有读方法在 ACL "read" 部分
  • 所有写方法在 ACL "write" 部分
  • ACL 中的方法与 RPCD 脚本方法名称完全匹配

测试

  • 运行验证脚本:./secubox-tools/validate-modules.sh
  • 通过 ubus 测试每个方法:ubus call luci.<模块> <方法>
  • 在浏览器中测试前端(检查控制台错误)
  • 部署后清除浏览器缓存
  • 验证 RPCD 重启:/etc/init.d/rpcd restart

自动化验证命令

# 运行全面验证
./secubox-tools/validate-modules.sh

# 验证特定模块
./secubox-tools/validate-module-generation.sh luci-app-system-hub

# 检查 JSON 语法
find luci-app-system-hub -name "*.json" -exec jsonlint {} \;

# 检查 shell 脚本
shellcheck luci-app-system-hub/root/usr/libexec/rpcd/*

测试和部署

使用 ubus 进行本地测试

在部署到路由器之前,本地测试 RPCD 脚本:

# 将 RPCD 脚本复制到本地 /tmp
cp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub /tmp/

# 设置可执行权限
chmod +x /tmp/luci.system-hub

# 测试 'list' 操作
/tmp/luci.system-hub list

# 使用方法测试 'call' 操作
/tmp/luci.system-hub call status

# 测试带参数的方法
echo '{"service":"network","action":"restart"}' | /tmp/luci.system-hub call service_action

部署脚本

使用部署脚本进行快速迭代:

#!/bin/bash
# deploy-system-hub.sh

ROUTER="root@192.168.8.191"

echo "正在将 system-hub 部署到 $ROUTER"

# 部署 API 模块
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js \
    "$ROUTER:/www/luci-static/resources/system-hub/"

# 部署视图
scp luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/*.js \
    "$ROUTER:/www/luci-static/resources/view/system-hub/"

# 部署 RPCD 后端
scp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub \
    "$ROUTER:/usr/libexec/rpcd/"

# 部署 ACL
scp luci-app-system-hub/root/usr/share/rpcd/acl.d/luci-app-system-hub.json \
    "$ROUTER:/usr/share/rpcd/acl.d/"

# 设置权限并重启
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.system-hub && /etc/init.d/rpcd restart"

echo "部署完成请清除浏览器缓存Ctrl+Shift+R"

浏览器测试

  1. 打开浏览器控制台F12
  2. 导航到模块页面
  3. 检查错误:
    • RPC 错误object not found、method not found、access denied
    • JavaScript 错误api.method is not a function
    • 404 错误(找不到视图文件)
  4. 测试功能:
    • 数据加载正确显示
    • 操作有效(启动/停止服务、保存设置)
    • 没有 "NaN"、"undefined" 或空值

远程 ubus 测试

在路由器上测试 RPCD 方法:

# 列出所有方法
ssh router "ubus list luci.system-hub"

# 调用无参数方法
ssh router "ubus call luci.system-hub status"

# 调用带参数方法
ssh router "ubus call luci.system-hub service_action '{\"service\":\"network\",\"action\":\"restart\"}'"

# 格式化 JSON 输出
ssh router "ubus call luci.system-hub get_health | jsonlint"

调试技巧

启用 RPCD 调试日志

# 编辑 /etc/init.d/rpcd
# 在 procd_set_param command 中添加 -v 标志
procd_set_param command "$PROG" -v

# 重启 RPCD
/etc/init.d/rpcd restart

# 监视日志
logread -f | grep rpcd

启用 JavaScript 控制台日志

// 添加到 api.js
console.log('API v0.1.0 已加载于', new Date().toISOString());

// 添加到视图
console.log('正在加载健康数据...');
API.getHealth().then(function(data) {
    console.log('健康数据:', data);
});

测试 JSON 输出

# 在路由器上
/usr/libexec/rpcd/luci.system-hub call get_health | jsonlint

# 检查常见错误
# - 缺少逗号
# - 尾随逗号
# - 未加引号的键
# - 无效的转义序列

最佳实践总结

应该做的:

  • 对所有 ubus 对象使用 luci. 前缀
  • RPCD 脚本命名与 ubus 对象完全匹配
  • 对 API 模块使用 baseclass.extend()
  • 使用 'require module/api as API' 模式导入 API
  • 在前端添加 null/undefined 检查:health.cpu?.usage || 0
  • 部署前使用 jsonlint 验证 JSON
  • 浏览器测试前先使用 ubus call 测试
  • 后端更改后重启 RPCD
  • 前端更改后清除浏览器缓存
  • 提交前运行 ./secubox-tools/validate-modules.sh

不应该做的:

  • 使用没有 luci. 前缀的 ubus 对象名称
  • 对 API 模块使用 baseclass.singleton() 或普通对象
  • 使用 L.require('module.path') 导入 API返回类而不是实例
  • 忘记将方法添加到 ACL 文件
  • 在 ACL 部分混淆读/写方法
  • 从 RPCD 脚本输出非 JSON
  • 后端和前端之间使用不一致的数据结构
  • 不先本地测试就部署
  • 假设数据存在 - 始终检查 null/undefined
  • 忘记使 RPCD 脚本可执行(chmod +x

版本历史

v1.02025-12-26

  • 初始参考指南
  • 基于 luci-app-secubox v1.0.0 和 luci-app-system-hub v0.1.0
  • 记录了所有关键模式和常见错误
  • 针对实际实现挑战进行了验证

参考资料


联系方式

如有问题或想为本参考指南做贡献:


参考指南结束