Files
felhom.eu/hub/internal/web/templates/config_form_body.html
T

216 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "config_form_body"}}
<form method="POST" action="{{if .IsNew}}/configs/new{{else}}/configs/{{.Config.CustomerID}}/edit{{end}}" class="config-form">
{{.CSRFField}}
<div class="card">
<h2>Customer Identity</h2>
<div class="form-grid">
<div class="form-group">
<label for="customer_id">Customer ID *</label>
<input type="text" id="customer_id" name="customer_id"
value="{{.Config.CustomerID}}"
pattern="[a-zA-Z0-9.\-]+"
placeholder="e.g. customer-1"
{{if not .IsNew}}readonly{{end}}
required>
{{if .IsNew}}<span class="form-hint">Letters, numbers, dots, hyphens only</span>{{end}}
</div>
<div class="form-group">
<label for="customer_name">Display Name *</label>
<input type="text" id="customer_name" name="customer_name"
value="{{.Config.CustomerName}}"
placeholder="e.g. Kovács család"
required>
</div>
<div class="form-group">
<label for="domain">Domain *</label>
<input type="text" id="domain" name="domain"
value="{{.Config.Domain}}"
placeholder="e.g. kovacs.felhom.eu"
required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email"
value="{{.Config.Email}}"
placeholder="e.g. kovacs@example.com">
</div>
</div>
</div>
<details class="card" {{if index .Overrides "infrastructure"}}open{{end}}>
<summary><h2 style="display:inline">Infrastructure</h2></summary>
<div class="form-grid" style="margin-top: 1rem;">
<div class="form-group">
<label for="cf_tunnel_token">Cloudflare Tunnel Token</label>
<input type="text" id="cf_tunnel_token" name="cf_tunnel_token"
value="{{with .Overrides}}{{with index . "infrastructure"}}{{with index . "cf_tunnel_token"}}{{.}}{{end}}{{end}}{{end}}"
placeholder="Optional">
</div>
<div class="form-group">
<label for="cf_api_token">Cloudflare API Token</label>
<input type="text" id="cf_api_token" name="cf_api_token"
value="{{with .Overrides}}{{with index . "infrastructure"}}{{with index . "cf_api_token"}}{{.}}{{end}}{{end}}{{end}}"
placeholder="DNS-01 + WAF permissions">
<small class="form-hint">Szükséges jogosultságok: Zone DNS:Edit (ACME), Zone WAF:Edit (geo-korlátozás)</small>
</div>
</div>
</details>
<details class="card" {{if index .Overrides "git"}}open{{end}}>
<summary><h2 style="display:inline">Git Sync</h2></summary>
<p class="text-muted" style="margin: 0.5rem 0 0; font-size: 0.85rem;">Opcionális — csak privát alkalmazás-katalógushoz. A verziófrissítés enélkül is működik.</p>
<div class="form-grid" style="margin-top: 1rem;">
<div class="form-group">
<label for="git_username">Git Username</label>
<input type="text" id="git_username" name="git_username"
value="{{with .Overrides}}{{with index . "git"}}{{with index . "username"}}{{.}}{{end}}{{end}}{{end}}"
placeholder="For private catalog">
</div>
<div class="form-group">
<label for="git_token">Git Token</label>
<input type="text" id="git_token" name="git_token"
value="{{with .Overrides}}{{with index . "git"}}{{with index . "token"}}{{.}}{{end}}{{end}}{{end}}"
placeholder="For private catalog">
</div>
</div>
</details>
<details class="card" {{if index .Overrides "logging"}}open{{end}}>
<summary><h2 style="display:inline">Hibakeresési mód (fejlesztői)</h2></summary>
<div class="form-grid" style="margin-top: 1rem;">
<div class="form-group">
<label><input type="checkbox" name="debug_mode"
{{with .Overrides}}{{with index . "logging"}}{{if eq (index . "level") "debug"}}checked{{end}}{{end}}{{end}}>
Debug mód — bőbeszédű napló + /debug menü a vezérlőn</label>
<small class="form-hint">Bekapcsolva a vezérlő újraindul a következő ciklusban; a menü: https://felhom.&lt;domain&gt;/debug. Tesztelés után kapcsold ki.</small>
</div>
</div>
</details>
<details class="card" {{if index .Overrides "offsite"}}open{{end}}>
<summary><h2 style="display:inline">Offsite backup</h2></summary>
<div class="form-grid" style="margin-top: 1rem;">
<div class="form-group">
<label><input type="checkbox" name="offsite_enabled"
{{with .Overrides}}{{with index . "offsite"}}{{if index . "enabled"}}checked{{end}}{{end}}{{end}}>
Enable offsite (provisions a Hetzner Storage Box on save)</label>
</div>
<div class="form-group">
<label for="offsite_type">Type</label>
<select id="offsite_type" name="offsite_type">
<option value="shared" {{with .Overrides}}{{with index . "offsite"}}{{if eq (index . "type") "shared"}}selected{{end}}{{end}}{{end}}>Shared (sub-account on the pool box)</option>
<option value="dedicated" {{with .Overrides}}{{with index . "offsite"}}{{if eq (index . "type") "dedicated"}}selected{{end}}{{end}}{{end}}>Dedicated (own box)</option>
</select>
</div>
<div class="form-group">
<label for="offsite_quota_gb">Soft-quota GB (shared)</label>
<input type="number" id="offsite_quota_gb" name="offsite_quota_gb"
value="{{with .Overrides}}{{with index . "offsite"}}{{with index . "quota_gb"}}{{.}}{{end}}{{end}}{{end}}" placeholder="e.g. 50">
</div>
<div class="form-group">
<label for="offsite_box_type">Box type (dedicated)</label>
<select id="offsite_box_type" name="offsite_box_type">
<option value="bx11" {{with .Overrides}}{{with index . "offsite"}}{{if eq (index . "box_type") "bx11"}}selected{{end}}{{end}}{{end}}>bx11 (1 TB)</option>
<option value="bx21" {{with .Overrides}}{{with index . "offsite"}}{{if eq (index . "box_type") "bx21"}}selected{{end}}{{end}}{{end}}>bx21 (5 TB)</option>
<option value="bx31" {{with .Overrides}}{{with index . "offsite"}}{{if eq (index . "box_type") "bx31"}}selected{{end}}{{end}}{{end}}>bx31 (10 TB)</option>
</select>
</div>
</div>
{{with .Overrides}}{{with index . "offsite"}}{{if index . "host"}}
<p class="form-hint" style="margin-top:.5rem">Provisioned: {{index . "user"}}@{{index . "host"}}:{{index . "repo_path"}} (the transient password is never shown here).</p>
<!-- R-70: the delivery state replaces the old static "delivered once" claim — the hub
now RENDERS what it knows (one_time_secrets × report offbox presence) instead of
asserting a delivery it never checked (DIAG-f10: a burned credential hid 2 days). -->
{{with $.Delivery}}
<p class="form-hint" style="margin-top:.35rem" data-delivery-state="{{.State}}"><span class="n {{.BadgeClass}}">{{.Badge}}</span> {{.Line}}</p>
{{if .StaleLine}}<p class="form-hint" style="margin-top:.35rem">{{.StaleLine}}</p>{{end}}
{{end}}
<!-- F4: explicit operator recovery for a consumed-password dead-end (fresh-guest DR).
Rides the parent form (nested forms are invalid HTML) via formaction; the _csrf field
submits with it. Resets the box credential + stages a fresh one-time password. -->
<button type="submit" class="btn btn-outline" style="margin-top:.5rem"
formaction="/configs/{{$.Config.CustomerID}}/offsite-reissue" formmethod="POST"
data-confirm="Re-issue the offsite credentials? The box password is reset and a fresh one-time password is staged for the controller. Guests with a working installed key are unaffected (key-auth-first); a stuck fresh guest picks the new password up on its next config refresh.">
Re-issue offsite credentials</button>
{{if eq (index . "type") "shared"}}
<!-- SLICE 4: operator freeze lever (readonly on the sub-account) — MANUAL only, never
automatic: freezing also blocks prune, the customer's only way down from over-quota. -->
<button type="submit" class="btn btn-outline" style="margin-top:.5rem"
formaction="/configs/{{$.Config.CustomerID}}/offsite-freeze" formmethod="POST"
data-confirm="Freeze the offsite storage (read-only)? New backups AND prune will fail until unfrozen — use for runaway usage only.">
Freeze offsite (read-only)</button>
<button type="submit" class="btn btn-outline" name="unfreeze" value="1" style="margin-top:.5rem"
formaction="/configs/{{$.Config.CustomerID}}/offsite-freeze" formmethod="POST"
data-confirm="Unfreeze the offsite storage (read-write again)?">
Unfreeze offsite</button>
{{end}}
{{end}}{{end}}{{end}}
</details>
<details class="card" {{if .PBSDR.DRTier}}open{{end}}>
<summary><h2 style="display:inline">DR tier (PBS, ep0)</h2></summary>
<div class="form-grid" style="margin-top: 1rem;">
<div class="form-group">
<label><input type="checkbox" name="dr_tier" {{if .PBSDR.DRTier}}checked{{end}}>
DR tier — offsite disaster-recovery via PBS (the DEFAULT for new customers; disabling is the exception and removes the Felhom-side cost entirely)</label>
<small class="form-hint">One flag, ordered rollout: the ep0 namespace + token provision automatically once the host's WG tunnel peer registers; the host agent applies the storage entry on its next tick. Offsite app backup requires this tier (the escrow ceremony depends on the PBS key).</small>
{{if not .PBSDR.Supported}}
<small class="form-hint">Not configured on this hub (no tenantsync key) — the flag is stored, but provisioning waits until it is.</small>
{{end}}
</div>
<div class="form-group">
<label for="pbsdr_storage_id">PVE storage id</label>
<input type="text" id="pbsdr_storage_id" name="pbsdr_storage_id"
value="{{.PBSDR.StorageID}}" placeholder="felhom-pbs">
<small class="form-hint">The storage-entry id the agent creates on the box. Default felhom-pbs; the demo host's adopted manual entry is felhom-offsite.</small>
</div>
</div>
{{if .PBSDR.DRTier}}
<!-- v0.51.0 cascade status (scenario D): per-stage reality vs the stored intent,
reusing the fail-closed guard wording. Neutral states, never red — waiting is
honest, not broken. -->
<ul class="form-hint" style="margin-top:.5rem; list-style:none; padding-left:0; display:grid; gap:.25rem">
<li>{{if not .PBSDR.NoHost}}<span class="badge badge-ok">done</span> host enrolled ({{.PBSDR.HostID}}){{else}}<span class="badge badge-neutral">waiting</span> no host enrolled yet — the Day-0 install enrolls it{{end}}</li>
<li>{{if .PBSDR.WGPeer}}<span class="badge badge-ok">done</span> WG tunnel peer registered{{else}}<span class="badge badge-neutral">waiting</span> the host has not reported a WG key yet — the tunnel peer must exist before the PBS DR tier (registration is hands-free once the agent starts){{end}}</li>
<li>{{if .PBSDR.Provisioned}}<span class="badge badge-ok">done</span> descriptor provisioned (namespace {{.PBSDR.Namespace}}, token {{.PBSDR.TokenID}}){{else}}<span class="badge badge-neutral">waiting</span> provisions automatically when the WG peer registers{{end}}</li>
<li>{{if .PBSDR.EscrowStale}}<span class="badge badge-warn">stale</span> key escrow STALE — offsite credentials were re-issued since the ceremony; the sealed repo password no longer matches. Re-run the ceremony with the customer (R-moment){{else if .PBSDR.EscrowPresent}}<span class="badge badge-ok">done</span> key escrow present (ceremony done){{else}}<span class="badge badge-neutral">waiting</span> ceremony possible once the descriptor is applied on the box (run it with the customer — R-moment){{end}}</li>
</ul>
{{end}}
{{if and .PBSDR.Provisioned (not .PBSDR.DRTier)}}
<p class="form-hint" style="margin-top:.5rem">Tier OFF with a provisioned namespace: runs stop and the box reports the DR capabilities inactive; the ep0 namespace + data are KEPT (teardown of an ever-on tier is a deliberate manual operator action).</p>
{{end}}
{{if .PBSDR.Provisioned}}
<!-- The offsite F4 precedent: explicit operator re-key for a consumed-secret dead-end.
Rides the parent form via formaction; _csrf submits with it. -->
<button type="submit" class="btn btn-outline" style="margin-top:.5rem"
formaction="/configs/{{.Config.CustomerID}}/pbsdr-reissue" formmethod="POST"
data-confirm="Re-issue the PBS credentials? The endpoint token is re-keyed and a fresh one-time secret is staged for the host agent. A box with a working storage entry re-applies on its next desired-state fetch.">
Re-issue PBS credentials</button>
{{end}}
</details>
<div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center;">
<button type="submit" class="btn">{{if .IsNew}}Create Configuration{{else}}Save Changes{{end}}</button>
<a href="{{if .IsNew}}/configs{{else}}/customers/{{.Config.CustomerID}}{{end}}" class="btn btn-outline">Cancel</a>
<span id="cfg-inflight" class="form-hint" style="display:none">Saving… offsite provisioning can take up to a minute — please do not click again.</span>
</div>
</form>
<script>
// F5: the offsite save takes ~2560s (create + action-wait + host-key scan). Without feedback
// operators re-click, which used to strand the provision (live finding F1). Disable all submit
// buttons + show the in-flight notice once a submit is underway (deferred a tick so the
// clicked button's formaction still applies).
(function () {
var form = document.querySelector('form.config-form');
if (!form) return;
form.addEventListener('submit', function () {
document.getElementById('cfg-inflight').style.display = 'inline';
var btns = form.querySelectorAll('button[type=submit]');
setTimeout(function () {
for (var i = 0; i < btns.length; i++) btns[i].disabled = true;
}, 0);
});
})();
</script>
{{end}}