mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 18:13:46 +00:00
149 lines
7.8 KiB
HTML
149 lines
7.8 KiB
HTML
{{define "title"}}Console · {{.Repeater.Name}} · MeshTender{{end}}
|
|
{{define "header"}}
|
|
<div class="row g-2 align-items-center">
|
|
<div class="col">
|
|
<div class="page-pretitle">Console</div>
|
|
<h1 class="page-title fs-1">{{.Repeater.Name}}</h1>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{define "content"}}
|
|
<div class="alert alert-info" role="alert">
|
|
Connect a MeshCore KISS modem and send commands to this repeater. You can run the commands in the
|
|
Allowed commands list; the server enforces what you're allowed to send.
|
|
</div>
|
|
|
|
{{/* Confirm/location prompts. Connecting the modem sends a signed login that
|
|
confirms access (server-side). These reflect the state at page load; console.js
|
|
then updates them live — hiding the confirm prompt and revealing the location
|
|
prompt once a "confirmed" status arrives, and hiding the location prompt once a
|
|
"location" status arrives. The location prompt is always rendered when no
|
|
location is stored (hidden until the repeater is confirmed with admin) so the
|
|
swap needs no new markup. The flex row is nested so the [hidden] attribute wins
|
|
(Bootstrap's .d-flex is display:flex !important and would otherwise show it). */}}
|
|
{{if not .Repeater.Confirmed}}
|
|
<div class="alert alert-warning" role="alert" data-testid="confirm-banner">
|
|
This repeater hasn't been confirmed yet. Connect your modem below — MeshTender will send a signed login
|
|
to verify it has access. Confirming happens automatically as soon as the repeater replies.
|
|
</div>
|
|
{{end}}
|
|
{{if not .Repeater.Latitude}}
|
|
<div class="alert alert-info" role="alert" data-testid="location-banner"{{if not (and .Repeater.Confirmed .Repeater.IsAdmin)}} hidden{{end}}>
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div class="me-3">This repeater's location isn't known yet.</div>
|
|
<button type="button" class="btn btn-sm btn-primary flex-shrink-0" data-fetch-location data-testid="fetch-location">{{template "icon-map-pin" "me-1"}}Fetch location</button>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if and .Repeater.Confirmed .Repeater.AccessKnown (not .Repeater.IsAdmin)}}
|
|
<div class="alert alert-warning" role="alert" data-testid="guest-banner">
|
|
MeshTender only has <strong>guest</strong> access here. Guest is available to anyone with a blank password,
|
|
so there's no point operating at this level — re-run <code>setperm <your key> 3</code> on the repeater
|
|
to grant admin, then reconnect.
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="row row-cards">
|
|
<div class="col-12 col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header"><h2 class="card-title">Console</h2></div>
|
|
<div class="card-body">
|
|
<div id="unsupported" class="alert alert-danger" role="alert" hidden>
|
|
This browser does not support WebSerial. Use Chrome, Edge, or another Chromium-based browser over HTTPS or on localhost.
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label" for="path">Path <span class="form-label-description">optional</span></label>
|
|
<input type="text" class="form-control font-monospace" id="path" placeholder="e.g. 11, 22, 33" autocomplete="off" spellcheck="false">
|
|
<small class="form-hint">Hex hops from your modem to the repeater, in order, same length each (e.g. <code>11, 22, 33</code>). Leave blank to reach it by flood.</small>
|
|
</div>
|
|
<div class="btn-list">
|
|
<button id="connect" class="btn btn-primary" type="button">{{template "icon-plug" "me-1"}}Connect modem</button>
|
|
{{if .ShowConfig}}
|
|
<button type="button" class="btn btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#config-panel" aria-expanded="false" aria-controls="config-panel" data-testid="apply-config">{{template "icon-settings" "me-1"}}Apply organization configuration…</button>
|
|
{{end}}
|
|
</div>
|
|
<ul id="log" class="evlog console mt-3" role="log" aria-live="polite" aria-label="Console activity"></ul>
|
|
<form id="cmdform" class="input-group mt-3" autocomplete="off" hidden>
|
|
<input type="text" id="cmdinput" class="form-control font-monospace" placeholder="type a command, e.g. ver" aria-label="Command to send">
|
|
<button type="submit" id="cmdsend" class="btn btn-primary">Send</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{{if .ShowConfig}}
|
|
<div class="collapse mt-3" id="config-panel"
|
|
data-config-url="/repeaters/{{.Repeater.PublicID}}/config.json"
|
|
data-location-url="/repeaters/{{.Repeater.PublicID}}/location">
|
|
<div class="card">
|
|
<div class="card-header"><h2 class="card-title">Apply organization configuration</h2></div>
|
|
<div class="card-body">
|
|
<p class="text-secondary">
|
|
Pick an organization and profile to see its recommended configuration for this repeater. Every command is
|
|
listed; the ones you're allowed to send show a Run button. Connect the modem to run them — results appear in
|
|
the console log above and next to each command.
|
|
</p>
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="cfg-org">Organization</label>
|
|
<select id="cfg-org" class="form-select" data-cfg="org"></select>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="cfg-profile">Profile</label>
|
|
<select id="cfg-profile" class="form-select" data-cfg="profile"></select>
|
|
</div>
|
|
</div>
|
|
<div data-cfg="location"></div>
|
|
<div id="config-map" data-cfg="map" style="height:220px;display:none" class="mb-3 rounded" role="region" aria-label="Pick the repeater's location"></div>
|
|
<div data-cfg="commands" data-testid="config-commands"></div>
|
|
</div>
|
|
<div class="card-footer d-flex align-items-center">
|
|
<span data-cfg="hint" class="text-secondary small" data-testid="config-hint"></span>
|
|
<div class="btn-list ms-auto">
|
|
<button type="button" class="btn btn-primary" data-cfg="connect" data-testid="config-connect">{{template "icon-plug" "me-1"}}<span data-cfg="connect-label">Connect modem</span></button>
|
|
<button type="button" class="btn btn-primary" data-cfg="run-all" data-testid="config-run-all">Run all</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
<div class="col-12 col-lg-4">
|
|
<div class="card" data-testid="allowed-commands">
|
|
<div class="card-header"><h2 class="card-title">Allowed commands</h2></div>
|
|
<div class="card-body">
|
|
{{if .Commands}}
|
|
<div class="d-flex flex-wrap gap-1" style="max-height:360px;overflow-y:auto">
|
|
{{range .Commands}}
|
|
<button type="button" class="chip btn btn-sm{{if .Risky}} btn-outline-danger{{end}}" data-template="{{.Template}}" title="{{.Args}}">{{.Template}}</button>
|
|
{{end}}
|
|
</div>
|
|
{{else}}
|
|
<p class="text-secondary mb-0">No commands allowed here yet.</p>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 d-flex align-items-center">
|
|
<a class="back-link" href="/">{{template "icon-arrow-left" "me-1"}}Back to dashboard</a>
|
|
{{if .Debug}}
|
|
<a class="text-secondary ms-2" href="/repeaters/{{.Repeater.PublicID}}/console">Disable debug</a>
|
|
{{else}}
|
|
<a class="text-secondary ms-2" href="/repeaters/{{.Repeater.PublicID}}/console?debug=1">Debug: show raw frames</a>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if .ShowConfig}}
|
|
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
|
|
<script src="{{ asset "/static/leaflet.js" }}"></script>
|
|
<script src="{{ asset "/static/regionmap.js" }}"></script>
|
|
<script src="{{ asset "/static/console-config.js" }}"></script>
|
|
{{end}}
|
|
|
|
<script nonce="{{.Nonce}}">
|
|
window.MESHTENDER_WS = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/repeaters/{{.Repeater.PublicID}}/console/ws{{if .Debug}}?debug=1{{end}}";
|
|
</script>
|
|
<script src="{{ asset "/static/console.js" }}"></script>
|
|
{{end}}
|