- Both olimex_esp32_c6_evb and olimex_esp32_c6_evb_pn532 drivers now sign every API request with X-Request-Time / X-Request-Sig headers using HMAC-SHA256(api_secret, METHOD:path:unix_timestamp) - Board model gains api_secret column (nullable, default None) - boards.py edit route saves api_secret from form - edit.html adds API Secret input with cryptographic Generate button - If api_secret is empty/None, headers are omitted (backward compat)
113 lines
4.7 KiB
HTML
113 lines
4.7 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Edit {{ board.name }} – Location Management{% endblock %}
|
||
|
||
{% block content %}
|
||
<nav aria-label="breadcrumb" class="mb-3">
|
||
<ol class="breadcrumb">
|
||
<li class="breadcrumb-item"><a href="{{ url_for('boards.list_boards') }}">Boards</a></li>
|
||
<li class="breadcrumb-item"><a href="{{ url_for('boards.board_detail', board_id=board.id) }}">{{ board.name }}</a></li>
|
||
<li class="breadcrumb-item active">Edit</li>
|
||
</ol>
|
||
</nav>
|
||
|
||
<div class="card border-0 rounded-4" style="max-width:700px">
|
||
<div class="card-header bg-transparent fw-semibold pt-3">
|
||
<i class="bi bi-pencil me-1"></i> Edit Board — {{ board.name }}
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="POST">
|
||
<!-- Connection -->
|
||
<h6 class="text-secondary mb-3 text-uppercase small fw-semibold">Connection</h6>
|
||
<div class="mb-3">
|
||
<label class="form-label">Local Name</label>
|
||
<input type="text" name="name" class="form-control" value="{{ board.name }}" required />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Board Type</label>
|
||
<select name="board_type" class="form-select">
|
||
{% for value, label in board_types %}
|
||
<option value="{{ value }}" {% if board.board_type == value %}selected{% endif %}>{{ label }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-8">
|
||
<label class="form-label">IP / Hostname</label>
|
||
<input type="text" name="host" class="form-control font-monospace" value="{{ board.host }}" required />
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">Port</label>
|
||
<input type="number" name="port" class="form-control" value="{{ board.port }}" />
|
||
</div>
|
||
</div>
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-6">
|
||
<label class="form-label">Number of Relays</label>
|
||
<input type="number" name="num_relays" class="form-control" value="{{ board.num_relays }}" min="1" max="32" />
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label">Number of Inputs</label>
|
||
<input type="number" name="num_inputs" class="form-control" value="{{ board.num_inputs }}" min="0" max="32" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Labels -->
|
||
<h6 class="text-secondary mb-3 text-uppercase small fw-semibold">Relay Labels</h6>
|
||
<div class="row g-2 mb-4">
|
||
{% for n in range(1, board.num_relays + 1) %}
|
||
<div class="col-6">
|
||
<label class="form-label small">Relay {{ n }}</label>
|
||
<input type="text" name="relay_{{ n }}_label" class="form-control form-control-sm"
|
||
placeholder="Relay {{ n }}"
|
||
value="{{ board.labels.get('relay_' ~ n, '') }}" />
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<h6 class="text-secondary mb-3 text-uppercase small fw-semibold">Input Labels</h6>
|
||
<div class="row g-2 mb-4">
|
||
{% for n in range(1, board.num_inputs + 1) %}
|
||
<div class="col-6">
|
||
<label class="form-label small">Input {{ n }}</label>
|
||
<input type="text" name="input_{{ n }}_label" class="form-control form-control-sm"
|
||
placeholder="Input {{ n }}"
|
||
value="{{ board.labels.get('input_' ~ n, '') }}" />
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<!-- API Security -->
|
||
<h6 class="text-secondary mb-3 text-uppercase small fw-semibold">API Security</h6>
|
||
<div class="mb-4">
|
||
<label class="form-label">API Secret <span class="text-secondary small">(HMAC-SHA256 shared secret)</span></label>
|
||
<div class="input-group">
|
||
<input type="text" name="api_secret" id="api-secret-input" class="form-control font-monospace"
|
||
placeholder="Leave empty to disable API authentication"
|
||
value="{{ board.api_secret or '' }}">
|
||
<button class="btn btn-outline-secondary" type="button" onclick="genSecret()">
|
||
<i class="bi bi-shuffle me-1"></i>Generate
|
||
</button>
|
||
</div>
|
||
<div class="form-text">Must match <code>API_SECRET</code> in the board's <code>secrets.h</code>.</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i> Save</button>
|
||
<a href="{{ url_for('boards.board_detail', board_id=board.id) }}" class="btn btn-outline-secondary">Cancel</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
function genSecret() {
|
||
const buf = new Uint8Array(32);
|
||
crypto.getRandomValues(buf);
|
||
document.getElementById('api-secret-input').value =
|
||
Array.from(buf).map(b => b.toString(16).padStart(2, '0')).join('');
|
||
}
|
||
</script>
|
||
{% endblock %}
|