From f0b311fc4385556db569537042911d21c8b561d1 Mon Sep 17 00:00:00 2001 From: gnacho Date: Tue, 15 Sep 2026 00:24:45 +0200 Subject: [PATCH 1/6] httpd: make the session timeout configurable The session timeout was a compile-time constant (200 s), so changing it meant rebuilding the firmware. Make it a variable that keeps the same default and add a "session " command, so it can be set from the console or from the startup configuration like the other settings. Measured on KP_9000_9XHML_X_V3_1: ROM +180 bytes (BANK1 +17, BANK2 +161) and xdata +2 bytes; the internal RAM map is unchanged. --- README.md | 1 + cmd_parser.c | 23 +++++++++++++++++++++++ html/app.js | 3 ++- httpd/httpd.c | 8 ++++++-- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74b2ffbd..1968fb26 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,7 @@ netmask zzz.zzz.zzz.zzz = Network mask of the switch port x name xxx = Name xxx the port number x port z 1g = Set 1g speed for port z igmp on/off = Turn IGMP on or off +session xxxx = Web session timeout in seconds (default 200) ``` [To be continue] diff --git a/cmd_parser.c b/cmd_parser.c index 5a3a3d60..e6decbeb 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -41,6 +41,8 @@ extern __xdata struct flash_region_t flash_region; extern __xdata char passwd[21]; +extern __xdata uint16_t session_timeout; + extern __xdata struct dhcp_state dhcp_state; __xdata uint8_t vlan_names[VLAN_NAMES_SIZE]; @@ -1593,6 +1595,25 @@ void parse_syslog(void) } } +void parse_session(void) +{ + if (cmd_words_len >= 2) { + /* atoi_short() returns zero for no digits and for a value past + * 65535; a zero-second timeout is not useful either, so both + * tests are needed. */ + if (!atoi_short(cmd_words_b[1]) || !atoi_results_short) { + print_string("Must be 1-65535 s\n"); + return; + } + session_timeout = atoi_results_short; + } + /* Also the confirmation after setting: one message for both. */ + print_string("Session timeout: "); + itoa_short(session_timeout); + print_string(" s\n"); +} + + // Parse command into words // cmd_words_len contains the number of words found. // cmd_words_b[] contains only start of a word offset. @@ -1895,6 +1916,8 @@ void cmd_parser(void) __banked } } else if (cmd_compare(0, "ingress")) { parse_ingress(); + } else if (cmd_compare(0, "session")) { + parse_session(); } else { print_string("Unknown command\n"); diff --git a/html/app.js b/html/app.js index 0a50b322..4bcb5c73 100644 --- a/html/app.js +++ b/html/app.js @@ -427,6 +427,7 @@ var CONF_CMDS=[ /^ip\s+(\d{1,3}\.){3}\d{1,3}$/,/^ip\s+dhcp$/, /^gw\s+(\d{1,3}\.){3}\d{1,3}$/,/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/, /^syslog\s+(on|off)$/,/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,/^syslog\s+port\s+\d{1,5}$/, + /^session\s+\d{1,5}$/, /^passwd\s+\S+$/,/^hostname\s+\S{1,23}$/, /^vlan\s+\d{1,4}\s+d$/,/^vlan\s+\d{1,4}\s+mgmt$/, /^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}t?)+$/, @@ -1612,7 +1613,7 @@ tabHooks.system={enter:sysLoad}; var CONF_OVERWRITE=[ /^ip\b/,/^gw\b/,/^netmask\b/,/^hostname\b/, - /^syslog\s+ip\b/,/^syslog\s+port\b/,/^passwd\b/, + /^syslog\s+ip\b/,/^syslog\s+port\b/,/^passwd\b/,/^session\b/, /^vlan\s+\d{1,4}\s+mgmt$/,/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^pvid\s+\d{1,2}\b/,/^ingress\b/, /^port\s+\d{1,2}(?!\s+name\b)/,/^port\s+\d{1,2}\s+name\b/, diff --git a/httpd/httpd.c b/httpd/httpd.c index f87de897..41a3fc65 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -12,7 +12,11 @@ #include "debug.h" #define SESSION_ID_LENGTH 12 -#define SESSION_TIMEOUT 200 +#define SESSION_TIMEOUT_DEFAULT 200 + +/* Seconds of inactivity after which a session cookie stops validating. + * Configurable with the "session" command, see cmd_parser.c. */ +__xdata uint16_t session_timeout = SESSION_TIMEOUT_DEFAULT; #define CMARK_S 6 @@ -341,7 +345,7 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) read_reg_timer(&now); if (session) { - if (now - last_session_use > SESSION_TIMEOUT) { + if (now - last_session_use > session_timeout) { dbg_string("Session expired\n"); } else { if (is_word_x(session, session_id)) { From 4686515de3385effe72a0c593b0c4a4a43f9caf8 Mon Sep 17 00:00:00 2001 From: gnacho Date: Wed, 16 Sep 2026 18:07:49 +0200 Subject: [PATCH 2/6] web: add a session timeout field to the Services card Adds a numeric input next to the syslog settings to read and set the session timeout without knowing the command. The current value is reported in information.json, so the field also shows the default the firmware was built with. Costs 86 bytes of code (BANK1) and nothing in RAM. --- html/app.js | 21 +++++++++++++++------ html/index.html | 2 ++ httpd/page_impl.c | 5 ++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/html/app.js b/html/app.js index 4bcb5c73..e4ddb7ae 100644 --- a/html/app.js +++ b/html/app.js @@ -71,8 +71,9 @@ bw_title:"Bandwidth limits",bw_h:"Mbit/s, 0.016-10000",bw_in:"Ingress limit",bw_ bw_exceed:"When exceeded",bw_fc:"Flow control",bw_drop:"Drop", bw_in_err:"Ingress limit must be 0.016-10000 Mbit/s",bw_out_err:"Egress limit must be 0.016-10000 Mbit/s", sy_network:"Network",sy_dhcp:"Use DHCP",sy_dhcp_t:"Request address via DHCP",sy_services:"Services", -sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port", -sy_services_note:"Service state reflects the startup config; runtime state is not readable.", + sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port",sy_sesstmo:"Session timeout (s)", + sy_services_note:"Service state reflects the startup config; runtime state is not readable.", + sy_sesstmo_err:"Session timeout must be 1-65535 s", sy_password:"Admin password",sy_newpw:"New password",sy_repeat:"Repeat",sy_pwapply:"Change password", sy_pw_note:"Takes effect immediately; save to flash to keep it after reboot.",sy_console:"Console", sy_cmd:"CLI command...",sy_send:"Send",sy_startup:"Startup configuration",sy_replayed:"replayed on every boot", @@ -182,8 +183,9 @@ bw_title:"帯域制限",bw_h:"Mbit/s、0.016〜10000",bw_in:"入力制限",bw_ou bw_exceed:"超過時の動作",bw_fc:"フロー制御",bw_drop:"破棄", bw_in_err:"入力制限は 0.016〜10000 Mbit/s の範囲で指定してください",bw_out_err:"出力制限は 0.016〜10000 Mbit/s の範囲で指定してください", sy_network:"ネットワーク",sy_dhcp:"DHCP を使用",sy_dhcp_t:"DHCP でアドレスを取得",sy_services:"サービス", -sy_igmp:"IGMP スヌーピング",sy_sysip:"サーバー IP",sy_server:"サーバー",sy_port:"ポート", -sy_services_note:"サービスの状態は起動設定を反映しています。実行時の状態は読み取れません。", + sy_igmp:"IGMP スヌーピング",sy_sysip:"サーバー IP",sy_server:"サーバー",sy_port:"ポート",sy_sesstmo:"セッションタイムアウト (秒)", + sy_services_note:"サービスの状態は起動設定を反映しています。実行時の状態は読み取れません。", + sy_sesstmo_err:"セッションタイムアウトは 1〜65535 秒で指定してください", sy_password:"管理者パスワード",sy_newpw:"新しいパスワード",sy_repeat:"再入力",sy_pwapply:"パスワードを変更", sy_pw_note:"即時に反映されます。再起動後も保持するにはフラッシュに保存してください。",sy_console:"コンソール", sy_cmd:"CLI コマンド...",sy_send:"送信",sy_startup:"起動設定",sy_replayed:"起動のたびに再実行されます", @@ -293,8 +295,9 @@ bw_title:"带宽限制",bw_h:"Mbit/s,0.016-10000",bw_in:"入方向限速",bw_o bw_exceed:"超限动作",bw_fc:"流量控制",bw_drop:"丢弃", bw_in_err:"入方向限速范围为 0.016-10000 Mbit/s",bw_out_err:"出方向限速范围为 0.016-10000 Mbit/s", sy_network:"网络",sy_dhcp:"使用 DHCP",sy_dhcp_t:"通过 DHCP 获取地址",sy_services:"服务", -sy_igmp:"IGMP 侦听",sy_sysip:"服务器 IP",sy_server:"服务器",sy_port:"端口", -sy_services_note:"服务状态反映启动配置;运行时状态无法读取。", + sy_igmp:"IGMP 侦听",sy_sysip:"服务器 IP",sy_server:"服务器",sy_port:"端口",sy_sesstmo:"会话超时(秒)", + sy_services_note:"服务状态反映启动配置;运行时状态无法读取。", + sy_sesstmo_err:"会话超时必须为 1-65535 秒", sy_password:"管理员密码",sy_newpw:"新密码",sy_repeat:"重复输入",sy_pwapply:"修改密码", sy_pw_note:"立即生效;如需重启后保留请保存到 Flash。",sy_console:"控制台", sy_cmd:"CLI 命令...",sy_send:"发送",sy_startup:"启动配置",sy_replayed:"每次启动时重新执行", @@ -1503,6 +1506,7 @@ function sysLoad(){ var sl=(S.info.syslog_server||"").split(":"); if(sl[0]&&sl[0]!=="0.0.0.0")$("sy-sysip").value=sl[0]; if(sl[1])$("sy-sysport").value=sl[1]; + if(S.info.session_timeout)$("sy-sesstmo").value=S.info.session_timeout; }).catch(function(){}); cfgReload(); } @@ -1555,6 +1559,11 @@ $("sy-syslog").addEventListener("change",function(){ }else cmds.push("syslog off"); postCmds(cmds).catch(function(){}); }); +$("sy-sesstmo").addEventListener("change",function(){ + var v=+this.value; + if(!(v>=1&&v<=65535)){toast(t("sy_sesstmo_err"),"err");sysLoad();return;} + postCmd("session "+v).catch(function(){sysLoad()}); +}); $("sy-pwapply").addEventListener("click",function(){ var a=$("sy-pw1").value,b=$("sy-pw2").value; if(a.length<1||a.length>20){toast(t("sy_pw_len"),"err");return;} diff --git a/html/index.html b/html/index.html index 3e1696b1..3bd9105c 100644 --- a/html/index.html +++ b/html/index.html @@ -375,6 +375,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} Port +

Service state reflects the startup config; runtime state is not readable.

Admin password

diff --git a/httpd/page_impl.c b/httpd/page_impl.c index ec7ef26a..6011f80a 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -28,6 +28,7 @@ extern __code const struct machine machine; extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE]; extern __xdata uint16_t slen; extern __xdata uint16_t management_vlan; +extern __xdata uint16_t session_timeout; extern __xdata uint16_t cont_len; extern __xdata uint32_t cont_addr; extern __code uint8_t * __code hex; @@ -229,7 +230,9 @@ void send_basic_info(void) itoa_html(syslog_state.server_ip[2]); char_to_html('.'); itoa_html(syslog_state.server_ip[3]); char_to_html(':'); itoa16_html(syslog_state.server_port); - slen += strtox(outbuf + slen, "\",\"mac_address\":\""); + slen += strtox(outbuf + slen, "\",\"session_timeout\":"); + itoa16_html(session_timeout); + slen += strtox(outbuf + slen, ",\"mac_address\":\""); byte_to_html(uip_ethaddr.addr[0]); char_to_html(':'); byte_to_html(uip_ethaddr.addr[1]); char_to_html(':'); byte_to_html(uip_ethaddr.addr[2]); char_to_html(':'); From cdc84c75e4b71988f6c7d9eaa97f29ae8cb06a1b Mon Sep 17 00:00:00 2001 From: gnacho Date: Wed, 16 Sep 2026 19:05:48 +0200 Subject: [PATCH 3/6] httpd: give the session cookie a Max-Age matching the timeout The session cookie carried neither Expires nor Max-Age, so browsers that clean up session cookies aggressively (LibreWolf, for one) may drop it while the session is still valid. Send Max-Age=session_timeout so the declared lifetime matches the timeout already enforced by the firmware. itoa16_html() only converted four digits, so extend it to five to print timeouts up to 65535. That also fixes information.json, which used the same helper and reported wrong values from 10000 s up. --- httpd/httpd.c | 2 ++ httpd/page_impl.c | 6 ++++-- httpd/page_impl.h | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index fb9c678f..c01b0650 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -668,6 +668,8 @@ static void run_login_body(__xdata uint8_t *body) "Set-Cookie: session="); for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++) outbuf[slen++] = session_id[i]; + slen += strtox(outbuf + slen, "; Max-Age="); + itoa16_html(session_timeout); slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); } else { dbg_string("Password invalid!\n"); diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 6011f80a..6000fd26 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -100,12 +100,14 @@ void itoa_html(uint8_t v) char_to_html('0' + (v % 10)); } -void itoa16_html(uint16_t v) /* sufficient for VLAN IDs (max 4094) */ +void itoa16_html(uint16_t v) /* up to five digits (65535) */ { uint8_t print_zeros = 0; uint8_t d; - d = v / 1000; + d = v / 10000; if (d) { char_to_html('0' + d); print_zeros = 1; } + d = (v / 1000) % 10; + if (d || print_zeros) { char_to_html('0' + d); print_zeros = 1; } d = (v / 100) % 10; if (d || print_zeros) { char_to_html('0' + d); print_zeros = 1; } d = (v / 10) % 10; diff --git a/httpd/page_impl.h b/httpd/page_impl.h index cad4cd6f..22a9f259 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -5,6 +5,8 @@ extern __code uint8_t * __code HTTP_RESPONCE_TXT; +void itoa16_html(uint16_t v); + bool send_counters(uint8_t phys_port); void send_status(void); void send_vlan(uint16_t vlan); From 2fc08f40d259ae0c6af7f970c10c890e350e05fd Mon Sep 17 00:00:00 2001 From: gnacho Date: Fri, 25 Sep 2026 23:22:35 +0200 Subject: [PATCH 4/6] httpd: drop Max-Age from the session cookie The server timeout is an inactivity timeout: scan_header() refreshes last_session_use on every authenticated request. Max-Age counts from login instead, and the cookie is only set there, so the browser deletes it session_timeout seconds after logging in however busy the session is. Leave expiry to the server, which already handles it. The five-digit itoa16_html() stays: information.json must print timeouts from 10000 to 65535 correctly. --- httpd/httpd.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 5d9cb2cd..c4ac485a 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -690,8 +690,6 @@ static void run_login_body(__xdata uint8_t *body) "Set-Cookie: session="); for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++) outbuf[slen++] = session_id[i]; - slen += strtox(outbuf + slen, "; Max-Age="); - itoa16_html(session_timeout); slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); } else { dbg_string("Password invalid!\n"); From 4a9137387c613efcd8e0400153dd3a0a8fb5a3af Mon Sep 17 00:00:00 2001 From: gnacho Date: Fri, 25 Sep 2026 23:22:35 +0200 Subject: [PATCH 5/6] test: define session_timeout in the host harness test_port_tables and test_page_json link httpd/page_impl.c unmodified, which now references session_timeout. Define it in support.c next to the other firmware globals so the tests link again on current main. --- test/support.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support.c b/test/support.c index 1270554f..191f8a5a 100644 --- a/test/support.c +++ b/test/support.c @@ -27,6 +27,7 @@ uint8_t cmd_available; uint8_t err_status; uint8_t cmd_history[CMD_HISTORY_SIZE]; uint16_t cmd_history_ptr; +__xdata uint16_t session_timeout = 200; /* httpd global, referenced by page_impl.c */ /* editor-internal read pointer (defined in cmd_editor.c, external linkage) */ extern __xdata uint8_t l; From 423886dc54d2d463c8c7cf898fa537a2c07e5347 Mon Sep 17 00:00:00 2001 From: gnacho Date: Fri, 25 Sep 2026 23:22:35 +0200 Subject: [PATCH 6/6] web: keep service labels unindented and add the es session strings Restore the sy_igmp and sy_services_note lines to their original indentation so they do not show up as changed in the diff; the new session timeout strings move to their own lines. Also add the es strings, which were missing after the merge with main. --- html/app.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/html/app.js b/html/app.js index 22a884cb..527075c9 100644 --- a/html/app.js +++ b/html/app.js @@ -71,9 +71,9 @@ bw_title:"Bandwidth limits",bw_h:"Mbit/s, 0.016-10000",bw_in:"Ingress limit",bw_ bw_exceed:"When exceeded",bw_fc:"Flow control",bw_drop:"Drop", bw_in_err:"Ingress limit must be 0.016-10000 Mbit/s",bw_out_err:"Egress limit must be 0.016-10000 Mbit/s", sy_network:"Network",sy_dhcp:"Use DHCP",sy_dhcp_t:"Request address via DHCP",sy_services:"Services", - sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port",sy_sesstmo:"Session timeout (s)", - sy_services_note:"Service state reflects the startup config; runtime state is not readable.", - sy_sesstmo_err:"Session timeout must be 1-65535 s", +sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port", +sy_sesstmo:"Session timeout (s)",sy_sesstmo_err:"Session timeout must be 1-65535 s", +sy_services_note:"Service state reflects the startup config; runtime state is not readable.", sy_password:"Admin password",sy_newpw:"New password",sy_repeat:"Repeat",sy_pwapply:"Change password", sy_pw_note:"Takes effect immediately; save to flash to keep it after reboot.",sy_console:"Console", sy_cmd:"CLI command...",sy_send:"Send",sy_startup:"Startup configuration",sy_replayed:"replayed on every boot", @@ -184,9 +184,9 @@ bw_title:"帯域制限",bw_h:"Mbit/s、0.016〜10000",bw_in:"入力制限",bw_ou bw_exceed:"超過時の動作",bw_fc:"フロー制御",bw_drop:"破棄", bw_in_err:"入力制限は 0.016〜10000 Mbit/s の範囲で指定してください",bw_out_err:"出力制限は 0.016〜10000 Mbit/s の範囲で指定してください", sy_network:"ネットワーク",sy_dhcp:"DHCP を使用",sy_dhcp_t:"DHCP でアドレスを取得",sy_services:"サービス", - sy_igmp:"IGMP スヌーピング",sy_sysip:"サーバー IP",sy_server:"サーバー",sy_port:"ポート",sy_sesstmo:"セッションタイムアウト (秒)", - sy_services_note:"サービスの状態は起動設定を反映しています。実行時の状態は読み取れません。", - sy_sesstmo_err:"セッションタイムアウトは 1〜65535 秒で指定してください", +sy_igmp:"IGMP スヌーピング",sy_sysip:"サーバー IP",sy_server:"サーバー",sy_port:"ポート", +sy_sesstmo:"セッションタイムアウト (秒)",sy_sesstmo_err:"セッションタイムアウトは 1〜65535 秒で指定してください", +sy_services_note:"サービスの状態は起動設定を反映しています。実行時の状態は読み取れません。", sy_password:"管理者パスワード",sy_newpw:"新しいパスワード",sy_repeat:"再入力",sy_pwapply:"パスワードを変更", sy_pw_note:"即時に反映されます。再起動後も保持するにはフラッシュに保存してください。",sy_console:"コンソール", sy_cmd:"CLI コマンド...",sy_send:"送信",sy_startup:"起動設定",sy_replayed:"起動のたびに再実行されます", @@ -297,9 +297,9 @@ bw_title:"带宽限制",bw_h:"Mbit/s,0.016-10000",bw_in:"入方向限速",bw_o bw_exceed:"超限动作",bw_fc:"流量控制",bw_drop:"丢弃", bw_in_err:"入方向限速范围为 0.016-10000 Mbit/s",bw_out_err:"出方向限速范围为 0.016-10000 Mbit/s", sy_network:"网络",sy_dhcp:"使用 DHCP",sy_dhcp_t:"通过 DHCP 获取地址",sy_services:"服务", - sy_igmp:"IGMP 侦听",sy_sysip:"服务器 IP",sy_server:"服务器",sy_port:"端口",sy_sesstmo:"会话超时(秒)", - sy_services_note:"服务状态反映启动配置;运行时状态无法读取。", - sy_sesstmo_err:"会话超时必须为 1-65535 秒", +sy_igmp:"IGMP 侦听",sy_sysip:"服务器 IP",sy_server:"服务器",sy_port:"端口", +sy_sesstmo:"会话超时(秒)",sy_sesstmo_err:"会话超时必须为 1-65535 秒", +sy_services_note:"服务状态反映启动配置;运行时状态无法读取。", sy_password:"管理员密码",sy_newpw:"新密码",sy_repeat:"重复输入",sy_pwapply:"修改密码", sy_pw_note:"立即生效;如需重启后保留请保存到 Flash。",sy_console:"控制台", sy_cmd:"CLI 命令...",sy_send:"发送",sy_startup:"启动配置",sy_replayed:"每次启动时重新执行", @@ -411,6 +411,7 @@ bw_exceed:"Al excederse",bw_fc:"Control de flujo",bw_drop:"Descartar", bw_in_err:"El límite de entrada debe estar entre 0.016 y 10000 Mbit/s",bw_out_err:"El límite de salida debe estar entre 0.016 y 10000 Mbit/s", sy_network:"Red",sy_dhcp:"Usar DHCP",sy_dhcp_t:"Solicitar dirección mediante DHCP",sy_services:"Servicios", sy_igmp:"IGMP snooping",sy_sysip:"IP del servidor",sy_server:"Servidor",sy_port:"Puerto", +sy_sesstmo:"Tiempo de sesión (s)",sy_sesstmo_err:"El tiempo de sesión debe estar entre 1 y 65535 s", sy_services_note:"El estado del servicio refleja la configuración de inicio; el estado en ejecución no es legible.", sy_password:"Contraseña de administrador",sy_newpw:"Nueva contraseña",sy_repeat:"Repetir",sy_pwapply:"Cambiar contraseña", sy_pw_note:"Toma efecto inmediatamente; guarda en flash para conservarla tras el reinicio.",sy_console:"Consola",