Files
marte-debug/Tools/go_web_client/static/index.html
T
2026-05-20 17:21:33 +02:00

413 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MARTe2 Debug Client</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="uplot.min.css">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif;font-size:12px;background:#1e1e2e;color:#cdd6f4}
/* ── layout ── */
#app{display:flex;flex-direction:column;height:100vh}
#toolbar{display:flex;align-items:center;gap:6px;padding:4px 8px;background:#181825;border-bottom:1px solid #313244;flex-shrink:0;flex-wrap:wrap}
#main{display:flex;flex:1;overflow:hidden}
#left-panel{width:240px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid #313244;overflow:hidden}
#center-panel{flex:1;overflow:hidden;display:flex;flex-direction:column;gap:6px;padding:6px}
#right-panel{width:260px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid #313244;overflow:hidden}
#log-panel{height:160px;flex-shrink:0;border-top:1px solid #313244;display:flex;flex-direction:column;overflow:hidden}
/* ── toolbar ── */
.tb-group{display:flex;align-items:center;gap:4px;padding:0 6px;border-right:1px solid #313244}
.tb-group:last-child{border-right:none}
input[type=text],input[type=number]{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 6px;border-radius:4px;width:100px}
input[type=number]{width:60px}
button{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 8px;border-radius:4px;cursor:pointer;white-space:nowrap}
button:hover{background:#45475a}
button.active{background:#89b4fa;color:#1e1e2e;border-color:#89b4fa}
button.danger{background:#f38ba8;color:#1e1e2e;border-color:#f38ba8}
button.warn{background:#fab387;color:#1e1e2e;border-color:#fab387}
button.ok{background:#a6e3a1;color:#1e1e2e;border-color:#a6e3a1}
label{color:#a6adc8;user-select:none}
#conn-status{width:8px;height:8px;border-radius:50%;background:#f38ba8;display:inline-block;flex-shrink:0;vertical-align:middle}
#conn-status.ok{background:#a6e3a1}
#udp-stats{color:#585b70;font-size:11px;margin-left:4px}
/* ── dropdown menus ── */
.menu-wrap{position:relative}
.dropdown{position:absolute;top:calc(100% + 4px);left:0;z-index:200;background:#1e1e2e;border:1px solid #45475a;border-radius:6px;padding:8px;min-width:260px;display:none;flex-direction:column;gap:6px;box-shadow:0 4px 16px rgba(0,0,0,.5)}
.dropdown.open{display:flex}
.menu-sep{border:none;border-top:1px solid #313244;margin:2px 0}
.menu-row{display:flex;gap:6px;align-items:center}
.menu-row input[type=text]{flex:1;min-width:0;width:auto}
.menu-row input[type=number]{width:64px}
.menu-btn-row{display:flex;gap:6px}
.menu-btn-row button{flex:1}
/* ── panels ── */
.panel-header{padding:4px 8px;background:#181825;border-bottom:1px solid #313244;font-weight:600;color:#89b4fa;display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
.panel-search{padding:4px;border-bottom:1px solid #313244;flex-shrink:0}
.panel-search input{width:100%;background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:3px 6px;border-radius:4px}
.panel-body{flex:1;overflow-y:auto}
.panel-toggle{background:transparent;border:none;color:#585b70;cursor:pointer;padding:0 2px;font-size:11px;line-height:1}
.panel-toggle:hover{color:#cdd6f4;background:transparent}
/* ── collapsible panels ── */
#left-panel{transition:width .15s;position:relative}
#left-panel.collapsed{width:0!important;border:none;overflow:hidden}
#right-panel{transition:width .15s;position:relative}
#right-panel.collapsed{width:0!important;border:none;overflow:hidden}
#log-panel{transition:height .15s}
#log-panel.collapsed{height:28px;overflow:hidden}
/* collapse/resize strips */
.panel-strip{width:6px;flex-shrink:0;display:flex;align-items:center;justify-content:center;cursor:col-resize;background:#181825;border:none;color:#585b70;font-size:9px;user-select:none;position:relative}
.panel-strip::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:2px;height:32px;background:#45475a;border-radius:1px;pointer-events:none}
.panel-strip:hover{background:#313244}
.panel-strip:hover::after{background:#89b4fa}
#left-strip{border-right:1px solid #313244}
#right-strip{border-left:1px solid #313244}
/* ── tree ── */
.tree-node{padding:1px 0}
.tree-leaf{display:flex;align-items:center;gap:2px;padding:1px 4px;cursor:default;user-select:none}
.tree-leaf:hover{background:#313244}
.tree-leaf.selected{background:#45475a}
.tree-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tree-class{color:#585b70;font-size:10px;flex-shrink:0}
details>summary{list-style:none;cursor:pointer;padding:1px 4px;display:flex;align-items:center;gap:4px;user-select:none}
details>summary:hover{background:#313244}
details>summary::before{content:'▶';font-size:9px;color:#585b70;width:10px;flex-shrink:0}
details[open]>summary::before{content:'▼'}
details>.children{padding-left:14px}
.tree-btn{background:transparent;border:1px solid #45475a;color:#a6adc8;padding:0 4px;border-radius:3px;cursor:pointer;font-size:10px;line-height:14px}
.tree-btn:hover{background:#45475a;color:#cdd6f4}
.tree-btn.t{border-color:#89b4fa;color:#89b4fa}
.tree-btn.f{border-color:#a6e3a1;color:#a6e3a1}
.tree-btn.b{border-color:#fab387;color:#fab387}
/* ── right panel tabs ── */
.tabs{display:flex;border-bottom:1px solid #313244;flex-shrink:0}
.tab{flex:1;padding:4px;text-align:center;cursor:pointer;color:#585b70;border-bottom:2px solid transparent}
.tab.active{color:#89b4fa;border-bottom-color:#89b4fa}
.tab-content{display:none;flex:1;overflow-y:auto;flex-direction:column}
.tab-content.active{display:flex}
/* ── signal items (right panel) ── */
.sig-item{display:flex;align-items:center;gap:4px;padding:3px 6px;border-bottom:1px solid #181825}
.sig-item:hover{background:#313244}
.sig-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}
.sig-val{color:#a6e3a1;font-size:11px;min-width:60px;text-align:right;font-family:monospace}
.sig-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
/* ── plots ── */
.plot-card{background:#181825;border:1px solid #313244;border-radius:6px;overflow:hidden;flex:1;min-height:160px;display:flex;flex-direction:column}
.plot-card.drop-active{border-color:#89b4fa}
.plot-header{display:flex;align-items:center;gap:6px;padding:4px 8px;background:#11111b;border-bottom:1px solid #313244;flex-shrink:0}
.plot-title{font-weight:600;color:#89b4fa;flex:1}
.plot-series-bar{display:flex;flex-wrap:wrap;gap:4px;padding:3px 8px;background:#11111b;border-bottom:1px solid #313244;flex-shrink:0}
.plot-series-bar:empty{display:none;padding:0;border:none}
.series-chip{display:inline-flex;align-items:center;gap:3px;background:#1e1e2e;border:1px solid #45475a;border-radius:10px;padding:1px 3px 1px 6px;font-size:10px}
.series-chip-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
.series-chip-name{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#cdd6f4}
.series-chip-rm{background:transparent;border:none;color:#585b70;cursor:pointer;padding:0 2px;font-size:12px;line-height:1}
.series-chip-rm:hover{color:#f38ba8;background:transparent}
.plot-drop{flex:1;display:flex;align-items:center;justify-content:center;color:#45475a;border:2px dashed #313244;margin:8px;border-radius:4px}
.plot-drop.over{border-color:#89b4fa;color:#89b4fa}
.uplot-wrap{flex:1;min-height:0;padding:2px 4px 4px;display:flex;flex-direction:column}
/* ── logs ── */
#log-toolbar{display:flex;align-items:center;gap:6px;padding:3px 8px;background:#181825;border-bottom:1px solid #313244;flex-shrink:0}
#log-body{flex:1;overflow-y:auto;font-family:monospace;font-size:11px;padding:2px 0}
.log-line{padding:1px 8px;display:flex;gap:8px}
.log-time{color:#585b70;flex-shrink:0}
.log-lvl{flex-shrink:0;min-width:50px;font-weight:600}
.log-msg{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
.log-line.DEBUG .log-lvl{color:#89b4fa}
.log-line.INFO .log-lvl{color:#a6e3a1}
.log-line.WARNING .log-lvl{color:#fab387}
.log-line.ERROR,.log-line.FatalError,.log-line.FATAL{background:#2d1b1b}
.log-line.ERROR .log-lvl,.log-line.FatalError .log-lvl,.log-line.FATAL .log-lvl{color:#f38ba8}
.log-hidden{display:none}
/* ── dialogs ── */
.dialog-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:100}
.dialog{background:#1e1e2e;border:1px solid #45475a;border-radius:8px;padding:16px;min-width:320px;max-width:500px}
.dialog h3{margin-bottom:12px;color:#89b4fa}
.dialog label{display:block;margin-bottom:4px;color:#a6adc8}
.dialog input,.dialog select,.dialog textarea{width:100%;background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:4px 8px;border-radius:4px;margin-bottom:10px}
.dialog textarea{height:80px;resize:vertical;font-family:monospace}
.dialog .btns{display:flex;gap:8px;justify-content:flex-end;margin-top:4px}
.dialog select option{background:#1e1e2e}
.form-row{display:flex;gap:8px}
.form-row>*{flex:1}
.form-check{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.form-check input[type=checkbox]{width:auto;margin:0}
.form-check label{margin:0;color:#a6adc8}
/* ── step status bar ── */
#step-bar{background:#2d2b45;border-bottom:1px solid #313244;padding:4px 8px;display:none;align-items:center;gap:8px;flex-shrink:0}
#step-bar.visible{display:flex}
/* ── scrollbars ── */
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-track{background:#181825}
::-webkit-scrollbar-thumb{background:#45475a;border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:#585b70}
/* ── misc ── */
.empty-hint{padding:16px;color:#45475a;text-align:center}
.break-item{padding:3px 6px;border-bottom:1px solid #181825;display:flex;align-items:center;gap:4px;font-size:11px}
.break-sig{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#fab387}
.msg-item{padding:3px 6px;border-bottom:1px solid #181825;font-size:11px}
.msg-status{width:14px;text-align:center;flex-shrink:0}
select{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 4px;border-radius:4px}
</style>
</head>
<body>
<div id="app">
<!-- ═══ TOOLBAR ═══ -->
<div id="toolbar">
<!-- Connection menu -->
<div class="menu-wrap tb-group">
<button id="conn-menu-btn" onclick="toggleMenu('conn-dropdown')">
<span id="conn-status"></span> Connection ▾
</button>
<div class="dropdown" id="conn-dropdown">
<div class="menu-row">
<input type="text" id="host" value="127.0.0.1" placeholder="host" style="flex:2">
<input type="number" id="port" value="8080" placeholder="ctrl" style="width:60px">
<button id="btn-connect" onclick="toggleConnect()">Connect</button>
</div>
<div class="menu-row" id="port-manual-row" style="display:none">
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">UDP:</label>
<input type="number" id="udp-port" value="8081" placeholder="udp" style="width:72px">
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">Log:</label>
<input type="number" id="log-port" value="8082" placeholder="log" style="width:72px">
</div>
<div class="menu-row">
<label class="form-check" style="margin:0;font-size:11px">
<input type="checkbox" id="auto-ports" checked onchange="toggleAutoPorts(this.checked)">
<span style="color:#a6adc8">Auto ports (SERVICE_INFO)</span>
</label>
</div>
<hr class="menu-sep">
<div class="menu-btn-row">
<button onclick="discoverCmd()">Discover</button>
<button onclick="treeCmd()">Tree</button>
<button onclick="serviceInfoCmd()">Info</button>
</div>
</div>
</div>
<div class="tb-group">
<button id="btn-pause" onclick="togglePause()">⏸ Pause</button>
<button onclick="openStepDialog()">⚙ Step</button>
</div>
<div class="tb-group">
<button onclick="addPlot()">+ Plot</button>
</div>
<div class="tb-group">
<button onclick="openForceDialog()">⚡ Force</button>
<button onclick="openBreakDialog()">🔴 Break</button>
<button onclick="openMsgDialog()">✉ Msg</button>
</div>
<div class="tb-group" style="border:none;margin-left:auto">
<span id="udp-stats" style="color:#585b70;font-size:11px">0 pkts</span>
</div>
</div>
<!-- ═══ STEP STATUS BAR ═══ -->
<div id="step-bar">
<span>⏹ PAUSED at <b id="paused-gam"></b></span>
<span id="step-remaining"></span>
<select id="step-thread" style="width:120px"></select>
<button onclick="step(1)">Step 1</button>
<button onclick="step(5)">Step 5</button>
<button onclick="step(20)">Step 20</button>
<button class="ok" onclick="togglePause()">▶ Resume</button>
</div>
<!-- ═══ MAIN ═══ -->
<div id="main">
<!-- LEFT: object tree -->
<div id="left-panel">
<div class="panel-header">
<span>Object Tree</span>
<button style="font-size:10px;padding:1px 6px" onclick="sendCmd('TREE')"></button>
</div>
<div class="panel-search"><input id="tree-search" oninput="filterTree(this.value)" placeholder="Search…"></div>
<div class="panel-body" id="tree-body">
<div class="empty-hint">Connect to MARTe2 then click Tree</div>
</div>
</div>
<!-- Left resize/collapse strip -->
<div id="left-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
<!-- CENTER: plots -->
<div id="center-panel">
<div class="empty-hint" id="no-plots-hint">Add a plot panel to begin — drag signals from the tree or traced list</div>
</div>
<!-- Right resize/collapse strip -->
<div id="right-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
<!-- RIGHT: tabs -->
<div id="right-panel">
<div class="tabs">
<div class="tab active" onclick="switchTab('traced')">Traced</div>
<div class="tab" onclick="switchTab('forced')">Forced</div>
<div class="tab" onclick="switchTab('breaks')">Breaks</div>
<div class="tab" onclick="switchTab('msgs')">Msgs</div>
</div>
<div class="tab-content active" id="tab-traced">
<div class="empty-hint" id="no-traced-hint">No signals traced</div>
</div>
<div class="tab-content" id="tab-forced">
<div class="empty-hint" id="no-forced-hint">No signals forced</div>
</div>
<div class="tab-content" id="tab-breaks">
<div class="empty-hint" id="no-breaks-hint">No breakpoints set</div>
</div>
<div class="tab-content" id="tab-msgs">
<div class="empty-hint" id="no-msgs-hint">No messages sent</div>
</div>
</div>
</div>
<!-- ═══ LOGS ═══ -->
<div id="log-panel">
<div id="log-toolbar">
<button class="panel-toggle" title="Collapse logs" onclick="togglePanel('log-panel','▼','▲',this)" id="log-toggle-btn"></button>
<span style="font-weight:600;color:#89b4fa">Logs</span>
<label><input type="checkbox" id="lf-debug" checked onchange="renderLogs()"> Debug</label>
<label><input type="checkbox" id="lf-info" checked onchange="renderLogs()"> Info</label>
<label><input type="checkbox" id="lf-warn" checked onchange="renderLogs()"> Warn</label>
<label><input type="checkbox" id="lf-error" checked onchange="renderLogs()"> Error</label>
<input type="text" id="log-filter" placeholder="Filter…" oninput="renderLogs()" style="width:150px">
<button onclick="logs=[];renderLogs()" style="margin-left:auto">Clear</button>
<label><input type="checkbox" id="log-autoscroll" checked> Auto-scroll</label>
</div>
<div id="log-body"></div>
</div>
</div><!-- #app -->
<!-- ═══ DIALOGS ═══ -->
<!-- Force dialog -->
<div class="dialog-overlay" id="dlg-force" style="display:none" onclick="if(event.target===this)closeDlg('dlg-force')">
<div class="dialog">
<h3>⚡ Force Signal</h3>
<label>Signal</label>
<input id="force-sig" list="force-sig-list" placeholder="Signal path…">
<datalist id="force-sig-list"></datalist>
<label>Value</label>
<input id="force-val" placeholder="e.g. 42">
<div class="btns">
<button onclick="closeDlg('dlg-force')">Cancel</button>
<button class="active" onclick="doForce()">Force</button>
</div>
</div></div>
<!-- Unforce confirm -->
<div class="dialog-overlay" id="dlg-unforce" style="display:none" onclick="if(event.target===this)closeDlg('dlg-unforce')">
<div class="dialog">
<h3>Remove Force</h3>
<p id="unforce-msg" style="margin-bottom:12px;color:#cdd6f4"></p>
<div class="btns">
<button onclick="closeDlg('dlg-unforce')">Cancel</button>
<button class="danger" onclick="doUnforce()">Unforce</button>
</div>
</div></div>
<!-- Break dialog -->
<div class="dialog-overlay" id="dlg-break" style="display:none" onclick="if(event.target===this)closeDlg('dlg-break')">
<div class="dialog">
<h3>🔴 Set Breakpoint</h3>
<label>Signal</label>
<input id="break-sig" list="break-sig-list" placeholder="Signal path…">
<datalist id="break-sig-list"></datalist>
<div class="form-row">
<div>
<label>Operator</label>
<select id="break-op">
<option>></option><option>&gt;=</option>
<option>&lt;</option><option>&lt;=</option>
<option>==</option><option>!=</option>
</select>
</div>
<div>
<label>Threshold</label>
<input id="break-thresh" placeholder="0">
</div>
</div>
<div class="btns">
<button onclick="closeDlg('dlg-break')">Cancel</button>
<button class="active" onclick="doBreak()">Set Break</button>
</div>
</div></div>
<!-- Message dialog -->
<div class="dialog-overlay" id="dlg-msg" style="display:none" onclick="if(event.target===this)closeDlg('dlg-msg')">
<div class="dialog">
<h3>✉ Send Message</h3>
<label>Destination</label>
<input id="msg-dest" list="msg-dest-list" placeholder="e.g. App.Functions.GAM1">
<datalist id="msg-dest-list"></datalist>
<label>Function</label>
<input id="msg-func" placeholder="FunctionName">
<label>Payload (key=value lines)</label>
<textarea id="msg-payload" placeholder="Key = Value&#10;Key2 = Value2"></textarea>
<div class="form-check">
<input type="checkbox" id="msg-wait">
<label for="msg-wait">Wait for reply</label>
</div>
<div class="btns">
<button onclick="closeDlg('dlg-msg')">Cancel</button>
<button class="active" onclick="doSendMsg()">Send</button>
</div>
</div></div>
<!-- Info dialog -->
<div class="dialog-overlay" id="dlg-info" style="display:none" onclick="if(event.target===this)closeDlg('dlg-info')">
<div class="dialog" style="max-width:600px;width:90vw">
<h3 id="info-title">Info</h3>
<pre id="info-body" style="background:#11111b;padding:8px;border-radius:4px;overflow:auto;max-height:400px;font-size:11px;color:#cdd6f4;white-space:pre-wrap"></pre>
<div class="btns" style="margin-top:12px">
<button onclick="closeDlg('dlg-info')">Close</button>
</div>
</div></div>
<!-- Step dialog -->
<div class="dialog-overlay" id="dlg-step" style="display:none" onclick="if(event.target===this)closeDlg('dlg-step')">
<div class="dialog">
<h3>⚙ Step Execution</h3>
<div class="form-row">
<div>
<label>Count</label>
<input type="number" id="step-count" value="1" min="1">
</div>
<div>
<label>Thread (optional)</label>
<input id="step-thread-inp" list="step-thread-list" placeholder="all">
<datalist id="step-thread-list"></datalist>
</div>
</div>
<div class="btns">
<button onclick="closeDlg('dlg-step')">Cancel</button>
<button class="active" onclick="doStep()">Step</button>
</div>
</div></div>
<script src="uplot.min.js"></script>
<script src="app.js"></script>
</body>
</html>