Files
BountyBoard/web/templates/admin.html
T
etalon e8beb7d4f3 feat: sidebar nav (default), group member management, per-customer ticketing identity mapping
- Navigation: new per-user setting (default "side") renders the page links as a
  left sidebar while keeping theme/bell/avatar/logout in the top-right; falls
  back to a top bar under 760px or when set to "top". Profile selector added.
- Group conversations: record a creatorId; the creator gets a "Manage members"
  button to add/remove participants (new GET/POST/DELETE members endpoints,
  creator-only). Existing groups backfilled.
- Customer ticketing: admin can map each assigned consultant to their username
  in that customer's ticketing system. Per-customer mapping takes precedence
  over the consultant's global ticketingIdentities during sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 11:05:33 +02:00

164 lines
7.3 KiB
HTML

{{define "content"}}
<h1>Administration</h1>
<div class="error-box" id="error" role="alert"></div>
<nav class="tabs" role="tablist" aria-label="Admin sections">
<button class="btn" role="tab" data-tab="customers" aria-selected="true">Customers</button>
<button class="btn" role="tab" data-tab="users">Users</button>
<button class="btn" role="tab" data-tab="settings">Settings</button>
<button class="btn" role="tab" data-tab="status">Service status</button>
<button class="btn" role="tab" data-tab="audit">Audit log</button>
</nav>
<section id="tab-customers" class="card" role="tabpanel">
<div class="spread">
<h2>Customers</h2>
<button class="btn primary" id="customer-new">+ New customer</button>
</div>
<div id="customer-list" class="stack"></div>
<dialog id="customer-dialog">
<form id="customer-form" method="dialog" class="stack" style="min-width:480px">
<h2 id="customer-dialog-title">New customer</h2>
<div class="field">
<label for="c-name">Name</label>
<input type="text" id="c-name" required>
</div>
<div class="row">
<div class="field">
<label for="c-type">Ticketing system</label>
<select id="c-type">
<option value="jira">Jira Cloud</option>
<option value="azure_devops">Azure DevOps</option>
<option value="youtrack">YouTrack</option>
<option value="wekan">WeKan</option>
<option value="demo">Demo (offline)</option>
</select>
</div>
<div class="field">
<label for="c-project">Project key</label>
<input type="text" id="c-project" placeholder="ACME">
</div>
</div>
<div class="field" id="f-baseurl">
<label for="c-baseurl">Base URL</label>
<input type="text" id="c-baseurl" placeholder="https://acme.atlassian.net">
</div>
<fieldset id="creds-jira" class="creds">
<legend>Jira credentials</legend>
<div class="field"><label for="c-jira-email">Email</label><input type="text" id="c-jira-email" autocomplete="off"></div>
<div class="field"><label for="c-jira-token">API token</label><input type="password" id="c-jira-token" autocomplete="off"></div>
</fieldset>
<fieldset id="creds-azure_devops" class="creds" hidden>
<legend>Azure DevOps credentials</legend>
<div class="field"><label for="c-ado-org">Organization</label><input type="text" id="c-ado-org"></div>
<div class="field"><label for="c-ado-project">Project</label><input type="text" id="c-ado-project"></div>
<div class="field"><label for="c-ado-pat">Personal access token</label><input type="password" id="c-ado-pat" autocomplete="off"></div>
</fieldset>
<fieldset id="creds-youtrack" class="creds" hidden>
<legend>YouTrack credentials</legend>
<div class="field"><label for="c-yt-token">Permanent token</label><input type="password" id="c-yt-token" autocomplete="off"></div>
</fieldset>
<fieldset id="creds-wekan" class="creds" hidden>
<legend>WeKan credentials</legend>
<p class="hint">Project key above = the WeKan board id; the connector imports cards assigned to each consultant's WeKan username.</p>
<div class="field"><label for="c-wekan-user">Username</label><input type="text" id="c-wekan-user" autocomplete="off"></div>
<div class="field"><label for="c-wekan-pass">Password</label><input type="password" id="c-wekan-pass" autocomplete="off"></div>
</fieldset>
<fieldset id="creds-demo" class="creds" hidden>
<legend>Demo</legend>
<p class="hint">No credentials required — tickets are fabricated locally.</p>
</fieldset>
<div class="row">
<div class="field">
<label for="c-poll">Poll interval (seconds)</label>
<input type="number" id="c-poll" min="10" value="60">
</div>
<div class="field">
<label for="c-budget">Default budget</label>
<input type="number" id="c-budget" min="0" step="0.01" value="1000">
</div>
</div>
<div class="field">
<label for="c-consultants">Assigned consultants</label>
<select id="c-consultants" multiple size="4"></select>
<p class="hint">Hold Ctrl/Cmd to select multiple. At least one is needed for syncing.</p>
</div>
<div class="field" id="c-identities-wrap" hidden>
<label>Ticketing account mapping</label>
<p class="hint">Username in this project's ticketing system for each assigned consultant. Overrides the consultant's global identity. Leave blank to use their global one.</p>
<div id="c-identities"></div>
</div>
<div class="spread">
<span id="c-test-result" class="hint" aria-live="polite"></span>
<span>
<button class="btn" type="button" id="c-test">Test connection</button>
<button class="btn" type="button" id="c-cancel">Cancel</button>
<button class="btn primary" type="submit" id="c-save">Save</button>
</span>
</div>
</form>
</dialog>
</section>
<section id="tab-users" class="card" role="tabpanel" hidden>
<div class="spread">
<h2>Users</h2>
<input type="text" id="user-search" placeholder="Search name or email…" aria-label="Search users" style="max-width:280px">
</div>
<table class="list" id="user-table">
<thead><tr><th>Name</th><th>Email</th><th>Admin</th><th>Consultant</th><th>Developer</th><th>Status</th><th></th></tr></thead>
<tbody></tbody>
</table>
</section>
<section id="tab-settings" class="card" role="tabpanel" hidden>
<h2>Application settings</h2>
<form id="settings-form" class="stack" style="max-width:480px">
<div class="field">
<label for="s-branding">Branding name</label>
<input type="text" id="s-branding">
</div>
<div class="field">
<label for="s-atomizer">Atomizer base URL override</label>
<input type="text" id="s-atomizer" placeholder="leave empty to use the environment default">
</div>
<div class="field">
<label for="s-poll">Default poll interval (seconds)</label>
<input type="number" id="s-poll" min="10">
</div>
<div class="field">
<label for="s-budget">Default customer budget</label>
<input type="number" id="s-budget" min="0" step="0.01">
</div>
<div class="field">
<label><input type="checkbox" id="s-hideclaims"> Hide competing claim requests from developers</label>
</div>
<button class="btn primary" type="submit">Save settings</button>
</form>
</section>
<section id="tab-status" class="card" role="tabpanel" hidden>
<div class="spread">
<h2>Service status</h2>
<button class="btn small" id="status-refresh">Refresh</button>
</div>
<div id="status-body" class="stack"></div>
</section>
<section id="tab-audit" class="card" role="tabpanel" hidden>
<div class="spread">
<h2>Audit log</h2>
<span>
<input type="text" id="audit-entity" placeholder="Filter by entity id" aria-label="Filter by entity id">
<button class="btn small" id="audit-apply">Apply</button>
</span>
</div>
<table class="list" id="audit-table">
<thead><tr><th>When</th><th>Actor</th><th>Action</th><th>Entity</th><th>Details</th></tr></thead>
<tbody></tbody>
</table>
<button class="btn mt" id="audit-more">Load more</button>
</section>
{{end}}