diff --git a/components/ts_console/commands/ts_cmd_ssh.c b/components/ts_console/commands/ts_cmd_ssh.c index 168601f..db7d598 100644 --- a/components/ts_console/commands/ts_cmd_ssh.c +++ b/components/ts_console/commands/ts_cmd_ssh.c @@ -16,6 +16,7 @@ */ #include "ts_console.h" +#include "ts_i18n.h" #include "ts_api.h" #include "ts_log.h" #include "ts_core.h" /* TS_MALLOC_PSRAM, TS_STRDUP_PSRAM */ @@ -1530,56 +1531,56 @@ static int ssh_cmd_handler(int argc, char **argv) /* 显示帮助 */ if (s_ssh_args.help->count > 0) { - ts_console_printf("\nUsage: ssh [options]\n\n"); - ts_console_printf("SSH client for remote operations\n\n"); - ts_console_printf("Connection Options:\n"); - ts_console_printf(" --host Remote host address\n"); - ts_console_printf(" --port SSH port (default: 22)\n"); - ts_console_printf(" --user Username\n"); - ts_console_printf(" --password Password (for password auth)\n"); - ts_console_printf(" --key Private key file (for public key auth)\n"); - ts_console_printf(" --keyid Use key from secure storage (see 'key' command)\n"); - ts_console_printf(" --exec Execute command on remote host\n"); - ts_console_printf(" --shell Open interactive shell\n"); - ts_console_printf(" --forward Port forwarding: L::\n"); - ts_console_printf(" --test Test SSH connection\n"); - ts_console_printf(" --timeout Connection timeout in seconds (default: 10)\n"); - ts_console_printf(" --verbose Show detailed output\n"); - ts_console_printf("\nKey File Management:\n"); - ts_console_printf(" --keygen Generate SSH key pair to file\n"); - ts_console_printf(" --copyid Deploy public key to remote server\n"); - ts_console_printf(" --revoke Remove public key from remote server\n"); - ts_console_printf(" --type Key type: rsa, rsa2048, rsa4096, ecdsa, ec256, ec384\n"); - ts_console_printf(" --output Output file path for private key\n"); - ts_console_printf(" --comment Comment for the public key\n"); - ts_console_printf("\nGeneral:\n"); - ts_console_printf(" --help Show this help\n"); - ts_console_printf("\nExamples:\n"); - ts_console_printf(" # Generate RSA key pair to file\n"); - ts_console_printf(" ssh --keygen --type rsa2048 --output /sdcard/id_rsa\n"); + ts_console_printf("\n%s\n\n", TS_STR(TS_STR_SSH_HELP_USAGE)); + ts_console_printf("%s\n\n", TS_STR(TS_STR_SSH_HELP_DESC)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_CONN_OPTS)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_HOST)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_PORT)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_USER)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_PASSWORD)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_KEY)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_KEYID)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EXEC)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_SHELL)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_FORWARD)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_TEST)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_TIMEOUT)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_VERBOSE)); + ts_console_printf("\n%s\n", TS_STR(TS_STR_SSH_HELP_KEY_MGMT)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_KEYGEN)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_COPYID)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_REVOKE)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_TYPE)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_OUTPUT)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_COMMENT)); + ts_console_printf("\n%s\n", TS_STR(TS_STR_SSH_HELP_GENERAL)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_HELP_OPT)); + ts_console_printf("\n%s\n", TS_STR(TS_STR_SSH_HELP_EXAMPLES)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_KEYGEN)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_KEYGEN_CMD)); ts_console_printf(" \n"); - ts_console_printf(" # Connect using stored key (manage keys with 'key' command)\n"); - ts_console_printf(" key --list # List stored keys\n"); - ts_console_printf(" key --generate --id agx --type rsa # Generate RSA key\n"); - ts_console_printf(" ssh --host 192.168.1.100 --user nvidia --keyid agx --shell\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_STORED)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_KEY_LIST)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_KEY_GEN)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_SSH)); ts_console_printf(" \n"); - ts_console_printf(" # Deploy public key to remote server (using secure storage key)\n"); - ts_console_printf(" ssh --copyid --host 192.168.1.100 --user nvidia --password pw --keyid agx\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_COPYID)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_COPYID_CMD)); ts_console_printf(" \n"); - ts_console_printf(" # Revoke (remove) deployed public key from remote server\n"); - ts_console_printf(" ssh --revoke --host 192.168.1.100 --user nvidia --password pw --keyid agx\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_REVOKE)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_REVOKE_CMD)); ts_console_printf(" \n"); - ts_console_printf(" # Or deploy using file-based key\n"); - ts_console_printf(" ssh --copyid --host 192.168.1.100 --user nvidia --password pw --key /sdcard/id_rsa\n"); - ts_console_printf("\nNote: Key management has moved to the 'key' command. Use 'key --help' for details.\n"); - ts_console_printf(" Use 'hosts' command to manage known hosts.\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_FILE)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_EX_FILE_CMD)); + ts_console_printf("\n%s\n", TS_STR(TS_STR_SSH_HELP_NOTE)); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_NOTE_HOSTS)); return 0; } /* 参数错误检查 */ if (nerrors > 0) { arg_print_errors(stderr, s_ssh_args.end, "ssh"); - ts_console_printf("Use 'ssh --help' for usage information\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_USE)); return 1; } @@ -1754,7 +1755,7 @@ static int ssh_cmd_handler(int argc, char **argv) result = do_ssh_test(host, port, user, &auth, timeout); } else { ts_console_printf("Error: Specify --exec, --shell, --forward, --test, or --keygen\n"); - ts_console_printf("Use 'ssh --help' for usage information\n"); + ts_console_printf("%s\n", TS_STR(TS_STR_SSH_HELP_USE)); result = 1; } diff --git a/components/ts_console/include/ts_i18n.h b/components/ts_console/include/ts_i18n.h index 9b0e0e8..6fb3544 100644 --- a/components/ts_console/include/ts_i18n.h +++ b/components/ts_console/include/ts_i18n.h @@ -30,9 +30,6 @@ extern "C" { typedef enum { TS_LANG_EN = 0, /**< English (default) */ TS_LANG_ZH_CN, /**< Simplified Chinese */ - TS_LANG_ZH_TW, /**< Traditional Chinese */ - TS_LANG_JA, /**< Japanese */ - TS_LANG_KO, /**< Korean */ TS_LANG_MAX } ts_language_t; @@ -109,6 +106,49 @@ typedef enum { TS_STR_SHUTTING_DOWN, TS_STR_REBOOT_IN, + /* Help messages */ + TS_STR_HELP_USE_CMD, /**< "Use ' --help' for command details" */ + TS_STR_SSH_HELP_USE, /**< "Use 'ssh --help' for usage information" */ + TS_STR_SSH_HELP_USAGE, + TS_STR_SSH_HELP_DESC, + TS_STR_SSH_HELP_CONN_OPTS, + TS_STR_SSH_HELP_HOST, + TS_STR_SSH_HELP_PORT, + TS_STR_SSH_HELP_USER, + TS_STR_SSH_HELP_PASSWORD, + TS_STR_SSH_HELP_KEY, + TS_STR_SSH_HELP_KEYID, + TS_STR_SSH_HELP_EXEC, + TS_STR_SSH_HELP_SHELL, + TS_STR_SSH_HELP_FORWARD, + TS_STR_SSH_HELP_TEST, + TS_STR_SSH_HELP_TIMEOUT, + TS_STR_SSH_HELP_VERBOSE, + TS_STR_SSH_HELP_KEY_MGMT, + TS_STR_SSH_HELP_KEYGEN, + TS_STR_SSH_HELP_COPYID, + TS_STR_SSH_HELP_REVOKE, + TS_STR_SSH_HELP_TYPE, + TS_STR_SSH_HELP_OUTPUT, + TS_STR_SSH_HELP_COMMENT, + TS_STR_SSH_HELP_GENERAL, + TS_STR_SSH_HELP_HELP_OPT, + TS_STR_SSH_HELP_EXAMPLES, + TS_STR_SSH_HELP_EX_KEYGEN, + TS_STR_SSH_HELP_EX_KEYGEN_CMD, + TS_STR_SSH_HELP_EX_STORED, + TS_STR_SSH_HELP_EX_KEY_LIST, + TS_STR_SSH_HELP_EX_KEY_GEN, + TS_STR_SSH_HELP_EX_SSH, + TS_STR_SSH_HELP_EX_COPYID, + TS_STR_SSH_HELP_EX_COPYID_CMD, + TS_STR_SSH_HELP_EX_REVOKE, + TS_STR_SSH_HELP_EX_REVOKE_CMD, + TS_STR_SSH_HELP_EX_FILE, + TS_STR_SSH_HELP_EX_FILE_CMD, + TS_STR_SSH_HELP_NOTE, + TS_STR_SSH_HELP_NOTE_HOSTS, + TS_STR_MAX } ts_string_id_t; diff --git a/components/ts_console/src/ts_console.c b/components/ts_console/src/ts_console.c index 5e0d987..d4ab0cf 100644 --- a/components/ts_console/src/ts_console.c +++ b/components/ts_console/src/ts_console.c @@ -8,6 +8,7 @@ */ #include "ts_console.h" +#include "ts_i18n.h" #include "ts_log.h" #include "esp_console.h" #include "esp_vfs_dev.h" @@ -249,6 +250,12 @@ esp_err_t ts_console_init(const ts_console_config_t *config) return ESP_ERR_INVALID_STATE; } + /* Initialize i18n (loads system.language from config for help/ssh --help etc.) */ + esp_err_t i18n_ret = ts_i18n_init(); + if (i18n_ret != ESP_OK) { + return i18n_ret; + } + /* Use defaults if no config provided */ if (config) { s_console.config = *config; diff --git a/components/ts_console/src/ts_console_builtin.c b/components/ts_console/src/ts_console_builtin.c index d0eb90f..debb5b0 100644 --- a/components/ts_console/src/ts_console_builtin.c +++ b/components/ts_console/src/ts_console_builtin.c @@ -49,7 +49,7 @@ static int cmd_help(int argc, char **argv) } } - ts_console_printf("Use ' --help' for command details\n\n"); + ts_console_printf("%s\n\n", TS_STR(TS_STR_HELP_USE_CMD)); return 0; } @@ -288,9 +288,6 @@ static int cmd_lang(int argc, char **argv) ts_console_printf("\nAvailable languages:\n"); ts_console_printf(" en - %s\n", ts_i18n_get_language_name(TS_LANG_EN)); ts_console_printf(" zh-cn - %s\n", ts_i18n_get_language_name(TS_LANG_ZH_CN)); - ts_console_printf(" zh-tw - %s\n", ts_i18n_get_language_name(TS_LANG_ZH_TW)); - ts_console_printf(" ja - %s\n", ts_i18n_get_language_name(TS_LANG_JA)); - ts_console_printf(" ko - %s\n", ts_i18n_get_language_name(TS_LANG_KO)); ts_console_printf("\nCurrent: %s\n\n", ts_i18n_get_language_name(ts_i18n_get_language())); return 0; } @@ -302,9 +299,6 @@ static int cmd_lang(int argc, char **argv) if (strcmp(lang_str, "en") == 0) lang = TS_LANG_EN; else if (strcmp(lang_str, "zh-cn") == 0 || strcmp(lang_str, "zh") == 0) lang = TS_LANG_ZH_CN; - else if (strcmp(lang_str, "zh-tw") == 0) lang = TS_LANG_ZH_TW; - else if (strcmp(lang_str, "ja") == 0) lang = TS_LANG_JA; - else if (strcmp(lang_str, "ko") == 0) lang = TS_LANG_KO; else { ts_console_error("Unknown language: %s\n", lang_str); ts_console_printf("Use 'lang -l' to list available languages\n"); @@ -378,7 +372,7 @@ static int cmd_log(int argc, char **argv) esp_err_t ts_console_register_builtin_cmds(void) { /* Initialize lang command arguments */ - s_lang_args.lang = arg_str0(NULL, NULL, "", "Language code (en/zh-cn/zh-tw/ja/ko)"); + s_lang_args.lang = arg_str0(NULL, NULL, "", "Language code (en/zh-cn)"); s_lang_args.list = arg_lit0("l", "list", "List available languages"); s_lang_args.end = arg_end(2); diff --git a/components/ts_console/src/ts_i18n.c b/components/ts_console/src/ts_i18n.c index a16f47c..90f30c4 100644 --- a/components/ts_console/src/ts_i18n.c +++ b/components/ts_console/src/ts_i18n.c @@ -82,6 +82,49 @@ static const char *s_strings_en[TS_STR_MAX] = { [TS_STR_REBOOTING] = "Rebooting...", [TS_STR_SHUTTING_DOWN] = "Shutting down...", [TS_STR_REBOOT_IN] = "Rebooting in %d seconds", + + /* Help messages */ + [TS_STR_HELP_USE_CMD] = "Use ' --help' for command details", + [TS_STR_SSH_HELP_USE] = "Use 'ssh --help' for usage information", + [TS_STR_SSH_HELP_USAGE] = "Usage: ssh [options]", + [TS_STR_SSH_HELP_DESC] = "SSH client for remote operations", + [TS_STR_SSH_HELP_CONN_OPTS]= "Connection Options:", + [TS_STR_SSH_HELP_HOST] = " --host Remote host address", + [TS_STR_SSH_HELP_PORT] = " --port SSH port (default: 22)", + [TS_STR_SSH_HELP_USER] = " --user Username", + [TS_STR_SSH_HELP_PASSWORD] = " --password Password (for password auth)", + [TS_STR_SSH_HELP_KEY] = " --key Private key file (for public key auth)", + [TS_STR_SSH_HELP_KEYID] = " --keyid Use key from secure storage (see 'key' command)", + [TS_STR_SSH_HELP_EXEC] = " --exec Execute command on remote host", + [TS_STR_SSH_HELP_SHELL] = " --shell Open interactive shell", + [TS_STR_SSH_HELP_FORWARD] = " --forward Port forwarding: L::", + [TS_STR_SSH_HELP_TEST] = " --test Test SSH connection", + [TS_STR_SSH_HELP_TIMEOUT] = " --timeout Connection timeout in seconds (default: 10)", + [TS_STR_SSH_HELP_VERBOSE] = " --verbose Show detailed output", + [TS_STR_SSH_HELP_KEY_MGMT]= "Key File Management:", + [TS_STR_SSH_HELP_KEYGEN] = " --keygen Generate SSH key pair to file", + [TS_STR_SSH_HELP_COPYID] = " --copyid Deploy public key to remote server", + [TS_STR_SSH_HELP_REVOKE] = " --revoke Remove public key from remote server", + [TS_STR_SSH_HELP_TYPE] = " --type Key type: rsa, rsa2048, rsa4096, ecdsa, ec256, ec384", + [TS_STR_SSH_HELP_OUTPUT] = " --output Output file path for private key", + [TS_STR_SSH_HELP_COMMENT] = " --comment Comment for the public key", + [TS_STR_SSH_HELP_GENERAL] = "General:", + [TS_STR_SSH_HELP_HELP_OPT]= " --help Show this help", + [TS_STR_SSH_HELP_EXAMPLES]= "Examples:", + [TS_STR_SSH_HELP_EX_KEYGEN]= " # Generate RSA key pair to file", + [TS_STR_SSH_HELP_EX_KEYGEN_CMD] = " ssh --keygen --type rsa2048 --output /sdcard/id_rsa", + [TS_STR_SSH_HELP_EX_STORED]= " # Connect using stored key (manage keys with 'key' command)", + [TS_STR_SSH_HELP_EX_KEY_LIST] = " key --list # List stored keys", + [TS_STR_SSH_HELP_EX_KEY_GEN] = " key --generate --id agx --type rsa # Generate RSA key", + [TS_STR_SSH_HELP_EX_SSH] = " ssh --host 192.168.1.100 --user nvidia --keyid agx --shell", + [TS_STR_SSH_HELP_EX_COPYID]= " # Deploy public key to remote server (using secure storage key)", + [TS_STR_SSH_HELP_EX_COPYID_CMD] = " ssh --copyid --host 192.168.1.100 --user nvidia --password pw --keyid agx", + [TS_STR_SSH_HELP_EX_REVOKE]= " # Revoke (remove) deployed public key from remote server", + [TS_STR_SSH_HELP_EX_REVOKE_CMD] = " ssh --revoke --host 192.168.1.100 --user nvidia --password pw --keyid agx", + [TS_STR_SSH_HELP_EX_FILE] = " # Or deploy using file-based key", + [TS_STR_SSH_HELP_EX_FILE_CMD] = " ssh --copyid --host 192.168.1.100 --user nvidia --password pw --key /sdcard/id_rsa", + [TS_STR_SSH_HELP_NOTE] = "Note: Key management has moved to the 'key' command. Use 'key --help' for details.", + [TS_STR_SSH_HELP_NOTE_HOSTS] = " Use 'hosts' command to manage known hosts.", }; /* Simplified Chinese strings */ @@ -150,228 +193,61 @@ static const char *s_strings_zh_cn[TS_STR_MAX] = { [TS_STR_REBOOTING] = "正在重启...", [TS_STR_SHUTTING_DOWN] = "正在关机...", [TS_STR_REBOOT_IN] = "%d 秒后重启", -}; - -/* Traditional Chinese strings */ -static const char *s_strings_zh_tw[TS_STR_MAX] = { - /* System messages */ - [TS_STR_WELCOME] = "歡迎使用天山作業系統", - [TS_STR_VERSION] = "版本", - [TS_STR_READY] = "就緒", - [TS_STR_ERROR] = "錯誤", - [TS_STR_SUCCESS] = "成功", - [TS_STR_FAILED] = "失敗", - [TS_STR_UNKNOWN_CMD] = "未知命令", - [TS_STR_HELP_HEADER] = "可用命令:", - [TS_STR_USAGE] = "用法", - - /* Common prompts */ - [TS_STR_YES] = "是", - [TS_STR_NO] = "否", - [TS_STR_OK] = "確定", - [TS_STR_CANCEL] = "取消", - [TS_STR_CONFIRM] = "確認", - [TS_STR_LOADING] = "載入中...", - [TS_STR_PLEASE_WAIT] = "請稍候...", - - /* Device status */ - [TS_STR_DEVICE_INFO] = "裝置資訊", - [TS_STR_UPTIME] = "運行時間", - [TS_STR_FREE_HEAP] = "可用記憶體", - [TS_STR_CHIP_MODEL] = "晶片型號", - [TS_STR_FIRMWARE_VER] = "韌體版本", - [TS_STR_TEMPERATURE] = "溫度", - - /* Network messages */ - [TS_STR_WIFI_CONNECTED] = "WiFi 已連接", - [TS_STR_WIFI_DISCONNECTED] = "WiFi 已斷開", - [TS_STR_WIFI_SCANNING] = "正在掃描 WiFi 網路...", - [TS_STR_WIFI_CONNECTING] = "正在連接 WiFi...", - [TS_STR_IP_ADDRESS] = "IP 位址", - [TS_STR_MAC_ADDRESS] = "MAC 位址", - [TS_STR_SIGNAL_STRENGTH] = "訊號強度", - - /* LED messages */ - [TS_STR_LED_CONTROLLER] = "LED 控制器", - [TS_STR_LED_COUNT] = "LED 數量", - [TS_STR_BRIGHTNESS] = "亮度", - [TS_STR_EFFECT] = "特效", - [TS_STR_COLOR] = "顏色", - - /* Power messages */ - [TS_STR_VOLTAGE] = "電壓", - [TS_STR_CURRENT] = "電流", - [TS_STR_POWER] = "功率", - [TS_STR_POWER_GOOD] = "電源正常", - [TS_STR_POWER_OFF] = "電源關閉", - /* Error messages */ - [TS_STR_ERR_INVALID_ARG] = "無效參數", - [TS_STR_ERR_NOT_FOUND] = "未找到", - [TS_STR_ERR_NO_MEM] = "記憶體不足", - [TS_STR_ERR_TIMEOUT] = "逾時", - [TS_STR_ERR_NOT_SUPPORTED] = "不支援", - [TS_STR_ERR_INVALID_STATE] = "狀態無效", - [TS_STR_ERR_IO] = "I/O 錯誤", - - /* Reboot/shutdown */ - [TS_STR_REBOOTING] = "正在重新啟動...", - [TS_STR_SHUTTING_DOWN] = "正在關機...", - [TS_STR_REBOOT_IN] = "%d 秒後重新啟動", -}; - -/* Japanese strings */ -static const char *s_strings_ja[TS_STR_MAX] = { - /* System messages */ - [TS_STR_WELCOME] = "TianShanOS へようこそ", - [TS_STR_VERSION] = "バージョン", - [TS_STR_READY] = "準備完了", - [TS_STR_ERROR] = "エラー", - [TS_STR_SUCCESS] = "成功", - [TS_STR_FAILED] = "失敗", - [TS_STR_UNKNOWN_CMD] = "不明なコマンド", - [TS_STR_HELP_HEADER] = "使用可能なコマンド:", - [TS_STR_USAGE] = "使用法", - - /* Common prompts */ - [TS_STR_YES] = "はい", - [TS_STR_NO] = "いいえ", - [TS_STR_OK] = "OK", - [TS_STR_CANCEL] = "キャンセル", - [TS_STR_CONFIRM] = "確認", - [TS_STR_LOADING] = "読み込み中...", - [TS_STR_PLEASE_WAIT] = "お待ちください...", - - /* Device status */ - [TS_STR_DEVICE_INFO] = "デバイス情報", - [TS_STR_UPTIME] = "稼働時間", - [TS_STR_FREE_HEAP] = "空きメモリ", - [TS_STR_CHIP_MODEL] = "チップモデル", - [TS_STR_FIRMWARE_VER] = "ファームウェア", - [TS_STR_TEMPERATURE] = "温度", - - /* Network messages */ - [TS_STR_WIFI_CONNECTED] = "WiFi 接続済み", - [TS_STR_WIFI_DISCONNECTED] = "WiFi 切断", - [TS_STR_WIFI_SCANNING] = "WiFi スキャン中...", - [TS_STR_WIFI_CONNECTING] = "WiFi に接続中...", - [TS_STR_IP_ADDRESS] = "IPアドレス", - [TS_STR_MAC_ADDRESS] = "MACアドレス", - [TS_STR_SIGNAL_STRENGTH] = "信号強度", - - /* LED messages */ - [TS_STR_LED_CONTROLLER] = "LEDコントローラ", - [TS_STR_LED_COUNT] = "LED数", - [TS_STR_BRIGHTNESS] = "明るさ", - [TS_STR_EFFECT] = "エフェクト", - [TS_STR_COLOR] = "色", - - /* Power messages */ - [TS_STR_VOLTAGE] = "電圧", - [TS_STR_CURRENT] = "電流", - [TS_STR_POWER] = "電力", - [TS_STR_POWER_GOOD] = "電源正常", - [TS_STR_POWER_OFF] = "電源オフ", - - /* Error messages */ - [TS_STR_ERR_INVALID_ARG] = "無効な引数", - [TS_STR_ERR_NOT_FOUND] = "見つかりません", - [TS_STR_ERR_NO_MEM] = "メモリ不足", - [TS_STR_ERR_TIMEOUT] = "タイムアウト", - [TS_STR_ERR_NOT_SUPPORTED] = "非対応", - [TS_STR_ERR_INVALID_STATE] = "無効な状態", - [TS_STR_ERR_IO] = "I/O エラー", - - /* Reboot/shutdown */ - [TS_STR_REBOOTING] = "再起動中...", - [TS_STR_SHUTTING_DOWN] = "シャットダウン中...", - [TS_STR_REBOOT_IN] = "%d秒後に再起動", -}; - -/* Korean strings */ -static const char *s_strings_ko[TS_STR_MAX] = { - /* System messages */ - [TS_STR_WELCOME] = "TianShanOS에 오신 것을 환영합니다", - [TS_STR_VERSION] = "버전", - [TS_STR_READY] = "준비됨", - [TS_STR_ERROR] = "오류", - [TS_STR_SUCCESS] = "성공", - [TS_STR_FAILED] = "실패", - [TS_STR_UNKNOWN_CMD] = "알 수 없는 명령", - [TS_STR_HELP_HEADER] = "사용 가능한 명령:", - [TS_STR_USAGE] = "사용법", - - /* Common prompts */ - [TS_STR_YES] = "예", - [TS_STR_NO] = "아니오", - [TS_STR_OK] = "확인", - [TS_STR_CANCEL] = "취소", - [TS_STR_CONFIRM] = "확인", - [TS_STR_LOADING] = "로딩 중...", - [TS_STR_PLEASE_WAIT] = "잠시만 기다려 주세요...", - - /* Device status */ - [TS_STR_DEVICE_INFO] = "장치 정보", - [TS_STR_UPTIME] = "가동 시간", - [TS_STR_FREE_HEAP] = "여유 메모리", - [TS_STR_CHIP_MODEL] = "칩 모델", - [TS_STR_FIRMWARE_VER] = "펌웨어 버전", - [TS_STR_TEMPERATURE] = "온도", - - /* Network messages */ - [TS_STR_WIFI_CONNECTED] = "WiFi 연결됨", - [TS_STR_WIFI_DISCONNECTED] = "WiFi 연결 끊김", - [TS_STR_WIFI_SCANNING] = "WiFi 네트워크 검색 중...", - [TS_STR_WIFI_CONNECTING] = "WiFi 연결 중...", - [TS_STR_IP_ADDRESS] = "IP 주소", - [TS_STR_MAC_ADDRESS] = "MAC 주소", - [TS_STR_SIGNAL_STRENGTH] = "신호 강도", - - /* LED messages */ - [TS_STR_LED_CONTROLLER] = "LED 컨트롤러", - [TS_STR_LED_COUNT] = "LED 개수", - [TS_STR_BRIGHTNESS] = "밝기", - [TS_STR_EFFECT] = "효과", - [TS_STR_COLOR] = "색상", - - /* Power messages */ - [TS_STR_VOLTAGE] = "전압", - [TS_STR_CURRENT] = "전류", - [TS_STR_POWER] = "전력", - [TS_STR_POWER_GOOD] = "전원 정상", - [TS_STR_POWER_OFF] = "전원 꺼짐", - - /* Error messages */ - [TS_STR_ERR_INVALID_ARG] = "잘못된 인수", - [TS_STR_ERR_NOT_FOUND] = "찾을 수 없음", - [TS_STR_ERR_NO_MEM] = "메모리 부족", - [TS_STR_ERR_TIMEOUT] = "시간 초과", - [TS_STR_ERR_NOT_SUPPORTED] = "지원되지 않음", - [TS_STR_ERR_INVALID_STATE] = "잘못된 상태", - [TS_STR_ERR_IO] = "I/O 오류", - - /* Reboot/shutdown */ - [TS_STR_REBOOTING] = "재시작 중...", - [TS_STR_SHUTTING_DOWN] = "종료 중...", - [TS_STR_REBOOT_IN] = "%d초 후 재시작", + /* Help messages */ + [TS_STR_HELP_USE_CMD] = "使用 '<命令> --help' 查看命令详情", + [TS_STR_SSH_HELP_USE] = "使用 'ssh --help' 查看用法", + [TS_STR_SSH_HELP_USAGE] = "用法: ssh [选项]", + [TS_STR_SSH_HELP_DESC] = "SSH 远程操作客户端", + [TS_STR_SSH_HELP_CONN_OPTS]= "连接选项:", + [TS_STR_SSH_HELP_HOST] = " --host 远程主机地址", + [TS_STR_SSH_HELP_PORT] = " --port SSH 端口 (默认: 22)", + [TS_STR_SSH_HELP_USER] = " --user 用户名", + [TS_STR_SSH_HELP_PASSWORD] = " --password 密码 (用于密码认证)", + [TS_STR_SSH_HELP_KEY] = " --key 私钥文件 (用于公钥认证)", + [TS_STR_SSH_HELP_KEYID] = " --keyid 使用安全存储中的密钥 (见 'key' 命令)", + [TS_STR_SSH_HELP_EXEC] = " --exec 在远程主机执行命令", + [TS_STR_SSH_HELP_SHELL] = " --shell 打开交互式 shell", + [TS_STR_SSH_HELP_FORWARD] = " --forward 端口转发: L<本地>:<远程主机>:<远程端口>", + [TS_STR_SSH_HELP_TEST] = " --test 测试 SSH 连接", + [TS_STR_SSH_HELP_TIMEOUT] = " --timeout 连接超时秒数 (默认: 10)", + [TS_STR_SSH_HELP_VERBOSE] = " --verbose 显示详细输出", + [TS_STR_SSH_HELP_KEY_MGMT]= "密钥文件管理:", + [TS_STR_SSH_HELP_KEYGEN] = " --keygen 生成 SSH 密钥对到文件", + [TS_STR_SSH_HELP_COPYID] = " --copyid 部署公钥到远程服务器", + [TS_STR_SSH_HELP_REVOKE] = " --revoke 从远程服务器撤销公钥", + [TS_STR_SSH_HELP_TYPE] = " --type 密钥类型: rsa, rsa2048, rsa4096, ecdsa, ec256, ec384", + [TS_STR_SSH_HELP_OUTPUT] = " --output 私钥输出文件路径", + [TS_STR_SSH_HELP_COMMENT] = " --comment 公钥注释", + [TS_STR_SSH_HELP_GENERAL] = "通用:", + [TS_STR_SSH_HELP_HELP_OPT]= " --help 显示此帮助", + [TS_STR_SSH_HELP_EXAMPLES]= "示例:", + [TS_STR_SSH_HELP_EX_KEYGEN]= " # 生成 RSA 密钥对到文件", + [TS_STR_SSH_HELP_EX_KEYGEN_CMD] = " ssh --keygen --type rsa2048 --output /sdcard/id_rsa", + [TS_STR_SSH_HELP_EX_STORED]= " # 使用存储的密钥连接 (使用 'key' 命令管理密钥)", + [TS_STR_SSH_HELP_EX_KEY_LIST] = " key --list # 列出存储的密钥", + [TS_STR_SSH_HELP_EX_KEY_GEN] = " key --generate --id agx --type rsa # 生成 RSA 密钥", + [TS_STR_SSH_HELP_EX_SSH] = " ssh --host 192.168.1.100 --user nvidia --keyid agx --shell", + [TS_STR_SSH_HELP_EX_COPYID]= " # 部署公钥到远程服务器 (使用安全存储密钥)", + [TS_STR_SSH_HELP_EX_COPYID_CMD] = " ssh --copyid --host 192.168.1.100 --user nvidia --password pw --keyid agx", + [TS_STR_SSH_HELP_EX_REVOKE]= " # 从远程服务器撤销已部署的公钥", + [TS_STR_SSH_HELP_EX_REVOKE_CMD] = " ssh --revoke --host 192.168.1.100 --user nvidia --password pw --keyid agx", + [TS_STR_SSH_HELP_EX_FILE] = " # 或使用基于文件的密钥部署", + [TS_STR_SSH_HELP_EX_FILE_CMD] = " ssh --copyid --host 192.168.1.100 --user nvidia --password pw --key /sdcard/id_rsa", + [TS_STR_SSH_HELP_NOTE] = "注意: 密钥管理已移至 'key' 命令。使用 'key --help' 查看详情。", + [TS_STR_SSH_HELP_NOTE_HOSTS] = " 使用 'hosts' 命令管理已知主机。", }; /* Language names */ static const char *s_language_names[TS_LANG_MAX] = { [TS_LANG_EN] = "English", [TS_LANG_ZH_CN] = "简体中文", - [TS_LANG_ZH_TW] = "繁體中文", - [TS_LANG_JA] = "日本語", - [TS_LANG_KO] = "한국어", }; /* All string tables */ static const char **s_string_tables[TS_LANG_MAX] = { [TS_LANG_EN] = s_strings_en, [TS_LANG_ZH_CN] = s_strings_zh_cn, - [TS_LANG_ZH_TW] = s_strings_zh_tw, - [TS_LANG_JA] = s_strings_ja, - [TS_LANG_KO] = s_strings_ko, }; /*===========================================================================*/ diff --git a/components/ts_webui/Kconfig b/components/ts_webui/Kconfig index b2478f2..4fc21a2 100644 --- a/components/ts_webui/Kconfig +++ b/components/ts_webui/Kconfig @@ -19,6 +19,15 @@ menu "TianShanOS WebUI Configuration" default 8 depends on TS_WEBUI_WS_ENABLE + config TS_WEBUI_TERMINAL_OUTPUT_BUF_SIZE + int "Terminal output buffer size (bytes)" + default 32768 + range 4096 131072 + depends on TS_WEBUI_WS_ENABLE + help + Max output size for local console commands (e.g. ssh --help). + 32KB recommended when PSRAM is available. + config TS_WEBUI_AUTH_REQUIRED bool "Require Authentication for API" default y diff --git a/components/ts_webui/src/ts_webui_ws.c b/components/ts_webui/src/ts_webui_ws.c index 341c8e5..2842261 100644 --- a/components/ts_webui/src/ts_webui_ws.c +++ b/components/ts_webui/src/ts_webui_ws.c @@ -34,8 +34,12 @@ #define MAX_WS_CLIENTS 8 #endif -/* 终端输出缓冲区大小 */ -#define TERMINAL_OUTPUT_BUF_SIZE 4096 +/* 终端输出缓冲区大小(可由 Kconfig 配置) */ +#ifdef CONFIG_TS_WEBUI_TERMINAL_OUTPUT_BUF_SIZE +#define TERMINAL_OUTPUT_BUF_SIZE CONFIG_TS_WEBUI_TERMINAL_OUTPUT_BUF_SIZE +#else +#define TERMINAL_OUTPUT_BUF_SIZE 32768 +#endif /* SSH Shell 输出缓冲区大小 */ #define SSH_OUTPUT_BUF_SIZE 2048 diff --git a/components/ts_webui/web/index.html b/components/ts_webui/web/index.html index eae73a8..75457b2 100644 --- a/components/ts_webui/web/index.html +++ b/components/ts_webui/web/index.html @@ -257,6 +257,11 @@

电压保护设置

currentLang = lang; try { localStorage.setItem('ts_language', lang); } catch(e) {} document.documentElement.lang = lang; + /* 同步到设备端 system.language(供终端 help 等 i18n 使用) */ + if (window.api && typeof window.api.configSet === 'function') { + var deviceLang = lang === 'zh-CN' ? 1 : 0; + window.api.configSet('system.language', deviceLang, true).catch(function(){}); + } window.dispatchEvent(new CustomEvent('languageChanged', { detail: { language: lang } })); return true; } diff --git a/components/ts_webui/web/js/app.js b/components/ts_webui/web/js/app.js index c6bd49e..0be8c44 100644 --- a/components/ts_webui/web/js/app.js +++ b/components/ts_webui/web/js/app.js @@ -129,6 +129,19 @@ document.addEventListener('DOMContentLoaded', () => { // 初始化认证 UI updateAuthUI(); + // 根据设备 system.language 同步 WebUI 语言(与设备终端 help i18n 一致) + (async function syncLanguageFromDevice() { + try { + const r = await api.configGet('system.language'); + if (r && r.code === 0 && r.data && r.data.value !== undefined) { + const webLang = r.data.value === 1 ? 'zh-CN' : 'en-US'; + if (typeof selectLanguage === 'function' && i18n && i18n.getLanguage() !== webLang) { + selectLanguage(webLang); + } + } + } catch (e) {} + })(); + // 更新 Footer 版本号 updateFooterVersion(); @@ -495,6 +508,12 @@ function handlePowerEvent(msg) { async function loadSystemPage() { clearInterval(refreshInterval); + // 取消之前设置的快捷操作刷新定时器(防止切换到其他页后仍触发) + if (quickActionsTimeoutId) { + clearTimeout(quickActionsTimeoutId); + quickActionsTimeoutId = null; + } + // 取消之前的订阅 if (subscriptionManager) { subscriptionManager.unsubscribe('system.dashboard'); // 取消聚合订阅 @@ -721,7 +740,10 @@ async function loadSystemPage() { await initDataWidgets(); // 快捷操作延迟 2 秒后台加载,避免与 initDataWidgets 的首次变量刷新竞争 API - setTimeout(() => void refreshQuickActions(), 2000); + quickActionsTimeoutId = setTimeout(() => { + quickActionsTimeoutId = null; + void refreshQuickActions(); + }, 2000); // 订阅 WebSocket 实时更新 - 使用聚合订阅(system.dashboard) if (subscriptionManager) { @@ -3972,7 +3994,7 @@ function deleteDataWidget(widgetId) { async function refreshQuickActions() { const container = document.getElementById('quick-actions-grid'); if (!container) { - console.warn('refreshQuickActions: quick-actions-grid not found'); + // 用户已切到其他页面,quick-actions-grid 不存在属正常情况,静默返回 return; } @@ -4167,6 +4189,15 @@ async function updateQuickActionServiceStatus() { // 触发快捷操作后的冷却时间(毫秒),避免连续触发导致后端只执行最后一个 let _quickActionTriggerCooldownUntil = 0; let _quickActionLastTriggeredId = ''; +let quickActionsTimeoutId = null; // 用于导航时取消,避免 quick-actions-grid not found + +/** 供 router 在页面切换时取消快捷操作定时器 */ +window.stopSystemPageTimers = function() { + if (quickActionsTimeoutId) { + clearTimeout(quickActionsTimeoutId); + quickActionsTimeoutId = null; + } +}; /** * 触发快捷操作 @@ -14165,11 +14196,23 @@ async function loadTerminalPage() { `; - // 初始化终端 + // 初始化终端(标签页可见时才连接,避免后台标签抢占当前会话导致 session_closed) webTerminal = new WebTerminal('terminal-container'); const ok = await webTerminal.init(); if (ok) { - webTerminal.connect(); + function doConnect() { + if (document.visibilityState === 'visible') { + webTerminal.connect(); + } else { + document.addEventListener('visibilitychange', function handler() { + if (document.visibilityState === 'visible') { + document.removeEventListener('visibilitychange', handler); + webTerminal.connect(); + } + }); + } + } + doConnect(); } } diff --git a/components/ts_webui/web/js/lang/en-US.js b/components/ts_webui/web/js/lang/en-US.js index 7c15f23..c4295e4 100644 --- a/components/ts_webui/web/js/lang/en-US.js +++ b/components/ts_webui/web/js/lang/en-US.js @@ -3297,6 +3297,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', { pageTitle: 'Web Terminal', errorLabel: 'Error', unknownError: 'Unknown error', + sessionClosed: 'Session closed, please reconnect', + sessionRestored: 'Session restored, please retry', terminalHint: 'Tip: Type help for commands | Ctrl+C interrupt | Ctrl+L clear | ↑↓ history', systemLogTitle: 'System Logs', levelLabel: 'Level', diff --git a/components/ts_webui/web/js/lang/zh-CN.js b/components/ts_webui/web/js/lang/zh-CN.js index 435728f..1519185 100644 --- a/components/ts_webui/web/js/lang/zh-CN.js +++ b/components/ts_webui/web/js/lang/zh-CN.js @@ -3306,6 +3306,8 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', { pageTitle: 'Web 终端', errorLabel: '错误', unknownError: '未知错误', + sessionClosed: '会话已关闭,请重新连接', + sessionRestored: '会话已恢复,请重试', terminalHint: '提示: 输入 help 查看命令 | Ctrl+C 中断 | Ctrl+L 清屏 | ↑↓ 历史', systemLogTitle: '系统日志', levelLabel: '级别', diff --git a/components/ts_webui/web/js/router.js b/components/ts_webui/web/js/router.js index 346fb17..16d762e 100644 --- a/components/ts_webui/web/js/router.js +++ b/components/ts_webui/web/js/router.js @@ -54,6 +54,14 @@ class Router { if (typeof stopDeviceStateMonitor === 'function') { stopDeviceStateMonitor(); } + // 取消系统页快捷操作定时器,避免切走后仍触发 refreshQuickActions + if (typeof stopSystemPageTimers === 'function') { + stopSystemPageTimers(); + } + // 离开终端页时主动断开 WebSocket,避免 1006 异常关闭 + if (typeof destroyWebTerminal === 'function') { + destroyWebTerminal(); + } // 检查访问权限 const access = this.checkAccess(hash); diff --git a/components/ts_webui/web/js/terminal.js b/components/ts_webui/web/js/terminal.js index 547e480..e84c96f 100644 --- a/components/ts_webui/web/js/terminal.js +++ b/components/ts_webui/web/js/terminal.js @@ -337,15 +337,22 @@ class WebTerminal { this.writeln('\r\n\x1b[1;31m' + (typeof t === 'function' ? t('terminal.connectionDisconnected') : '连接已断开') + '\x1b[0m'); - // 尝试重连 + // 尝试重连(仅当标签页可见时发起,避免后台 tab 抢占前台会话) if (event.code !== 1000) { // 非正常关闭 this.writeln('\x1b[33m' + (typeof t === 'function' ? t('terminal.reconnectIn') : '5秒后尝试重新连接...') + '\x1b[0m'); - setTimeout(() => { - if (!this.connected) { - this.writeln(typeof t === 'function' ? t('terminal.reconnecting') : '正在重新连接...'); - this.connect(); + const tryReconnect = () => { + if (this.connected) return; + if (document.visibilityState !== 'visible') { + document.addEventListener('visibilitychange', function handler() { + document.removeEventListener('visibilitychange', handler); + tryReconnect(); + }, { once: true }); + return; } - }, 5000); + this.writeln(typeof t === 'function' ? t('terminal.reconnecting') : '正在重新连接...'); + this.connect(); + }; + setTimeout(tryReconnect, 5000); } }; @@ -381,10 +388,18 @@ class WebTerminal { this.writePrompt(); break; - case 'error': - this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.errorLabel') : '错误') + ': ' + (msg.message || (typeof t === 'function' ? t('terminal.unknownError') : '未知错误')) + '\x1b[0m'); - this.writePrompt(); + case 'error': { + const errMsg = msg.message || (typeof t === 'function' ? t('terminal.unknownError') : '未知错误'); + if (errMsg.indexOf('Not a terminal session') >= 0 && this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify({ type: 'terminal_start' })); + const toast = typeof showToast === 'function' ? showToast : function(m) { console.log(m); }; + toast(typeof t === 'function' ? t('terminal.sessionRestored') : '会话已恢复,请重试', 'success'); + } else { + this.writeln('\x1b[1;31m' + (typeof t === 'function' ? t('terminal.errorLabel') : '错误') + ': ' + errMsg + '\x1b[0m'); + this.writePrompt(); + } break; + } case 'pong': // 心跳响应,忽略 @@ -405,6 +420,11 @@ class WebTerminal { this.write(msg.data); } break; + + case 'session_closed': + this.connected = false; + this.writeln('\x1b[33m' + (typeof t === 'function' ? t('terminal.sessionClosed') : '会话已关闭,请重新连接') + '\x1b[0m'); + break; default: console.log('Unknown message type:', msg.type); @@ -697,3 +717,11 @@ class WebTerminal { // 全局终端实例 let webTerminal = null; + +/** 供 router 在离开终端页时调用,主动关闭 WebSocket 避免 1006 */ +window.destroyWebTerminal = function() { + if (webTerminal) { + webTerminal.destroy(); + webTerminal = null; + } +};