diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json index 3609eaccc2..677ac339ee 100644 --- a/internal/cloudcp/portal/dist/build_manifest.json +++ b/internal/cloudcp/portal/dist/build_manifest.json @@ -1,5 +1,5 @@ { - "source_hash": "02f5d00566a4d55ae7999d1121d77557d884ffcedd01d2a278a62f90d61be7a4", + "source_hash": "f5ca96d0cb8760a1108b02f8b7f81a91bf85aa857f46f9317c753d304032ad63", "build_inputs": [ "package.json", "tsconfig.json", diff --git a/internal/cloudcp/portal/dist/portal_app.css b/internal/cloudcp/portal/dist/portal_app.css index 731c612ab6..a2a0894c77 100644 --- a/internal/cloudcp/portal/dist/portal_app.css +++ b/internal/cloudcp/portal/dist/portal_app.css @@ -603,6 +603,58 @@ header .logout-btn:hover, .account-stage-header { padding: 2px 4px 0; } +.workspace-list-wrap { + display: flex; + flex-direction: column; + gap: 8px; +} +.workspace-list-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 4px 4px 10px; +} +.workspace-list-summary { + font-size: 13px; + line-height: 1.55; + color: var(--ink-soft); + max-width: 720px; +} +.workspace-list-stats { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.workspace-list-stat { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + background: var(--panel-muted); + color: var(--ink-soft); + font-size: 12px; +} +.workspace-list-stat strong { + margin-right: 5px; + color: var(--ink); +} +.workspace-list-stat-attention strong { + color: var(--danger); +} +.workspace-list-head { + display: grid; + grid-template-columns: minmax(0, 1.55fr) 120px 120px 180px; + gap: 14px; + padding: 0 16px 6px; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ink-muted); +} .workspace-list { display: flex; flex-direction: column; @@ -804,6 +856,17 @@ header .logout-btn:hover, flex-direction: column; gap: 10px; } +.team-roster-head { + display: grid; + grid-template-columns: minmax(0, 1fr) 120px auto; + gap: 16px; + padding: 0 8px 2px; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--ink-muted); +} .team-member-row { display: grid; grid-template-columns: minmax(0, 1fr) 120px auto; @@ -835,6 +898,7 @@ header .logout-btn:hover, justify-content: end; gap: 10px; flex-wrap: wrap; + min-width: 0; } .team-member-role-summary { font-size: 12px; @@ -885,26 +949,45 @@ header .logout-btn:hover, flex-direction: column; gap: 12px; } -.service-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 12px; -} -.service-card { +.service-action-list { display: flex; flex-direction: column; - gap: 8px; - padding: 16px; + gap: 10px; } -.service-card-button { - width: 100%; - text-align: left; - cursor: pointer; +.service-action-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 16px; + align-items: center; + padding: 16px 18px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); } -.service-card:hover, -.service-card-button:hover { - border-color: var(--accent); - box-shadow: var(--shadow-sm); +.service-action-copy { + display: flex; + flex-direction: column; + gap: 6px; +} +.service-action-copy h3 { + margin: 0; + font-size: 19px; + line-height: 1.08; + letter-spacing: -0.03em; +} +.service-action-copy p { + margin: 0; + font-size: 14px; + line-height: 1.6; + color: var(--ink-soft); + max-width: 760px; +} +.service-action-cta { + display: flex; + align-items: center; +} +.service-action-button { + min-width: 92px; } .service-panel { padding: 22px; @@ -1090,6 +1173,9 @@ header .logout-btn:hover, .workspace-management-content { grid-template-columns: 1fr; } + .workspace-list-head { + display: none; + } } @media (max-width: 920px) { .portal-hero { @@ -1099,14 +1185,14 @@ header .logout-btn:hover, .portal-hero-stats { grid-template-columns: 1fr; } - .workspace-list, - .service-grid { - grid-template-columns: 1fr; - } .workspace-row, .workspace-row-main { grid-template-columns: 1fr; } + .workspace-list-toolbar, + .service-action-row { + grid-template-columns: 1fr; + } } @media (max-width: 720px) { header { @@ -1146,6 +1232,7 @@ header .logout-btn:hover, width: 100%; } .team-member-row, + .team-roster-head, .team-member-controls { grid-template-columns: 1fr; align-items: stretch; diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js index d4f4cc557e..df220ddbff 100644 --- a/internal/cloudcp/portal/dist/portal_app.js +++ b/internal/cloudcp/portal/dist/portal_app.js @@ -10,6 +10,8 @@ return "Tech"; case "read_only": return "Read-only"; + case "member": + return "Member"; default: return role || "Member"; } @@ -176,6 +178,14 @@ row.appendChild(controls); return row; } + function ensureRosterHead(container) { + var existing = container.querySelector(".team-roster-head"); + if (existing) return; + var head = document.createElement("div"); + head.className = "team-roster-head"; + head.innerHTML = "OperatorAccessControls"; + container.appendChild(head); + } function renderAddWorkspaceSection(accountID, entry) { var form = getElement("add-ws-form-" + accountID); var spinner = getElement("ws-spinner-" + accountID); @@ -209,6 +219,7 @@ return; } roster.textContent = ""; + ensureRosterHead(roster); for (var i = 0; i < entry.teamQuery.data.length; i += 1) { var member = entry.teamQuery.data[i]; roster.appendChild(renderTeamMemberRow(accountID, member, isOwner)); @@ -1646,6 +1657,9 @@ } return 'Checking'; } + function renderServiceActionRow(id, kicker, title, description, panelID, focusID) { + return '
" + description + '
Select a workspace from the fleet to review its lifecycle state and run explicit management actions.
No hosted workspaces yet. Create one to get started.
Review fleet health, move into a workspace, and keep lifecycle actions explicit.
No hosted workspaces yet. Create one to get started.
Use the fleet view to open workspaces, watch health posture, and keep management actions explicit.
Owners manage billing and access. Admins and techs keep the hosted fleet running day to day.
Keep the roster small and role assignment explicit. The people listed here are the ones who can operate the hosted fleet.
Add another operator with the minimum role they need for this account.
" + (hosted ? "Use this area for workspace access, fleet operations, hosted billing, and team management." : "This account does not currently have hosted workspace access. If that is unexpected, contact support while using the commercial tools below.") + '
Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.
" + (hosted ? "Use this area for workspace access, fleet operations, hosted billing, and team management." : "This account does not currently have hosted workspace access. If that is unexpected, contact support while using the commercial tools below.") + '
Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.
' + description + '
' + + 'No hosted workspaces yet. Create one to get started.
Review fleet health, move into a workspace, and keep lifecycle actions explicit.
' + + 'Use the fleet view to open workspaces, watch health posture, and keep management actions explicit.
' + '' + (hosted ? 'Use this area for workspace access, fleet operations, hosted billing, and team management.' : 'This account does not currently have hosted workspace access. If that is unexpected, contact support while using the commercial tools below.') + '
' + @@ -577,27 +619,11 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string '