diff options
Diffstat (limited to 'stacks/web/share/reverse-proxy/docroot/max25-websocket/index.php')
| -rw-r--r-- | stacks/web/share/reverse-proxy/docroot/max25-websocket/index.php | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/stacks/web/share/reverse-proxy/docroot/max25-websocket/index.php b/stacks/web/share/reverse-proxy/docroot/max25-websocket/index.php new file mode 100644 index 0000000..9876c61 --- /dev/null +++ b/stacks/web/share/reverse-proxy/docroot/max25-websocket/index.php @@ -0,0 +1,180 @@ +<?php +/** + * MAX25 browser terminal — remote operator UI (M25/1 via WebSocket proxy). + */ +require_once __DIR__ . '/config.php'; + +$base = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/'); +$ws_path = ($base !== '' ? $base : '/max25-websocket') . '/ws'; +$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'wss' : 'ws'; +$host = $_SERVER['HTTP_HOST'] ?? 'localhost'; +$dev_ws = getenv('MAX25_DEV_WS_URL'); +$ws_url = ($dev_ws !== false && $dev_ws !== '') ? $dev_ws : ($scheme . '://' . $host . $ws_path); +$session_defaults = max25_web_session_defaults(); +?><!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>MAX25 Terminal</title> + <style> + html, body { height: 100%; margin: 0; } + body { + display: flex; + flex-direction: column; + background: #0a0a12; + color: #c8d0d8; + font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace; + font-size: 14px; + } + #topbar { + flex: none; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: .5rem 1rem; + padding: .45rem .75rem; + background: #12121c; + border-bottom: 1px solid #2a2a38; + z-index: 2; + } + #topbar .brand { font-weight: 600; color: #e8ecf0; margin-right: .25rem; } + #topbar .field { color: #9aa4b2; font-size: .85rem; } + #topbar .field b { color: #dce2ea; font-weight: 500; } + #conn-status { margin-left: auto; font-size: .8rem; color: #8a94a4; } + #menu-wrap { position: relative; } + #menu-btn { + background: #1e1e2e; + border: 1px solid #3a3a4a; + color: #c8d0d8; + font: inherit; + font-size: .85rem; + padding: .25rem .65rem; + border-radius: 4px; + cursor: pointer; + } + #menu-btn:hover, #menu-btn[aria-expanded="true"] { background: #262638; } + #menu-panel { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + min-width: 15rem; + background: #161622; + border: 1px solid #3a3a4a; + border-radius: 4px; + box-shadow: 0 8px 24px rgba(0,0,0,.45); + z-index: 10; + padding: .35rem 0; + } + #menu-panel.open { display: block; } + #menu-panel button { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + color: #c8d0d8; + font: inherit; + font-size: .85rem; + padding: .4rem .85rem; + cursor: pointer; + } + #menu-panel button:hover { background: #222230; } + #menu-panel hr { border: none; border-top: 1px solid #2a2a38; margin: .25rem 0; } + #device-select { + background: #1e1e2e; + border: 1px solid #3a3a4a; + color: #dce2ea; + font: inherit; + font-size: .85rem; + padding: .2rem .45rem; + border-radius: 4px; + max-width: 11rem; + } + #term { + flex: 1; + min-height: 0; + box-sizing: border-box; + width: 100%; + margin: 0; + padding: .75rem 1rem; + white-space: pre-wrap; + overflow-y: auto; + overflow-x: hidden; + } + #input-bar { + flex: none; + display: flex; + width: 100%; + box-sizing: border-box; + border-top: 1px solid #2a2a38; + background: #12121c; + } + #cmd { + flex: 1; + width: 100%; + min-width: 0; + box-sizing: border-box; + border: none; + border-radius: 0; + background: #12121c; + color: #e0e4e8; + font: inherit; + font-size: 1rem; + line-height: 1.4; + padding: .75rem 1rem; + outline: none; + } + #cmd::placeholder { color: #6a7280; } + #cmd:focus { background: #161622; } + #cmd:disabled { opacity: .55; cursor: not-allowed; } + .hint { font-size: .75rem; color: #6a7280; padding: 0 .75rem .35rem; } + </style> +</head> +<body> + <header id="topbar"> + <span class="brand">MAX25</span> + <div id="menu-wrap"> + <button type="button" id="menu-btn" aria-expanded="false" aria-haspopup="true">Menu ▾</button> + <div id="menu-panel" role="menu"> + <button type="button" data-action="callerid">/callerid — Change CALLERID</button> + <button type="button" data-action="callid">/callid — Change CALLID</button> + <button type="button" data-action="status">/status — GET STATUS</button> + <button type="button" data-action="send">/send — Send line</button> + <button type="button" data-action="monitor">/monitor — Toggle MONITOR</button> + <button type="button" data-action="connect">/connect — Connection on/off</button> + <hr> + <button type="button" data-action="devices">/devices — List devices</button> + <button type="button" data-action="ax25_ui">/ax25_ui — Toggle AX.25 UI</button> + <hr> + <button type="button" data-action="help">/help — Commands</button> + </div> + </div> + <span class="field">DEVICE: <b id="hdr-device">-</b></span> + <label class="field" for="device-select">select + <select id="device-select" disabled title="max25d registered devices only"> + <option value="">—</option> + </select> + </label> + <span class="field">CALLERID: <b id="hdr-callerid">-</b></span> + <span class="field">CALLID: <b id="hdr-callid">-</b></span> + <span class="field">ax25-ui: <b id="hdr-ax25">-</b></span> + <span class="field">connected: <b id="hdr-connected">-</b></span> + <span id="conn-status">disconnected</span> + </header> + <pre id="term"></pre> + <form id="input-bar" autocomplete="off"> + <input type="text" id="cmd" disabled + autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" + enterkeyhint="send" + placeholder="Type to SEND — or /help for commands"> + </form> + <div class="hint">One device per session · /device <id> · plain text = SEND · only max25d devices</div> + <script> + window.MAX25_WS_URL = <?php echo json_encode($ws_url, JSON_UNESCAPED_SLASHES); ?>; + window.MAX25_SESSION_DEFAULTS = <?php echo json_encode($session_defaults, JSON_UNESCAPED_SLASHES); ?>; + </script> + <script src="max25-terminal.js"></script> +</body> +</html> |
