hub: merge the customer edit page into the Edit tab (v0.48.0 edit-a, part 2)
- Settings tab renamed Edit; embeds config_form_body (.ConfigForm via the
builder) + Controller Update + Geo + a new Danger zone card holding the
relocated Block/Unblock/Delete forms (endpoints + confirm() unchanged).
All cards are SIBLINGS after </form> — never nested in the config form.
- Customer Info header loses the Edit link and Block/Delete forms; only the
config-less Create Config action stays.
- GET /configs/{id}/edit is a 302 to /customers/{id}#tab=edit; tabs JS gains
the settings→edit legacy-hash alias.
- Post-action redirects land back on their tab: update/block/unblock/
offsite-reissue/offsite-freeze/pbsdr-reissue → #tab=edit, regen-password
→ #tab=setup; delete unchanged (/configs).
- handleConfigUpdate gains the server-side twin of the form's required
fields; the error path re-renders the STANDALONE page with the SUBMITTED
overrides (B3 red-proof: nil overrides → typed values reset → test FAILS;
header red-proof: restored header buttons → count=2 → test FAILS; both run).
- Tests: Group A (panel surface, sibling forms, header cleaned by COUNT),
Group B (B1 302, B2 create unchanged, B3 typed-values, B4/B5 anchor table).
Amended pins: customer_tabs_test settings→edit; pbsdr_test postUpdate now
supplies the required fields + FormRendersState asserts the embedded render.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZc5w5jDhFLv6qDC32KN5v
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
<a href="#tab=overview" data-tab="overview" class="active">Overview</a>
|
||||
<a href="#tab=applications" data-tab="applications">Applications</a>
|
||||
<a href="#tab=setup" data-tab="setup">Setup</a>
|
||||
<a href="#tab=settings" data-tab="settings">Settings</a>
|
||||
<a href="#tab=edit" data-tab="edit">Edit</a>
|
||||
<a href="#tab=backup" data-tab="backup">Backup & DR</a>
|
||||
<a href="#tab=events" data-tab="events">Events{{with mapGet .EventCounts "error"}}<span class="tab-badge">{{.}}</span>{{end}}</a>
|
||||
<a href="#tab=notifications" data-tab="notifications">Notifications</a>
|
||||
@@ -104,26 +104,9 @@
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<h2>Customer Info</h2>
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
{{if .HasConfig}}
|
||||
<a href="/configs/{{.CustomerID}}/edit" class="btn btn-outline btn-sm">Edit</a>
|
||||
{{if .IsBlocked}}
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/unblock" style="display:inline">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-sm">Unblock</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/block" style="display:inline"
|
||||
onsubmit="return confirm('Block this customer? They will be hidden from the Dashboard.')">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-outline btn-sm">Block</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form method="POST" action="/configs/{{.CustomerID}}/delete" style="display:inline"
|
||||
onsubmit="return confirm('Delete configuration for {{.CustomerID}}? This cannot be undone.')">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||
</form>
|
||||
{{else}}
|
||||
{{/* v0.48.0 edit-a: Edit/Block/Delete moved to the Edit tab (form + danger
|
||||
zone); only the config-less bootstrap action stays in the header. */}}
|
||||
{{if not .HasConfig}}
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/create-config" style="display:inline">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-sm">Create Config</button>
|
||||
@@ -539,8 +522,17 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ═══ Settings ═══ -->
|
||||
<div class="tab-panel" data-tab="settings">
|
||||
<!-- ═══ Edit ═══ (v0.48.0 edit-a: the standalone config form embedded via the shared
|
||||
config_form_body sub-template, plus the relocated Block/Delete danger zone. The
|
||||
Controller Update / Geo / Danger zone cards are SIBLINGS after </form> — nesting
|
||||
them inside the config <form> is invalid HTML and breaks the formaction sub-buttons. -->
|
||||
<div class="tab-panel" data-tab="edit">
|
||||
|
||||
{{if .HasConfig}}
|
||||
{{template "config_form_body" .ConfigForm}}
|
||||
{{else}}
|
||||
<section class="card"><p class="text-muted">No managed config yet — create one with the Create Config button above.</p></section>
|
||||
{{end}}
|
||||
|
||||
{{if .HasReports}}
|
||||
<!-- Controller Update -->
|
||||
@@ -596,9 +588,8 @@
|
||||
</form>
|
||||
<p class="text-muted" style="margin-top: 0.75em; font-size: 0.8em;">
|
||||
Controller updates are agent-driven (the version floor above) and config is delivered by the
|
||||
box pulling it on a config change — the hub never connects into the box. Edit the config via
|
||||
the <strong>Edit</strong> button (Overview tab); the controller re-pulls and restarts on its
|
||||
next report.
|
||||
box pulling it on a config change — the hub never connects into the box. Edit the config in
|
||||
the form above; the controller re-pulls and restarts on its next report.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -673,6 +664,34 @@
|
||||
<section class="card"><p class="text-muted">Controller update and geo-restriction settings appear once the first report arrives.</p></section>
|
||||
{{end}}
|
||||
|
||||
{{if .HasConfig}}
|
||||
<!-- Danger zone (v0.48.0 edit-a): the Block/Delete forms relocated verbatim from the
|
||||
Customer Info header — endpoints and confirm() handlers unchanged. -->
|
||||
<section class="card">
|
||||
<h2>Danger zone</h2>
|
||||
<p class="text-muted">Blocking hides the customer from the Dashboard (reports are still accepted); deleting removes the managed configuration permanently.</p>
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem;">
|
||||
{{if .IsBlocked}}
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/unblock" style="display:inline">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-sm">Unblock</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<form method="POST" action="/customers/{{.CustomerID}}/block" style="display:inline"
|
||||
onsubmit="return confirm('Block this customer? They will be hidden from the Dashboard.')">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-outline btn-sm">Block</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form method="POST" action="/configs/{{.CustomerID}}/delete" style="display:inline"
|
||||
onsubmit="return confirm('Delete configuration for {{.CustomerID}}? This cannot be undone.')">
|
||||
{{.CSRFField}}
|
||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ═══ Backup & DR ═══ -->
|
||||
@@ -1049,7 +1068,9 @@
|
||||
panels.forEach(function(p) { known[p.getAttribute('data-tab')] = true; });
|
||||
function currentTab() {
|
||||
var m = (location.hash || '').match(/^#tab=([a-z-]+)$/);
|
||||
return (m && known[m[1]]) ? m[1] : 'overview';
|
||||
var t = m ? m[1] : '';
|
||||
if (t === 'settings') t = 'edit'; // legacy alias: the Settings tab became Edit (v0.48.0)
|
||||
return known[t] ? t : 'overview';
|
||||
}
|
||||
function activate() {
|
||||
var tab = currentTab();
|
||||
|
||||
Reference in New Issue
Block a user