Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
4.1 KiB
HTML
109 lines
4.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Federation — FieldWitness{% endblock %}
|
|
{% block content %}
|
|
<h2><i class="bi bi-diagram-3 me-2"></i>Federation</h2>
|
|
<p class="text-muted">Gossip-based attestation sync between FieldWitness instances.</p>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card bg-dark">
|
|
<div class="card-body">
|
|
<h6 class="card-subtitle text-muted">Local Node</h6>
|
|
<p class="mb-1">Records: <strong>{{ node_info.size }}</strong></p>
|
|
<p class="mb-0 text-muted small">Root: {{ node_info.root }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card bg-dark">
|
|
<div class="card-body">
|
|
<h6 class="card-subtitle text-muted">Peers</h6>
|
|
<p class="mb-0"><strong>{{ peers|length }}</strong> configured</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card bg-dark">
|
|
<div class="card-body">
|
|
<h6 class="card-subtitle text-muted">Last Sync</h6>
|
|
{% if history %}
|
|
<p class="mb-0">{{ history[0].synced_at[:16] }}</p>
|
|
{% else %}
|
|
<p class="mb-0 text-muted">Never</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h5>Peers</h5>
|
|
{% if peers %}
|
|
<table class="table table-dark table-sm">
|
|
<thead>
|
|
<tr><th>URL</th><th>Fingerprint</th><th>Records</th><th>Health</th><th>Last Seen</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for p in peers %}
|
|
<tr>
|
|
<td><code>{{ p.url }}</code></td>
|
|
<td><code>{{ p.fingerprint[:16] }}...</code></td>
|
|
<td>{{ p.last_size }}</td>
|
|
<td>{% if p.healthy %}<span class="text-success">OK</span>{% else %}<span class="text-danger">DOWN</span>{% endif %}</td>
|
|
<td>{{ p.last_seen.strftime('%Y-%m-%d %H:%M') if p.last_seen else 'Never' }}</td>
|
|
<td>
|
|
<form method="POST" action="{{ url_for('federation.peer_remove') }}" class="d-inline">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<input type="hidden" name="url" value="{{ p.url }}">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Remove">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="text-muted">No peers configured. Add one below.</p>
|
|
{% endif %}
|
|
|
|
<div class="card bg-dark mb-4">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Add Peer</h6>
|
|
<form method="POST" action="{{ url_for('federation.peer_add') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<div class="row g-2">
|
|
<div class="col-md-5">
|
|
<input type="url" name="url" class="form-control bg-dark text-light" placeholder="https://peer:8000" required>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<input type="text" name="fingerprint" class="form-control bg-dark text-light" placeholder="Ed25519 fingerprint" required>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-primary w-100">Add</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if history %}
|
|
<h5>Sync History</h5>
|
|
<table class="table table-dark table-sm">
|
|
<thead>
|
|
<tr><th>Time</th><th>Peer</th><th>Records</th><th>Status</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for h in history %}
|
|
<tr>
|
|
<td>{{ h.synced_at[:19] }}</td>
|
|
<td><code>{{ h.peer_url[:30] }}</code></td>
|
|
<td>+{{ h.records_received }}</td>
|
|
<td>{% if h.success %}<span class="text-success">OK</span>{% else %}<span class="text-danger">{{ h.error[:40] if h.error else 'Failed' }}</span>{% endif %}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
{% endblock %}
|