mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
style(portal): strengthen workspace operator desk
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "0401fbaf26ce16d3985c1cca3d6116f9fc2f3cb72e756d1bb8bca91d0d8633e2",
|
||||
"source_hash": "46e32b3f10be76cc7ecaf28e84ea41b97ba549984641bbc401f9b9cd5484880a",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+43
@@ -912,10 +912,29 @@ header .logout-btn:hover,
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--line);
|
||||
transition:
|
||||
border-color 140ms ease,
|
||||
background 140ms ease,
|
||||
box-shadow 140ms ease;
|
||||
}
|
||||
.workspace-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.workspace-row:hover {
|
||||
background: #fbfcfe;
|
||||
}
|
||||
.workspace-row.selected {
|
||||
position: relative;
|
||||
border-color: rgba(11, 106, 114, 0.34);
|
||||
background: #eef8fa;
|
||||
box-shadow: inset 4px 0 0 var(--accent), 0 0 0 1px rgba(11, 106, 114, 0.12);
|
||||
}
|
||||
.workspace-row.selected .workspace-name {
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
.workspace-row.selected .workspace-row-note {
|
||||
color: var(--ink);
|
||||
}
|
||||
.workspace-row-primary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1066,6 +1085,30 @@ header .logout-btn:hover,
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.workspace-management-empty-checklist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.workspace-management-empty-check {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
.workspace-management-empty-check strong {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
}
|
||||
.workspace-management-empty-check span {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.workspace-management-empty-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
+6
-2
@@ -114,6 +114,10 @@
|
||||
if (!empty || !content || !title || !meta || !summary || !health || !lifecycle || !created || !guidance || !actionButton || !closeButton) return;
|
||||
var workspace = entry.selectedWorkspaceID ? findWorkspace(account, entry.selectedWorkspaceID) : null;
|
||||
var hasSelection = !!workspace;
|
||||
var rows = document.querySelectorAll("[data-workspace-row]");
|
||||
for (var i = 0; i < rows.length; i += 1) {
|
||||
rows[i].classList.toggle("selected", !!workspace && rows[i].getAttribute("data-workspace-row") === workspace.id);
|
||||
}
|
||||
panel.classList.add("visible");
|
||||
empty.hidden = hasSelection;
|
||||
content.hidden = !hasSelection;
|
||||
@@ -1805,7 +1809,7 @@
|
||||
if (account.can_manage && (state === "active" || state === "suspended" || state === "failed")) {
|
||||
manageAction = '<button type="button" class="btn-secondary btn-workspace-manage" data-action="select-workspace" data-account-id="' + escapeAttr(account.id) + '" data-workspace-id="' + escapeAttr(workspace.id) + '">Manage</button>';
|
||||
}
|
||||
return '<article class="workspace-row"><div class="workspace-row-primary"><div class="workspace-row-heading"><h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4><div class="workspace-meta">' + metaParts.join("") + '</div></div><div class="workspace-row-note">' + escapeHTML(workspaceRowNote(workspace)) + '</div></div><div class="workspace-row-status-cell workspace-row-status-cell-badge">' + healthBadgeHTML(workspace) + '</div><div class="workspace-row-status-cell workspace-row-status-cell-badge"><span class="badge badge-' + escapeHTML(state || "unknown") + '">' + escapeHTML(titleCase(state || "Unknown")) + '</span></div><div class="workspace-actions">' + openAction + manageAction + "</div></article>";
|
||||
return '<article class="workspace-row workspace-row-health-' + escapeAttr(status) + " workspace-row-state-" + escapeAttr(state || "unknown") + '" data-workspace-row="' + escapeAttr(workspace.id) + '"><div class="workspace-row-primary"><div class="workspace-row-heading"><h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4><div class="workspace-meta">' + metaParts.join("") + '</div></div><div class="workspace-row-note">' + escapeHTML(workspaceRowNote(workspace)) + '</div></div><div class="workspace-row-status-cell workspace-row-status-cell-badge">' + healthBadgeHTML(workspace) + '</div><div class="workspace-row-status-cell workspace-row-status-cell-badge"><span class="badge badge-' + escapeHTML(state || "unknown") + '">' + escapeHTML(titleCase(state || "Unknown")) + '</span></div><div class="workspace-actions">' + openAction + manageAction + "</div></article>";
|
||||
}
|
||||
function renderAccountOverviewSection(account) {
|
||||
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
|
||||
@@ -1844,7 +1848,7 @@
|
||||
workspaceDeskActions += '<button type="button" class="btn-secondary btn-compact" data-action="open-billing" data-account-id="' + escapeAttr(account.id) + '">Manage billing</button>';
|
||||
}
|
||||
workspaceDeskActions += '<button type="button" class="btn-secondary btn-compact" data-action="toggle-team" data-account-id="' + escapeAttr(account.id) + '" data-shell-target="team">Manage team</button>';
|
||||
workspaceManagement = '<section class="workspace-management-panel" id="workspace-management-' + escapeAttr(account.id) + '"><div class="workspace-management-header"><div><div class="account-panel-kicker">Workspace management</div><h3>Review one workspace at a time</h3><p>Select a workspace from the fleet to review its lifecycle state and run explicit management actions.</p></div><button type="button" class="btn-secondary btn-compact" id="workspace-management-close-' + escapeAttr(account.id) + '" data-action="clear-workspace-selection" data-account-id="' + escapeAttr(account.id) + '">Done</button></div><div class="workspace-management-empty" id="workspace-management-empty-' + escapeAttr(account.id) + '"><div class="workspace-management-empty-copy">Choose a workspace from the fleet to open its management desk.</div><div class="workspace-management-empty-grid"><div class="workspace-management-empty-card"><div class="account-panel-kicker">Account desk</div><h4>Keep account-wide actions close</h4><p>Use the management desk for workspace-level decisions, and keep billing or team changes one click away.</p><div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div></div><div class="workspace-management-empty-card workspace-management-empty-card-muted"><div class="account-panel-kicker">What shows here</div><ul class="workspace-management-empty-list"><li>Lifecycle and health summary for the selected workspace</li><li>The next explicit lifecycle action for that workspace</li><li>Account-level actions when you need billing or team changes instead</li></ul></div></div></div><div class="workspace-management-content" id="workspace-management-content-' + escapeAttr(account.id) + '" hidden><div class="workspace-management-meta" id="workspace-management-meta-' + escapeAttr(account.id) + '"></div><h4 id="workspace-management-title-' + escapeAttr(account.id) + '"></h4><p class="workspace-management-summary" id="workspace-management-summary-' + escapeAttr(account.id) + '"></p><div class="workspace-management-facts"><div class="workspace-management-fact"><span>Health</span><strong id="workspace-management-health-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Lifecycle</span><strong id="workspace-management-lifecycle-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Created</span><strong id="workspace-management-created-' + escapeAttr(account.id) + '"></strong></div></div><div class="workspace-management-guidance" id="workspace-management-guidance-' + escapeAttr(account.id) + '"></div><div class="workspace-management-actions"><button type="button" class="btn-danger" id="workspace-management-action-' + escapeAttr(account.id) + '" data-action="workspace-action" data-account-id="' + escapeAttr(account.id) + '">Manage workspace</button></div></div></section>';
|
||||
workspaceManagement = '<section class="workspace-management-panel" id="workspace-management-' + escapeAttr(account.id) + '"><div class="workspace-management-header"><div><div class="account-panel-kicker">Workspace management</div><h3>Review one workspace at a time</h3><p>Select a workspace from the fleet to review its lifecycle state and run explicit management actions.</p></div><button type="button" class="btn-secondary btn-compact" id="workspace-management-close-' + escapeAttr(account.id) + '" data-action="clear-workspace-selection" data-account-id="' + escapeAttr(account.id) + '">Done</button></div><div class="workspace-management-empty" id="workspace-management-empty-' + escapeAttr(account.id) + '"><div class="workspace-management-empty-copy">Choose a workspace from the fleet to open its management desk.</div><div class="workspace-management-empty-grid"><div class="workspace-management-empty-card"><div class="account-panel-kicker">Account desk</div><h4>Keep account-wide actions close</h4><p>Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.</p><div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div></div><div class="workspace-management-empty-card workspace-management-empty-card-muted"><div class="account-panel-kicker">Review flow</div><div class="workspace-management-empty-checklist"><div class="workspace-management-empty-check"><strong>1. Pick the workspace</strong><span>Select a fleet row to bring its health, lifecycle, and next action into this desk.</span></div><div class="workspace-management-empty-check"><strong>2. Check posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div><div class="workspace-management-empty-check"><strong>3. Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div></div></div></div></div><div class="workspace-management-content" id="workspace-management-content-' + escapeAttr(account.id) + '" hidden><div class="workspace-management-meta" id="workspace-management-meta-' + escapeAttr(account.id) + '"></div><h4 id="workspace-management-title-' + escapeAttr(account.id) + '"></h4><p class="workspace-management-summary" id="workspace-management-summary-' + escapeAttr(account.id) + '"></p><div class="workspace-management-facts"><div class="workspace-management-fact"><span>Health</span><strong id="workspace-management-health-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Lifecycle</span><strong id="workspace-management-lifecycle-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Created</span><strong id="workspace-management-created-' + escapeAttr(account.id) + '"></strong></div></div><div class="workspace-management-guidance" id="workspace-management-guidance-' + escapeAttr(account.id) + '"></div><div class="workspace-management-actions"><button type="button" class="btn-danger" id="workspace-management-action-' + escapeAttr(account.id) + '" data-action="workspace-action" data-account-id="' + escapeAttr(account.id) + '">Manage workspace</button></div></div></section>';
|
||||
}
|
||||
var workspaceHTML = workspaces.length ? '<div class="workspace-list-wrap"><div class="workspace-list-toolbar"><div class="workspace-list-summary">Review the hosted fleet, open a workspace, and keep lifecycle actions explicit.</div><div class="workspace-list-stats"><span class="workspace-list-stat"><strong>' + String(workspaces.length) + '</strong> total</span><span class="workspace-list-stat"><strong>' + String(healthyCount) + '</strong> healthy</span><span class="workspace-list-stat"><strong>' + String(checkingCount) + '</strong> checking</span><span class="workspace-list-stat workspace-list-stat-attention"><strong>' + String(unhealthyCount) + '</strong> needs attention</span></div></div><div class="workspace-list-head"><span>Workspace</span><span>Health</span><span>Lifecycle</span><span>Actions</span></div><div class="workspace-list">' + workspaces.map(function(workspace) {
|
||||
return renderWorkspaceCard(account, workspace, accountAPIBasePath);
|
||||
|
||||
@@ -136,6 +136,10 @@ export function renderWorkspaceManagement(account: PortalAccountSummary, entry:
|
||||
|
||||
var workspace = entry.selectedWorkspaceID ? findWorkspace(account, entry.selectedWorkspaceID) : null;
|
||||
var hasSelection = !!workspace;
|
||||
var rows = document.querySelectorAll<HTMLElement>('[data-workspace-row]');
|
||||
for (var i = 0; i < rows.length; i += 1) {
|
||||
rows[i].classList.toggle('selected', !!workspace && rows[i].getAttribute('data-workspace-row') === workspace.id);
|
||||
}
|
||||
panel.classList.add('visible');
|
||||
empty.hidden = hasSelection;
|
||||
content.hidden = !hasSelection;
|
||||
|
||||
@@ -166,8 +166,9 @@ describe('shell view', function() {
|
||||
expect(html).toContain('data-action="select-workspace"');
|
||||
expect(html).toContain('Workspace management');
|
||||
expect(html).toContain('Choose a workspace from the fleet to open its management desk.');
|
||||
expect(html).toContain('What shows here');
|
||||
expect(html).toContain('Lifecycle and health summary for the selected workspace');
|
||||
expect(html).toContain('Review flow');
|
||||
expect(html).toContain('Pick the workspace');
|
||||
expect(html).toContain('Escalate account changes separately');
|
||||
expect(html).toContain('data-action="clear-workspace-selection"');
|
||||
expect(html).toContain('Team management');
|
||||
expect(html).toContain('Invite someone new');
|
||||
|
||||
@@ -309,7 +309,7 @@ function renderWorkspaceCard(account: PortalAccountSummary, workspace: PortalWor
|
||||
}
|
||||
|
||||
return (
|
||||
'<article class="workspace-row">' +
|
||||
'<article class="workspace-row workspace-row-health-' + escapeAttr(status) + ' workspace-row-state-' + escapeAttr(state || 'unknown') + '" data-workspace-row="' + escapeAttr(workspace.id) + '">' +
|
||||
'<div class="workspace-row-primary">' +
|
||||
'<div class="workspace-row-heading">' +
|
||||
'<h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4>' +
|
||||
@@ -511,16 +511,16 @@ function renderAccountWorkspaceSection(account: PortalAccountSummary, accountAPI
|
||||
'<div class="workspace-management-empty-card">' +
|
||||
'<div class="account-panel-kicker">Account desk</div>' +
|
||||
'<h4>Keep account-wide actions close</h4>' +
|
||||
'<p>Use the management desk for workspace-level decisions, and keep billing or team changes one click away.</p>' +
|
||||
'<p>Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.</p>' +
|
||||
'<div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-management-empty-card workspace-management-empty-card-muted">' +
|
||||
'<div class="account-panel-kicker">What shows here</div>' +
|
||||
'<ul class="workspace-management-empty-list">' +
|
||||
'<li>Lifecycle and health summary for the selected workspace</li>' +
|
||||
'<li>The next explicit lifecycle action for that workspace</li>' +
|
||||
'<li>Account-level actions when you need billing or team changes instead</li>' +
|
||||
'</ul>' +
|
||||
'<div class="account-panel-kicker">Review flow</div>' +
|
||||
'<div class="workspace-management-empty-checklist">' +
|
||||
'<div class="workspace-management-empty-check"><strong>1. Pick the workspace</strong><span>Select a fleet row to bring its health, lifecycle, and next action into this desk.</span></div>' +
|
||||
'<div class="workspace-management-empty-check"><strong>2. Check posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div>' +
|
||||
'<div class="workspace-management-empty-check"><strong>3. Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
|
||||
@@ -1017,12 +1017,32 @@ header .logout-btn:hover,
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--line);
|
||||
transition: border-color 140ms ease, background 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
.workspace-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.workspace-row:hover {
|
||||
background: #fbfcfe;
|
||||
}
|
||||
|
||||
.workspace-row.selected {
|
||||
position: relative;
|
||||
border-color: rgba(11, 106, 114, 0.34);
|
||||
background: #eef8fa;
|
||||
box-shadow: inset 4px 0 0 var(--accent), 0 0 0 1px rgba(11, 106, 114, 0.12);
|
||||
}
|
||||
|
||||
.workspace-row.selected .workspace-name {
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.workspace-row.selected .workspace-row-note {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.workspace-row-primary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1198,6 +1218,34 @@ header .logout-btn:hover,
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.workspace-management-empty-checklist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workspace-management-empty-check {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.workspace-management-empty-check strong {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.workspace-management-empty-check span {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.workspace-management-empty-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
Reference in New Issue
Block a user