functionality updates

This commit is contained in:
2026-09-15 17:13:32 +06:00
parent bd7ac94e74
commit b8d3a44d22
11 changed files with 338 additions and 533 deletions

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoAP Hello — Test Console</title>
<title>CoAP Hello — UDP Reference</title>
<style>
:root {
color-scheme: light dark;
@@ -23,112 +23,144 @@
margin: 0; padding: 2rem 1.25rem; background: var(--bg); color: var(--fg);
font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
main { max-width: 640px; margin: 0 auto; }
main { max-width: 680px; margin: 0 auto; }
h1 { font-size: 1.4rem; margin: 0 0 .35rem; letter-spacing: -.01em; }
.sub { color: var(--muted); margin: 0 0 1.75rem; font-size: .9rem; }
.card {
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; padding: 1.25rem; margin-bottom: 1rem;
}
h2 { font-size: .82rem; text-transform: uppercase; letter-spacing: .06em;
color: var(--muted); margin: 0 0 .7rem; }
label { display: block; font-weight: 600; font-size: .82rem; margin-bottom: .4rem; }
input {
width: 100%; padding: .6rem .7rem; font: inherit; border-radius: 7px;
border: 1px solid var(--border); background: var(--bg); color: var(--fg);
}
input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.row { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .85rem; }
button {
font: inherit; font-weight: 600; padding: .55rem 1rem; border-radius: 7px;
border: 1px solid var(--border); background: var(--bg); color: var(--fg); cursor: pointer;
}
button:hover { border-color: var(--accent); color: var(--accent); }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover { opacity: .88; color: #fff; }
.hint { color: var(--muted); font-size: .82rem; margin: .8rem 0 0; }
.cmd { display: flex; gap: .5rem; align-items: stretch; margin-top: .55rem; }
.cmd pre { flex: 1; margin: 0; }
pre {
background: var(--code-bg); border: 1px solid var(--border); border-radius: 7px;
padding: .8rem; overflow-x: auto; margin: 0;
padding: .7rem .8rem; overflow-x: auto; margin: 0;
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.status { font-weight: 600; }
.status.ok { color: var(--ok); }
.status.err { color: var(--err); }
h2 { font-size: .82rem; text-transform: uppercase; letter-spacing: .06em;
color: var(--muted); margin: 0 0 .7rem; }
.hint { color: var(--muted); font-size: .82rem; margin: .8rem 0 0; }
button {
font: inherit; font-size: .8rem; font-weight: 600; padding: .35rem .7rem;
border-radius: 7px; border: 1px solid var(--border);
background: var(--bg); color: var(--muted); cursor: pointer; white-space: nowrap;
}
button:hover { border-color: var(--accent); color: var(--accent); }
button.copied { color: var(--ok); border-color: var(--ok); }
.what { font-size: .8rem; color: var(--muted); margin: 1rem 0 .1rem; }
.what:first-of-type { margin-top: 0; }
table { width: 100%; border-collapse: collapse; font-size: .87rem; }
th, td { text-align: left; padding: .4rem .5rem; border-bottom: 1px solid var(--border); }
th { font-size: .74rem; text-transform: uppercase; letter-spacing: .05em;
color: var(--muted); font-weight: 600; }
tr:last-child td { border-bottom: none; }
td.code { font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
.ok { color: var(--ok); font-weight: 600; }
.err { color: var(--err); font-weight: 600; }
code { background: var(--code-bg); padding: .1em .35em; border-radius: 4px;
font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; }
a { color: var(--accent); }
</style>
</head>
<body>
<main>
<h1>CoAP Hello — Test Console</h1>
<p class="sub">This page talks HTTP to a local bridge, which reissues each
call as CoAP over UDP and shows you the raw response code.</p>
<h1>CoAP Hello — UDP Reference</h1>
<p class="sub">This server speaks CoAP over UDP only, so there is nothing here
for a browser to call. Type a name and copy the command for the client you
use — the page is static and works straight off disk.</p>
<div class="card">
<label for="name">Name</label>
<input id="name" value="Imran" autocomplete="off" placeholder="leave blank for &quot;World&quot;">
<div class="row">
<button class="primary" data-act="get">GET /hello</button>
<button data-act="post">POST /hello</button>
<button data-act="json">POST (JSON body)</button>
<button data-act="health">GET /health</button>
<button data-act="notfound">GET /nope</button>
</div>
<p class="hint">Or hit an endpoint directly:
<a href="/hello?name=Imran">/hello?name=Imran</a> ·
<a href="/health">/health</a></p>
<input id="name" value="Imran" autocomplete="off" spellcheck="false"
placeholder="leave blank for &quot;World&quot;">
<p class="what">Bundled client — <code>cargo build --release</code> first</p>
<div class="cmd"><pre id="c-get"></pre><button data-copy="c-get">Copy</button></div>
<div class="cmd"><pre id="c-post"></pre><button data-copy="c-post">Copy</button></div>
<div class="cmd"><pre id="c-json"></pre><button data-copy="c-json">Copy</button></div>
<div class="cmd"><pre id="c-health"></pre><button data-copy="c-health">Copy</button></div>
<p class="what">Any other CoAP client, e.g. <a href="https://www.npmjs.com/package/coap-cli">coap-cli</a></p>
<div class="cmd"><pre id="c-cli"></pre><button data-copy="c-cli">Copy</button></div>
<p class="hint">Set <code>COAP_HOST</code> / <code>COAP_PORT</code> to point at
another server; both default to <code>127.0.0.1:5683</code>.</p>
</div>
<div class="card">
<h2>Response</h2>
<p id="status" class="status">—</p>
<pre id="out">Pick an action above.</pre>
<h2>Routes</h2>
<table>
<thead><tr><th>Request</th><th>Code</th><th>Response</th></tr></thead>
<tbody>
<tr><td class="code">GET /hello</td><td class="code ok">2.05</td><td class="code">Hello, World!</td></tr>
<tr><td class="code">GET /hello?name=…</td><td class="code ok">2.05</td><td class="code">Hello, …!</td></tr>
<tr><td class="code">POST /hello</td><td class="code ok">2.01</td><td class="code">Hello, &lt;payload&gt;!</td></tr>
<tr><td class="code">GET /health</td><td class="code ok">2.05</td><td class="code">{"status":"ok","uptime":12}</td></tr>
<tr><td class="code">POST /hello (empty)</td><td class="code err">4.00</td><td class="code">Bad Request</td></tr>
<tr><td class="code">GET /nope</td><td class="code err">4.04</td><td class="code">Not Found</td></tr>
<tr><td class="code">PUT /hello</td><td class="code err">4.05</td><td class="code">Method Not Allowed</td></tr>
</tbody>
</table>
<p class="hint">CoAP writes response codes as <code>class.detail</code>:
<code>2.05</code> is HTTP's 200, <code>4.04</code> its 404.
<code>POST /hello</code> takes raw text or <code>{"name":"…"}</code>.</p>
</div>
<div class="card">
<h2>Scripts</h2>
<p class="what">Full request/response suite against a running server</p>
<div class="cmd"><pre id="c-test">./test-coap.sh</pre><button data-copy="c-test">Copy</button></div>
<p class="what">Liveness check — exit 0 healthy, 1 not</p>
<div class="cmd"><pre id="c-health-sh">./healthcheck.sh -v</pre><button data-copy="c-health-sh">Copy</button></div>
<p class="what">Start the server</p>
<div class="cmd"><pre id="c-run">cargo run --release</pre><button data-copy="c-run">Copy</button></div>
</div>
</main>
<script>
const $ = (id) => document.getElementById(id);
const shell = (s) => "'" + String(s).replace(/'/g, `'\\''`) + "'";
async function call(method, path, body) {
$('status').textContent = '…';
$('status').className = 'status';
$('out').textContent = '';
try {
const res = await fetch(path, {
method,
body,
headers: body ? { 'Content-Type': 'text/plain' } : undefined,
});
const text = await res.text();
const coap = res.headers.get('X-Coap-Code') || '—';
$('status').textContent = `CoAP ${coap} · HTTP ${res.status}`;
$('status').className = 'status ' + (res.ok ? 'ok' : 'err');
$('out').textContent = `${method} ${path}\n${body ? 'body: ' + body + '\n' : ''}\n${text || '(empty)'}`;
} catch (err) {
$('status').textContent = 'Request failed';
$('status').className = 'status err';
$('out').textContent = String(err) + '\n\nIs the bridge still running?';
}
function render() {
const name = $('name').value.trim();
const q = name ? '/hello?name=' + encodeURIComponent(name) : '/hello';
const who = name || 'Imran';
$('c-get').textContent = `./target/release/client get ${shell(q)}`;
$('c-post').textContent = `./target/release/client post /hello ${shell(who)}`;
$('c-json').textContent = `./target/release/client post /hello ${shell(JSON.stringify({ name: who }))}`;
$('c-health').textContent = `./target/release/client get /health`;
$('c-cli').textContent = `coap get ${shell('coap://localhost:5683' + q)}`;
}
document.querySelectorAll('button[data-act]').forEach((btn) => {
btn.addEventListener('click', () => {
const name = $('name').value.trim();
switch (btn.dataset.act) {
case 'get':
return call('GET', '/hello' + (name ? '?name=' + encodeURIComponent(name) : ''));
case 'post':
return call('POST', '/hello', name || '');
case 'json':
return call('POST', '/hello', JSON.stringify({ name }));
case 'health':
return call('GET', '/health');
case 'notfound':
return call('GET', '/nope');
document.querySelectorAll('button[data-copy]').forEach((btn) => {
btn.addEventListener('click', async () => {
const text = $(btn.dataset.copy).textContent;
try {
await navigator.clipboard.writeText(text);
} catch {
// file:// and non-secure origins deny the clipboard API; fall back.
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
btn.textContent = 'Copied';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1200);
});
});
$('name').addEventListener('input', render);
render();
</script>
</body>
</html>