feat: polish alpine settings and wizard feedback

This commit is contained in:
a.tolmachev
2026-03-31 16:32:59 +03:00
parent 564334e300
commit 7aabf7077a
6 changed files with 167 additions and 336 deletions
+30 -258
View File
@@ -96,7 +96,7 @@
<div class="page-header" style="margin-bottom: 24px;">
<div class="page-header-text">
<h1 class="page-title">Account settings</h1>
<p class="page-subtitle">Manage your personal profile, security, and notification preferences.</p>
<p class="page-subtitle">Manage the live profile, password and workspace settings available in this build. Planned capabilities stay visible, but they do not pretend to be wired yet.</p>
</div>
</div>
@@ -215,10 +215,10 @@
<div class="avatar-upload-actions">
<div class="avatar-name" id="profile-display-name">Operator</div>
<div class="avatar-sub" id="profile-email">operator@acme-workspace</div>
<div class="field-hint">Profile photo upload is not available yet.</div>
<div class="field-hint">Your avatar is generated from your display name and email.</div>
</div>
</div>
<div class="field-hint" style="margin-bottom:16px;">Your name and email are stored on the backend and used across the console.</div>
<div class="field-hint" style="margin-bottom:16px;">Your name and email are stored on the backend and used across the console and session identity.</div>
<div class="field-row">
<div class="field-group">
@@ -245,196 +245,6 @@
</div>
</div>
<!-- ■ MEMBERS — moved to workspace-setup.html ■ -->
<div id="section-members" style="display:none;">
<!-- Members card -->
<div class="section-card" style="margin-bottom: 16px;">
<div class="section-card-header">
<div>
<div class="section-card-title">Team members</div>
<div class="section-card-subtitle" id="members-count-label">4 members</div>
</div>
<button class="btn-primary" type="button" style="padding:7px 14px;font-size:13px;" onclick="toggleInviteForm()">
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" style="margin-right:4px;"><path d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 010 1.5H8.5v4.25a.75.75 0 01-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
Invite member
</button>
</div>
<!-- Invite form -->
<div id="invite-form" style="display:none; padding: 0 20px 16px; border-bottom: 1px solid var(--border-subtle);">
<div style="display:flex; gap:10px; align-items:flex-end; flex-wrap:wrap;">
<div class="field-group" style="flex:1; min-width:200px; margin-bottom:0;">
<label class="field-label">Email address</label>
<input class="field-input" id="invite-email" type="email" placeholder="colleague@company.com" autocomplete="off">
</div>
<div class="field-group" style="width:150px; margin-bottom:0;">
<label class="field-label">Role</label>
<select class="field-input" id="invite-role">
<option value="admin">Admin</option>
<option value="developer" selected>Developer</option>
<option value="viewer">Viewer</option>
</select>
</div>
<button class="btn-primary" type="button" style="padding:7px 14px;font-size:13px;white-space:nowrap;" onclick="sendInvite()">Send invite</button>
<button class="btn-secondary" type="button" style="padding:7px 12px;font-size:13px;" onclick="toggleInviteForm()">Cancel</button>
</div>
<div id="invite-success" style="display:none; margin-top:10px; font-size:13px; color: #3fb950;">
✓ Invite sent successfully.
</div>
</div>
<!-- Members list -->
<div class="section-card-body" style="padding: 4px 20px 8px;" id="members-list">
<!-- Owner row (you) -->
<div class="member-row">
<div class="member-avatar" style="background:linear-gradient(135deg,#0d9488,#6366f1);">AT</div>
<div class="member-info">
<div class="member-name">Operator <span style="font-size:11px;font-weight:400;color:var(--text-muted);">(you)</span></div>
<div class="member-email">operator@acme.com</div>
</div>
<div class="member-last-active">Today</div>
<span class="member-role-badge role-owner">Owner</span>
</div>
<!-- Admin -->
<div class="member-row">
<div class="member-avatar" style="background:linear-gradient(135deg,#7c3aed,#4f46e5);">BS</div>
<div class="member-info">
<div class="member-name">Blake Smith</div>
<div class="member-email">blake@acme.com</div>
</div>
<div class="member-last-active">2 days ago</div>
<select class="member-role-select" onchange="updateRole(this, 'blake@acme.com')">
<option value="admin" selected>Admin</option>
<option value="developer">Developer</option>
<option value="viewer">Viewer</option>
</select>
<button class="member-remove-btn" onclick="removeMember(this, 'Blake Smith')" title="Remove member">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M10 8v4M6 8v4"/><path d="M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9"/></svg>
</button>
</div>
<!-- Developer -->
<div class="member-row">
<div class="member-avatar" style="background:linear-gradient(135deg,#d29922,#f59e0b);">CL</div>
<div class="member-info">
<div class="member-name">Carol Lane</div>
<div class="member-email">carol@acme.com</div>
</div>
<div class="member-last-active">1 week ago</div>
<select class="member-role-select" onchange="updateRole(this, 'carol@acme.com')">
<option value="admin">Admin</option>
<option value="developer" selected>Developer</option>
<option value="viewer">Viewer</option>
</select>
<button class="member-remove-btn" onclick="removeMember(this, 'Carol Lane')" title="Remove member">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M10 8v4M6 8v4"/><path d="M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9"/></svg>
</button>
</div>
<!-- Viewer -->
<div class="member-row">
<div class="member-avatar" style="background:linear-gradient(135deg,#0891b2,#06b6d4);">DM</div>
<div class="member-info">
<div class="member-name">Dan Morgan</div>
<div class="member-email">dan@acme.com</div>
</div>
<div class="member-last-active">Never</div>
<select class="member-role-select" onchange="updateRole(this, 'dan@acme.com')">
<option value="admin">Admin</option>
<option value="developer">Developer</option>
<option value="viewer" selected>Viewer</option>
</select>
<button class="member-remove-btn" onclick="removeMember(this, 'Dan Morgan')" title="Remove member">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M10 8v4M6 8v4"/><path d="M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9"/></svg>
</button>
</div>
</div>
<!-- Pending invites -->
<div id="pending-invites" style="border-top: 1px solid var(--border-subtle); padding: 12px 20px 16px;">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);margin-bottom:10px;">Pending invites <span id="pending-count" style="background:rgba(139,148,158,0.15);border-radius:10px;padding:1px 7px;font-size:10px;">1</span></div>
<div class="member-row" id="invite-john">
<div class="member-avatar" style="background:var(--bg-canvas);border:1.5px dashed var(--border);color:var(--text-muted);font-size:16px;">?</div>
<div class="member-info">
<div class="member-name">john@startup.com</div>
<div class="member-email">Invited 3 days ago · not yet accepted</div>
</div>
<div class="member-last-active"></div>
<span class="member-role-badge" style="background:rgba(139,148,158,0.12);color:var(--text-muted);border:1px solid var(--border);">Developer</span>
<button class="member-remove-btn" onclick="revokeInvite(this)" title="Revoke invite">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="2" y1="2" x2="14" y2="14"/><line x1="14" y1="2" x2="2" y2="14"/></svg>
</button>
</div>
</div>
</div>
<!-- Roles & permissions reference -->
<div class="section-card" style="margin-bottom: 20px;">
<div class="section-card-header">
<div class="section-card-title">Roles &amp; permissions</div>
</div>
<div class="section-card-body" style="padding: 4px 0 8px; gap: 0;">
<div class="role-def-row">
<span class="member-role-badge role-owner" style="min-width:80px;text-align:center;">Owner</span>
<div class="role-def-text">Full workspace control, billing access, can transfer or delete the workspace. Only one owner per workspace.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-admin" style="min-width:80px;text-align:center;">Admin</span>
<div class="role-def-text">Manage members and roles, create and edit all operations and agents, manage API keys.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-developer" style="min-width:80px;text-align:center;">Developer</span>
<div class="role-def-text">Create and edit operations and agents. Cannot manage members or workspace settings.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-viewer" style="min-width:80px;text-align:center;">Viewer</span>
<div class="role-def-text">Read-only access to operations, agents, and logs. Cannot create or modify anything.</div>
</div>
</div>
</div>
</div>
<script>
function toggleInviteForm() {
var f = document.getElementById('invite-form');
var s = document.getElementById('invite-success');
if (f.style.display === 'none') {
f.style.display = '';
s.style.display = 'none';
document.getElementById('invite-email').focus();
} else {
f.style.display = 'none';
}
}
function sendInvite() {
var email = document.getElementById('invite-email').value.trim();
if (!email || !email.includes('@')) return;
document.getElementById('invite-success').style.display = '';
document.getElementById('invite-email').value = '';
}
function updateRole(select, email) {
// In production: API call to update role
console.log('Role updated:', email, '->', select.value);
}
function removeMember(btn, name) {
if (!confirm('Remove ' + name + ' from this workspace?')) return;
btn.closest('.member-row').remove();
updateMembersCount();
}
function revokeInvite(btn) {
btn.closest('.member-row').remove();
var pending = document.getElementById('pending-invites');
if (pending && !pending.querySelector('.member-row')) {
pending.style.display = 'none';
}
}
function updateMembersCount() {
var rows = document.querySelectorAll('#members-list .member-row');
var label = document.getElementById('members-count-label');
if (label) label.textContent = rows.length + ' member' + (rows.length !== 1 ? 's' : '');
}
</script>
<!-- ■ SECURITY ■ -->
<div id="section-security" class="section-anchor">
<div class="section-card" style="margin-bottom: 20px;">
@@ -464,8 +274,15 @@
<div style="padding: 4px 20px 16px;">
<div class="settings-section-divider" style="margin-top:0;"></div>
<div style="font-size:13.5px;font-weight:600;color:var(--text-primary);margin-bottom:12px;">Advanced security</div>
<div class="field-hint" style="margin-bottom:12px;">Two-factor authentication, passkeys and session management are not implemented yet. The current live security flow is password-based login with HttpOnly session cookies.</div>
<div style="font-size:13.5px;font-weight:600;color:var(--text-primary);margin-bottom:12px;">Planned security capabilities</div>
<div class="callout warning" style="margin-bottom:0;">
<svg class="callout-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--amber)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<polygon points="8,1.5 15.5,14.5 0.5,14.5" fill="none"/><path d="M8 6v4M8 11.5v.5"/>
</svg>
<div>
<strong>Not available in this build.</strong> Two-factor authentication, passkeys and multi-session inventory are planned, but the current live flow is password-based login with one HttpOnly browser session.
</div>
</div>
</div>
</div>
@@ -477,7 +294,7 @@
<div class="member-row">
<div class="member-info">
<div class="member-name">Browser session — <span style="color:var(--green);font-size:12px;">current</span></div>
<div class="member-email">Session expires according to server-side auth settings. Use Log out to revoke the current browser session.</div>
<div class="member-email" id="settings-session-summary">Loading current session details…</div>
</div>
</div>
</div>
@@ -491,73 +308,28 @@
<div class="section-card-title">Notification preferences</div>
</div>
<div style="padding: 8px 20px 16px;">
<div class="toggle-setting-row">
<div class="toggle-setting-text">
<div class="toggle-setting-label">Operation error spike</div>
<div class="toggle-setting-desc">Alert when error rate exceeds 5% over a 5-minute window.</div>
<div class="callout info" style="margin-bottom:14px;">
<svg class="callout-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<circle cx="8" cy="8" r="6.5"/>
<path d="M8 11V8M8 5.5V5"/>
</svg>
<div>
<strong>Notifications are not wired yet.</strong> Alert routing and per-user preferences are planned, but this build does not persist or deliver notification settings.
</div>
<div class="toggle-sm on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="toggle-setting-row">
<div class="toggle-setting-text">
<div class="toggle-setting-label">Upstream latency degradation</div>
<div class="toggle-setting-desc">Alert when p99 latency exceeds the baseline by 3×.</div>
<div class="planned-list">
<div class="planned-item">
<div class="planned-item-title">Operation error spike</div>
<div class="planned-item-desc">Alert when error rate exceeds a rolling threshold for a published tool.</div>
</div>
<div class="toggle-sm on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="toggle-setting-row">
<div class="toggle-setting-text">
<div class="toggle-setting-label">Quota approaching limit</div>
<div class="toggle-setting-desc">Email when any operation reaches 80% of its monthly quota.</div>
<div class="planned-item">
<div class="planned-item-title">Upstream latency degradation</div>
<div class="planned-item-desc">Notify operators when p99 latency rises sharply above the baseline.</div>
</div>
<div class="toggle-sm on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="toggle-setting-row">
<div class="toggle-setting-text">
<div class="toggle-setting-label">New team member joined</div>
<div class="toggle-setting-desc">Notify workspace admins when someone accepts an invite.</div>
<div class="planned-item">
<div class="planned-item-title">Quota and usage digests</div>
<div class="planned-item-desc">Deliver periodic summaries for quota consumption, errors and workspace activity.</div>
</div>
<div class="toggle-sm" onclick="this.classList.toggle('on')"></div>
</div>
<div class="toggle-setting-row">
<div class="toggle-setting-text">
<div class="toggle-setting-label">Weekly usage digest</div>
<div class="toggle-setting-desc">Email summary of invocations, errors and latency each Monday.</div>
</div>
<div class="toggle-sm on" onclick="this.classList.toggle('on')"></div>
</div>
<div class="field-hint" style="margin-top:12px;">Notification preferences are local UI placeholders for now and are not synced to the backend yet.</div>
</div>
</div>
</div>
<!-- ■ DANGER ZONE — moved to workspace-setup.html ■ -->
<div id="section-danger" style="display:none;">
<div class="section-card" style="border-color: rgba(248,81,73,0.25);">
<div class="section-card-header" style="border-bottom-color: rgba(248,81,73,0.15);">
<div class="section-card-title" style="color: var(--red);">Danger zone</div>
</div>
<div>
<div class="danger-zone-action">
<div class="danger-zone-text">
<div class="danger-zone-title">Rename workspace</div>
<div class="danger-zone-desc">Changes the workspace slug used in all API endpoints. Existing API keys will continue to work. All existing tool call references will break.</div>
</div>
<button class="btn-danger" id="settings-ws-rename-btn" type="button">Rename</button>
</div>
<div class="danger-zone-action">
<div class="danger-zone-text">
<div class="danger-zone-title">Export all data</div>
<div class="danger-zone-desc">Download a ZIP archive of all operation configs, schemas, mappings and invocation history as JSON. May take a few minutes.</div>
</div>
<button class="btn-danger" id="settings-ws-export-btn" type="button">Export</button>
</div>
<div class="danger-zone-action">
<div class="danger-zone-text">
<div class="danger-zone-title">Delete workspace</div>
<div class="danger-zone-desc">Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.</div>
</div>
<button class="btn-danger" id="settings-ws-delete-btn" type="button">Delete workspace</button>
</div>
</div>
</div>