105 lines
3.6 KiB
HTML
105 lines
3.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<h1>Sign Certificate</h1>
|
|
|
|
<div class="card">
|
|
<h2>New Host Certificate</h2>
|
|
<p style="color:var(--muted); font-size:13px; margin-bottom:20px;">
|
|
Fill in the details below. The CA on this host will sign a new certificate.
|
|
Scan the QR codes with the mobile Nebula app or copy the PEM values.
|
|
</p>
|
|
|
|
{% if error %}
|
|
<div class="alert alert-error">{{ error }}</div>
|
|
{% endif %}
|
|
|
|
<form method="post" action="/sign">
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px;">
|
|
<div class="field">
|
|
<label for="name">Host Name *</label>
|
|
<input type="text" id="name" name="name" placeholder="e.g. laptop" required
|
|
value="{{ result.name if result else '' }}">
|
|
</div>
|
|
<div class="field">
|
|
<label for="ip">Overlay IP / Mask *</label>
|
|
<input type="text" id="ip" name="ip" placeholder="e.g. 10.1.1.5/24" required
|
|
value="{{ result.ip if result else '' }}">
|
|
</div>
|
|
</div>
|
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px;">
|
|
<div class="field">
|
|
<label for="groups">Groups <span style="color:var(--muted)">(comma-separated, optional)</span></label>
|
|
<input type="text" id="groups" name="groups" placeholder="e.g. laptops,users">
|
|
</div>
|
|
<div class="field">
|
|
<label for="duration">Duration <span style="color:var(--muted)">(optional, e.g. 8760h0m0s)</span></label>
|
|
<input type="text" id="duration" name="duration" placeholder="default: CA lifetime">
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Sign Certificate</button>
|
|
</form>
|
|
</div>
|
|
|
|
{% if result %}
|
|
<div class="card">
|
|
<h2>Certificate Issued</h2>
|
|
|
|
{# cert info summary #}
|
|
{% if result.cert_info %}
|
|
{% set details = result.cert_info.get("details", {}) %}
|
|
<div class="stat-grid" style="margin-bottom:20px;">
|
|
<div class="stat-box">
|
|
<div class="label">Name</div>
|
|
<div class="value" style="font-size:14px; padding-top:2px;">{{ details.get("name", result.name) }}</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="label">IP</div>
|
|
<div class="value" style="font-size:14px; padding-top:2px;">{{ (details.get("ips", [result.ip]) | first) }}</div>
|
|
</div>
|
|
{% if details.get("groups") %}
|
|
<div class="stat-box">
|
|
<div class="label">Groups</div>
|
|
<div class="value" style="font-size:14px; padding-top:2px;">{{ details.get("groups") | join(", ") }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if details.get("notAfter") %}
|
|
<div class="stat-box">
|
|
<div class="label">Expires</div>
|
|
<div class="value" style="font-size:13px; padding-top:2px; word-break:break-all;">{{ details.get("notAfter") }}</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<p style="color:var(--muted); font-size:12px; margin-bottom:16px;">
|
|
Scan each QR code separately with the Nebula mobile app, or use the PEM text below.
|
|
The private key is shown only once — store it securely.
|
|
</p>
|
|
|
|
<div class="qr-grid">
|
|
<div>
|
|
<div class="qr-box">
|
|
<div class="qr-label">Host Certificate</div>
|
|
{{ result.cert_qr | safe }}
|
|
</div>
|
|
<details style="margin-top:10px;">
|
|
<summary>Certificate PEM</summary>
|
|
<pre>{{ result.cert_pem }}</pre>
|
|
</details>
|
|
</div>
|
|
<div>
|
|
<div class="qr-box">
|
|
<div class="qr-label">Host Key (Private)</div>
|
|
{{ result.key_qr | safe }}
|
|
</div>
|
|
<details style="margin-top:10px;">
|
|
<summary>Key PEM</summary>
|
|
<pre>{{ result.key_pem }}</pre>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|