Constrain terminal layout and externalize TLS memory
Improve CSP and favicon handling while preventing terminal resize loops. Configure mbedTLS allocations in external PSRAM and document its security implications.
This commit is contained in:
+38
-16
@@ -25,13 +25,14 @@ static const char s_index_html[] =
|
||||
"--line:#29364a;--text:#e8eef8;--muted:#91a0b5;--accent:#55c2ff;"
|
||||
"--good:#52d68b;--warn:#ffc857;--bad:#ff6b7a;--radius:14px}\n"
|
||||
"*{box-sizing:border-box}\n"
|
||||
"html,body{height:100%;margin:0}\n"
|
||||
"html,body{height:100%;margin:0;overflow:hidden}\n"
|
||||
"body{background:radial-gradient(circle at top left,#142033 0,var(--bg) 42rem);"
|
||||
"color:var(--text);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"
|
||||
"\"Segoe UI\",sans-serif}\n"
|
||||
"button{font:inherit}\n"
|
||||
".page{min-height:100%;max-width:1440px;margin:auto;padding:clamp(14px,2.5vw,32px);"
|
||||
"display:grid;grid-template-rows:auto auto minmax(360px,1fr);gap:16px}\n"
|
||||
".page{height:100%;height:100dvh;min-height:0;max-width:1440px;margin:auto;"
|
||||
"padding:clamp(14px,2.5vw,32px);display:grid;"
|
||||
"grid-template-rows:auto auto minmax(0,1fr);gap:16px}\n"
|
||||
".topbar{display:flex;align-items:center;justify-content:space-between;gap:16px}\n"
|
||||
".brand{display:flex;align-items:center;gap:12px;min-width:0}\n"
|
||||
".logo{width:42px;height:42px;border:1px solid #347ba5;border-radius:12px;"
|
||||
@@ -69,12 +70,13 @@ static const char s_index_html[] =
|
||||
".input-state{margin:0;color:var(--warn);font-size:13px}\n"
|
||||
".input-state[data-enabled=true]{color:var(--good)}\n"
|
||||
".connection-detail{margin:0;color:var(--muted);font-size:12px;min-height:1.45em}\n"
|
||||
".terminal-panel{min-height:0;padding:10px;display:flex;flex-direction:column;overflow:hidden}\n"
|
||||
".terminal-panel{min-width:0;min-height:0;padding:10px;display:flex;flex-direction:column;overflow:hidden}\n"
|
||||
".terminal-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;"
|
||||
"padding:1px 5px 9px;color:var(--muted);font-size:12px}\n"
|
||||
".terminal-title{color:var(--text);font-weight:750;letter-spacing:.02em}\n"
|
||||
"#terminal{flex:1;min-height:0;border-radius:9px;overflow:hidden;background:#080c12;padding:8px}\n"
|
||||
"#terminal .xterm{height:100%}\n"
|
||||
"#terminal{flex:1;min-width:0;min-height:0;border-radius:9px;overflow:hidden;"
|
||||
"background:#080c12;padding:8px}\n"
|
||||
"#terminal .xterm{width:100%;height:100%}\n"
|
||||
"#terminal .xterm-viewport{border-radius:7px}\n"
|
||||
"@media(max-width:850px){.dashboard{grid-template-columns:1fr}.controls{align-items:flex-start}"
|
||||
".status-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}\n"
|
||||
@@ -82,8 +84,9 @@ static const char s_index_html[] =
|
||||
".logo{width:36px;height:36px}.status-grid{padding:10px;gap:8px}"
|
||||
".status-item{padding:9px}.controls{padding:12px}.terminal-panel{padding:7px}"
|
||||
".button-row{display:grid;grid-template-columns:1fr 1fr;width:100%}"
|
||||
".button:last-child{grid-column:1/-1}.page{grid-template-rows:auto auto minmax(420px,1fr)}}\n"
|
||||
".button:last-child{grid-column:1/-1}.page{grid-template-rows:auto auto minmax(0,1fr)}}\n"
|
||||
"</style>\n"
|
||||
"<link rel=\"icon\" href=\"data:,\">\n"
|
||||
"<link rel=\"stylesheet\" href=\"/assets/xterm.css\">\n"
|
||||
"<script defer src=\"/assets/xterm.js\"></script>\n"
|
||||
"<script defer src=\"/assets/addon-fit.js\"></script>\n"
|
||||
@@ -173,6 +176,8 @@ static const char s_app_js[] =
|
||||
"let writerId = 0;\n"
|
||||
"let unloading = false;\n"
|
||||
"let fitFrame = 0;\n"
|
||||
"let lastFitWidth = 0;\n"
|
||||
"let lastFitHeight = 0;\n"
|
||||
"let statusInFlight = false;\n"
|
||||
"let statusTimer = null;\n"
|
||||
"const setBadge = (target, text, tone) => {\n"
|
||||
@@ -329,14 +334,27 @@ static const char s_app_js[] =
|
||||
" reconnectDelay = 1000;\n"
|
||||
" connect();\n"
|
||||
"});\n"
|
||||
"const scheduleFit = () => {\n"
|
||||
" if (fitFrame !== 0) return;\n"
|
||||
" fitFrame = window.requestAnimationFrame(() => {\n"
|
||||
" fitFrame = 0;\n"
|
||||
" try { fitAddon.fit(); } catch (_) {}\n"
|
||||
" });\n"
|
||||
"const fitTerminal = () => {\n"
|
||||
" fitFrame = 0;\n"
|
||||
" const bounds = terminalHost.getBoundingClientRect();\n"
|
||||
" const width = Math.floor(bounds.width);\n"
|
||||
" const height = Math.floor(bounds.height);\n"
|
||||
" if (width < 1 || height < 1 || (width === lastFitWidth && height === lastFitHeight)) return;\n"
|
||||
" lastFitWidth = width;\n"
|
||||
" lastFitHeight = height;\n"
|
||||
" try {\n"
|
||||
" const dimensions = fitAddon.proposeDimensions();\n"
|
||||
" if (dimensions && dimensions.cols > 0 && dimensions.rows > 0 &&\n"
|
||||
" (dimensions.cols !== terminal.cols || dimensions.rows !== terminal.rows)) {\n"
|
||||
" terminal.resize(dimensions.cols, dimensions.rows);\n"
|
||||
" }\n"
|
||||
" } catch (_) {}\n"
|
||||
"};\n"
|
||||
"if ('ResizeObserver' in window) new ResizeObserver(scheduleFit).observe(terminalHost);\n"
|
||||
"const scheduleFit = () => {\n"
|
||||
" if (fitFrame === 0) fitFrame = window.requestAnimationFrame(fitTerminal);\n"
|
||||
"};\n"
|
||||
"const resizeObserver = 'ResizeObserver' in window ? new ResizeObserver(scheduleFit) : null;\n"
|
||||
"if (resizeObserver !== null) resizeObserver.observe(terminalHost);\n"
|
||||
"window.addEventListener('resize', scheduleFit);\n"
|
||||
"const textValue = (value, fallback) => typeof value === 'string' && value.length > 0 ? value : fallback;\n"
|
||||
"const updateStatus = (status) => {\n"
|
||||
@@ -397,6 +415,9 @@ static const char s_app_js[] =
|
||||
" ++connectionGeneration;\n"
|
||||
" clearReconnectTimer();\n"
|
||||
" if (statusTimer !== null) window.clearInterval(statusTimer);\n"
|
||||
" if (fitFrame !== 0) window.cancelAnimationFrame(fitFrame);\n"
|
||||
" if (resizeObserver !== null) resizeObserver.disconnect();\n"
|
||||
" window.removeEventListener('resize', scheduleFit);\n"
|
||||
" if (ticketAbort !== null) ticketAbort.abort();\n"
|
||||
" if (socket !== null) socket.close();\n"
|
||||
" socket = null;\n"
|
||||
@@ -484,8 +505,9 @@ static esp_err_t set_response_headers(httpd_req_t *request,
|
||||
if (result == ESP_OK && response->content_security_policy) {
|
||||
result = httpd_resp_set_hdr(
|
||||
request, "Content-Security-Policy",
|
||||
"default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; "
|
||||
"connect-src 'self'; base-uri 'none'; form-action 'none'; "
|
||||
"default-src 'none'; script-src 'self'; script-src-elem 'self'; "
|
||||
"style-src 'self' 'unsafe-inline'; img-src data:; connect-src 'self'; "
|
||||
"base-uri 'none'; form-action 'none'; "
|
||||
"frame-ancestors 'none'");
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user