tyo-mq
Version:
Distributed Message Pub/Sub Service with socket.io
250 lines (224 loc) • 10.7 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TYO MQ Manager</title>
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="stylesheet" href="manager.css">
</head>
<body>
<main class="shell">
<header class="topbar">
<div>
<h1>TYO MQ Manager</h1>
<p id="status">Not connected</p>
</div>
<button id="connect-btn" type="button">Connect</button>
</header>
<section class="panel connection">
<div class="field grow">
<label for="server-url">Server URL</label>
<input id="server-url" type="url" placeholder="https://your-mq-server.example">
</div>
<div class="field token">
<label for="admin-token">Admin token</label>
<input id="admin-token" type="password" autocomplete="off">
</div>
<button id="clear-token-btn" class="secondary" type="button">Clear token</button>
<button id="show-settings-btn" type="button">Refresh</button>
<button id="reload-settings-btn" class="secondary" type="button" title="Force the server to re-read its settings file from disk">Reload from disk</button>
</section>
<nav class="tabs" aria-label="Manager sections">
<button id="main-tab-btn" class="tab-button active" type="button" data-tab="main">Main</button>
<button id="persistence-tab-btn" class="tab-button" type="button" data-tab="persistence">Persistence</button>
<button id="observability-tab-btn" class="tab-button" type="button" data-tab="observability">Observability</button>
</nav>
<div id="main-tab" class="tab-panel active">
<section class="grid">
<div class="panel">
<div class="section-heading">
<h2>Realms</h2>
<button id="refresh-realms-btn" type="button">Refresh</button>
</div>
<form id="add-realm-form" class="inline-form">
<input id="new-realm" type="text" placeholder="realm name" autocomplete="off">
<label class="check">
<input id="new-realm-required" type="checkbox" checked>
Require auth
</label>
<button type="submit">Add</button>
</form>
<form id="rename-realm-form" class="inline-form">
<input id="rename-from" type="text" placeholder="current realm" autocomplete="off">
<input id="rename-to" type="text" placeholder="new realm" autocomplete="off">
<button type="submit">Rename</button>
</form>
<div id="realms-list" class="list empty">No realms loaded</div>
</div>
<div class="panel">
<div class="section-heading">
<h2>Authorization Requests</h2>
<div class="button-group">
<button id="refresh-request-btn" class="secondary" type="button">Refresh</button>
<button id="next-request-btn" type="button">Next</button>
</div>
</div>
<div class="field">
<label for="request-realm-filter">Realm filter</label>
<select id="request-realm-filter">
<option value="">Any realm</option>
</select>
<input id="request-realm-custom" class="hidden-field" type="text" placeholder="Custom realm" autocomplete="off">
</div>
<div id="request-card" class="request-card empty">No pending request loaded</div>
<div class="decision-row">
<select id="approve-role">
<option value="both">both</option>
<option value="producer">producer</option>
<option value="consumer">consumer</option>
</select>
<button id="approve-request-btn" type="button">Approve</button>
</div>
<div class="decision-row">
<input id="reject-reason" type="text" placeholder="Reject reason" autocomplete="off">
<button id="reject-request-btn" type="button">Reject</button>
</div>
</div>
<div class="panel">
<div class="section-heading">
<h2>Authorized Clients</h2>
<button id="refresh-tokens-btn" type="button">Refresh</button>
</div>
<div id="tokens-list" class="list empty">No tokens loaded</div>
</div>
<div class="panel">
<div class="section-heading">
<h2>External Auth</h2>
</div>
<p class="panel-note">
Tokens no local credential recognizes (realm JWTs, configured tokens)
are validated by POSTing them to this URL with the
<code>X-MQ-Auth-Secret</code> header. Local credentials are always
checked first, so enabling this cannot break existing clients.
</p>
<form id="external-auth-form" class="stacked-form">
<div class="field">
<label for="external-auth-url">Validation URL (auth_url)</label>
<input id="external-auth-url" type="url" placeholder="https://tyoman.tyo.com.au/api/v1/mq-auth" autocomplete="off">
</div>
<div class="field">
<label for="external-auth-secret">Callback secret (auth_secret)</label>
<input id="external-auth-secret" type="password" autocomplete="off" placeholder="not set">
</div>
<div class="form-actions">
<button type="submit">Save</button>
<button id="disable-external-auth-btn" class="secondary" type="button">Disable</button>
</div>
</form>
</div>
<div class="panel">
<div class="section-heading">
<h2>Management API Tokens</h2>
</div>
<p class="panel-note">
Bearer tokens for the HTTP management API
(<code>POST /api/realms</code>). Each token can only create or
rotate realms under its prefix. Tokens are generated in the
browser and shown once — copy immediately; the server keeps only
a hash.
</p>
<form id="add-mgmt-token-form" class="inline-form">
<input id="mgmt-token-prefix" type="text" placeholder="realm prefix, e.g. realm:prefix:" autocomplete="off">
<button type="submit">Generate</button>
</form>
<div id="mgmt-token-reveal" class="hidden-field"></div>
<div id="mgmt-tokens-list" class="list empty">No management tokens loaded</div>
</div>
</section>
<section class="panel">
<div class="section-heading">
<h2>Settings</h2>
<button id="copy-settings-btn" type="button">Copy</button>
</div>
<pre id="settings-output">{}</pre>
</section>
</div>
<div id="persistence-tab" class="tab-panel">
<section class="panel">
<div class="section-heading">
<h2>Persistence</h2>
<button id="refresh-persistence-btn" type="button">Refresh</button>
</div>
<form id="persistence-form" class="persistence-form">
<div class="field">
<label for="storage-backend">Storage backend</label>
<select id="storage-backend">
<option value="memory">memory</option>
<option value="sqlite">sqlite</option>
<option value="redis">redis</option>
<option value="custom">custom store</option>
</select>
</div>
<div class="field">
<label for="storage-default-ttl">Default TTL seconds</label>
<input id="storage-default-ttl" type="number" min="0" step="1" placeholder="86400">
</div>
<div id="sqlite-options" class="backend-options">
<div class="field">
<label for="sqlite-filename">SQLite filename</label>
<input id="sqlite-filename" type="text" placeholder="/data/tyo-mq.sqlite" autocomplete="off">
</div>
</div>
<div id="redis-options" class="backend-options">
<div class="field">
<label for="redis-url">Redis URL</label>
<input id="redis-url" type="text" placeholder="redis://redis:6379/0" autocomplete="off">
</div>
<div class="field">
<label for="redis-prefix">Redis key prefix</label>
<input id="redis-prefix" type="text" placeholder="tyo-mq" autocomplete="off">
</div>
</div>
<div id="custom-options" class="backend-options">
<div class="field">
<label for="custom-module">Custom store module path</label>
<input id="custom-module" type="text" placeholder="./my-store.js" autocomplete="off">
</div>
<div class="field full">
<label for="custom-options-json">Custom storage_options JSON</label>
<textarea id="custom-options-json" rows="8" spellcheck="false" placeholder="{ }"></textarea>
</div>
</div>
<div class="form-actions">
<button id="save-persistence-btn" type="submit">Apply persistence</button>
</div>
</form>
</section>
</div>
<div id="observability-tab" class="tab-panel">
<section class="panel">
<div class="section-heading">
<h2>Live stats</h2>
<button id="refresh-stats-btn" type="button">Refresh</button>
</div>
<pre id="stats-output">{}</pre>
</section>
<section class="panel">
<div class="section-heading">
<h2>Dead-letter queue</h2>
<button id="refresh-dlq-btn" type="button">Refresh</button>
</div>
<div class="inline-form">
<input id="dlq-realm" type="text" placeholder="realm filter (blank for all)" autocomplete="off">
</div>
<div id="dlq-list" class="list empty">No DLQ entries loaded</div>
</section>
</div>
</main>
<script src="tyo-mq-client.js"></script>
<script src="manager-ui.js"></script>
</body>
</html>