docs: add project description and server setup instructions to README

This commit is contained in:
2026-03-21 18:25:35 +00:00
parent c81815a83d
commit 6115a065c7
36 changed files with 4003 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
{% extends "base.html" %}
{% block title %}{{ device.config.name }} — Demeter IoT{% endblock %}
{% block content %}
<!-- Breadcrumb -->
<div class="breadcrumbs text-sm mb-4">
<ul>
<li><a href="/dashboard">Dashboard</a></li>
<li>{{ device.config.name }}</li>
</ul>
</div>
<!-- Device header -->
<div class="flex items-center gap-4 mb-6">
<h1 class="text-3xl font-bold">{{ device.config.name }}</h1>
{% if device.online %}
<div class="badge badge-success badge-lg">Online</div>
{% else %}
<div class="badge badge-error badge-lg">Offline</div>
{% endif %}
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Device info card -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">Device Information</h2>
<table class="table table-sm">
<tbody>
<tr>
<td class="font-medium opacity-60">Device ID</td>
<td class="font-mono">{{ device.config.id }}</td>
</tr>
<tr>
<td class="font-medium opacity-60">IP Address</td>
<td class="font-mono">{{ device.config.ip }}:{{ device.config.port }}</td>
</tr>
<tr>
<td class="font-medium opacity-60">Status</td>
<td>
{% if device.online %}
<span class="text-success">Connected</span>
{% else %}
<span class="text-error">Disconnected</span>
{% endif %}
</td>
</tr>
<tr>
<td class="font-medium opacity-60">Resources</td>
<td>{{ device.config.resources|length }} configured</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- CoAP Bridge card -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">CoAP Bridge</h2>
<p class="text-sm opacity-60 mb-2">Query the device directly via the HTTP-to-CoAP proxy.</p>
<div class="space-y-2">
{% for resource in device.config.resources %}
<div class="flex items-center justify-between">
<span class="font-mono text-sm">{{ resource.uri }}</span>
<button class="btn btn-xs btn-outline"
onclick="coapGet('{{ device.config.id }}', '{{ resource.uri }}')">
GET
</button>
</div>
{% endfor %}
<div class="flex items-center justify-between">
<span class="font-mono text-sm">device/info</span>
<button class="btn btn-xs btn-outline"
onclick="coapGet('{{ device.config.id }}', 'device/info')">
GET
</button>
</div>
</div>
<!-- Response display -->
<div class="mt-4">
<pre id="coap-response" class="bg-base-200 p-3 rounded-lg text-sm font-mono overflow-x-auto min-h-[60px] opacity-60">
Click GET to query a resource...</pre>
</div>
</div>
</div>
</div>
<!-- Sensor readings -->
<div class="card bg-base-100 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Sensor Readings</h2>
{% if device.readings %}
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Resource</th>
<th>Value</th>
<th>Unit</th>
<th>Age</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for uri, reading in device.readings.items() %}
<tr>
<td class="font-mono text-sm">{{ uri }}</td>
<td class="font-mono text-lg">
{% if reading.value is not none %}
{{ reading.value }}
{% else %}
<span class="opacity-40"></span>
{% endif %}
</td>
<td>{{ reading.unit }}</td>
<td>{{ reading.age_seconds()|round(0)|int }}s ago</td>
<td>
{% if reading.stale %}
<div class="badge badge-warning badge-sm">Stale</div>
{% else %}
<div class="badge badge-success badge-sm">Fresh</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-sm opacity-40 italic">No readings received yet. The device may be offline or not yet subscribed.</p>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
async function coapGet(deviceId, resource) {
const pre = document.getElementById('coap-response');
pre.textContent = 'Fetching...';
pre.classList.remove('opacity-60');
try {
const resp = await fetch(`/api/devices/${deviceId}/coap/${resource}`);
const data = await resp.json();
pre.textContent = JSON.stringify(data, null, 2);
if (!resp.ok) {
pre.classList.add('text-error');
} else {
pre.classList.remove('text-error');
}
} catch (e) {
pre.textContent = `Error: ${e.message}`;
pre.classList.add('text-error');
}
}
</script>
{% endblock %}