auth-yes/scratch/jules_ui_latest.patch

1290 lines
58 KiB
Diff

diff --git a/ui/components/SessionsPage.tsx b/ui/components/SessionsPage.tsx
index de74c47..a4aaddb 100644
--- a/ui/components/SessionsPage.tsx
+++ b/ui/components/SessionsPage.tsx
@@ -145,15 +145,6 @@ export const SessionsPage = ({
</div>
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
- <button
- type="button"
- class="btn-outline"
- style="min-height: 42px; display: inline-flex; align-items: center; gap: 0.4rem;"
- onclick="openEventDrawer()"
- >
- <span style="font-size: 1.1rem;">🎟️</span>
- <span>+ Create Workshop Pass</span>
- </button>
<button
type="button"
class="btn-primary"
@@ -161,659 +152,664 @@ export const SessionsPage = ({
onclick="openDelegateDrawer()"
>
<span style="font-size: 1.1rem;">🔑</span>
- <span>+ Delegate 1:1 Pass / Session</span>
+ <span>+ Delegate Session</span>
</button>
</div>
</div>
- {/* Create Multi-Claim Event Pass Drawer */}
+ {/* Delegate Session Drawer */}
<div
- id="eventDrawer"
+ id="delegateDrawer"
class="card"
- style="display: none; border-left: 4px solid var(--success); margin-bottom: 1.5rem;"
+ style="display: none; border-left: 4px solid var(--primary); margin-bottom: 1.5rem;"
>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
- <div>
- <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
- Create Multi-Claim Workshop / Event Pass
- </h3>
- <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0;">
- Generate a shared event pass with a universal 6-digit PIN, vanity
- link (<code style="font-size: 0.8rem;">/e/slug</code>), and CLI
- 1-liner for workshops, hackathons, and multi-user kiosks.
- </p>
- </div>
- <button
- type="button"
- onclick="closeEventDrawer()"
- style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
- >
- &times;
- </button>
- </div>
-
- <form
- id="eventForm"
- onsubmit="handleCreateEvent(event)"
- style="margin-top: 1rem;"
- >
- {/* Event Name */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
- Workshop / Event Name *
- </label>
- <input
- type="text"
- id="eventName"
- placeholder="e.g. Deno & SPIRE Zero-Trust Workshop, Elite Fleet Live Demo"
- required
- style="width: 100%;"
- />
- </div>
-
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.25rem;">
- {/* Target App */}
- <div>
- <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
- Target App Destination
- </label>
- <select
- id="eventAppId"
- style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
- >
- <option value="">Universal Sandbox (Default)</option>
- {apps.map((app) => (
- <option value={app.id}>
- {app.name} ({app.domain || "Internal"})
- </option>
- ))}
- </select>
- </div>
-
- {/* Guest Role */}
- <div>
- <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
- Attendee Access Role
- </label>
- <select
- id="eventRole"
- style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
- >
- <option value="viewer">Viewer (Read-Only Demo)</option>
- <option value="operator">Operator (Workshop Actions)</option>
- <option value="user">Standard User</option>
- </select>
- </div>
- </div>
-
- {/* Seat Capacity Presets */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
- Seat Capacity Limit
- </label>
- <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
- <button
- type="button"
- class="pill-btn seat-pill"
- onclick="selectSeats(this, 25)"
- >
- 25 Seats
- </button>
- <button
- type="button"
- class="pill-btn seat-pill active"
- onclick="selectSeats(this, 50)"
- >
- 50 Seats
- </button>
+ <div style="width: 100%;">
+ <div style="display: flex; justify-content: space-between; align-items: center;">
+ <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
+ Create New Pass / Session
+ </h3>
<button
type="button"
- class="pill-btn seat-pill"
- onclick="selectSeats(this, 100)"
+ onclick="closeDelegateDrawer()"
+ style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
>
- 100 Seats
+ &times;
</button>
- <button
- type="button"
- class="pill-btn seat-pill"
- onclick="selectSeats(this, 0)"
- >
- ∞ Unlimited
- </button>
- <input
- type="number"
- id="eventMaxSeats"
- value="50"
- min="0"
- max="10000"
- style="width: 100px; padding: 0.35rem 0.5rem; text-align: center;"
- title="Max seats (0 = unlimited)"
- />
</div>
- </div>
- {/* Duration Presets */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
- Event Duration & Expiration
- </label>
- <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
- <button
- type="button"
- class="pill-btn event-lifespan-pill"
- onclick="selectEventLifespan(this, 1)"
- >
- ⚡ 1 Hour
- </button>
+ <div style="display: flex; background: var(--surface-muted); padding: 4px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); gap: 4px; margin-top: 1rem; margin-bottom: 1rem;">
<button
type="button"
- class="pill-btn event-lifespan-pill active"
- onclick="selectEventLifespan(this, 3)"
+ id="tabBtnDirectPass"
+ class="delegate-tab-btn active"
+ onclick="switchDelegateTab('tabDirectPass')"
>
- 🛠️ 3 Hours (Standard Workshop)
+ 🔑 1:1 Direct Pass
</button>
<button
type="button"
- class="pill-btn event-lifespan-pill"
- onclick="selectEventLifespan(this, 12)"
+ id="tabBtnWorkshopPass"
+ class="delegate-tab-btn"
+ onclick="switchDelegateTab('tabWorkshopPass')"
>
- 📅 12 Hours (All-Day Hackathon)
- </button>
- <button
- type="button"
- class="pill-btn event-lifespan-pill"
- onclick="selectEventLifespan(this, 24)"
- >
- 🗓️ 24 Hours
+ 🎟️ Multi-Claim Workshop Pass
</button>
</div>
- <input type="hidden" id="eventLifespanHours" value="3" />
</div>
+ </div>
- {/* Custom Codes Accordion (Optional) */}
- <details style="margin-bottom: 1.5rem; background: var(--surface-muted); padding: 0.75rem 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle);">
- <summary style="font-size: 0.875rem; font-weight: 600; color: var(--text-primary); cursor: pointer;">
- ▸ Custom Vanity Slug & Custom PIN Code (Optional)
- </summary>
- <div style="margin-top: 0.75rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
- <div>
- <label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
- Vanity Slug (e.g. /e/deno-lab)
- </label>
- <input
- type="text"
- id="eventSlug"
- placeholder="auto-generated if blank"
- style="width: 100%; font-size: 0.85rem;"
- />
- </div>
- <div>
- <label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
- Custom 6-Digit PIN (e.g. 749-123)
- </label>
- <input
- type="text"
- id="eventPinCode"
- placeholder="auto-generated if blank"
- style="width: 100%; font-size: 0.85rem;"
- />
- </div>
- </div>
- </details>
+ <div id="tabDirectPass">
+ <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;">
+ Mint an isolated, scoped child session for 1-click magic links, CLI
+ tools, team members, or AI assistants without exposing your primary
+ passkeys.
+ </p>
- <div style="display: flex; gap: 0.75rem;">
- <button
- type="submit"
- id="submitEventBtn"
- class="btn-primary"
- style="min-height: 42px; background: var(--success); border-color: var(--success);"
- >
- 🎟️ Launch Workshop Pass
- </button>
- <button
- type="button"
- class="btn-outline"
- onclick="closeEventDrawer()"
- style="min-height: 42px;"
- >
- Cancel
- </button>
- </div>
- </form>
+ <form
+ id="delegateForm"
+ onsubmit="handleDelegateSession(event)"
+ style="margin-top: 1rem;"
+ >
+ {/* Label Input */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Session Label / Purpose *
+ </label>
+ <input
+ type="text"
+ id="delegateLabel"
+ placeholder="e.g. Friend Demo Pass, Antigravity Assistant, CI/CD Runner, Terminal CLI"
+ required
+ style="width: 100%;"
+ />
+ </div>
- {/* Event Launch Success Modal / Card */}
- <div
- id="eventHandoffModal"
- style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 2px solid var(--success); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
- >
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
- <div style="display: flex; align-items: center; gap: 0.5rem;">
- <span style="font-size: 1.3rem;">🎟️</span>
- <strong
- id="createdEventTitle"
- style="color: var(--text-primary); font-size: 1.1rem;"
+ {/* Direct 1-Click Destination (App Selection) */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
+ 1-Click Destination App (Optional)
+ </label>
+ <select
+ id="delegateTargetApp"
+ onchange="handleTargetAppChange(this.value)"
+ style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
- Workshop Pass Live!
- </strong>
+ <option value="">Auth-Yes Central Dashboard (Default)</option>
+ {apps.map((app) => (
+ <option value={app.name}>
+ {app.name} ({app.domain || "Internal App"})
+ </option>
+ ))}
+ </select>
+ <p style="font-size: 0.75rem; color: var(--text-muted); margin: 0.35rem 0 0 0;">
+ Selecting an app scopes the pass and automatically redirects the
+ recipient to that app upon 1-click redemption.
+ </p>
</div>
- <span class="badge badge-success">Active Now</span>
- </div>
- <p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
- Share the 6-digit PIN, 1-click link, or CLI 1-liner with attendees.
- You can monitor live claimed seats and trigger the master kill
- switch from the Event Cockpit below.
- </p>
-
- <div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem;">
- {/* PIN Code */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- 6-Digit Universal PIN (for /join or Kiosk Entry)
+ {/* Lifespan Presets */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Lifespan & Auto-Expiration
</label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="eventHandoffPin"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 1.1rem; text-align: center; letter-spacing: 3px; font-weight: 700; color: var(--primary);"
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
+ <button
+ type="button"
+ class="pill-btn lifespan-pill active"
+ data-hours="1"
+ onclick="selectLifespan(this, 1)"
>
- </code>
+ ⚡ 1 Hour (Quick Task)
+ </button>
<button
type="button"
- class="btn-primary"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyEventHandoff('pin')"
+ class="pill-btn lifespan-pill"
+ data-hours="12"
+ onclick="selectLifespan(this, 12)"
>
- Copy PIN
+ 🛠️ 12 Hours (Work Session)
</button>
- </div>
- </div>
-
- {/* 1-Click Vanity Link */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- Direct 1-Click Workshop Entrance URL
- </label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="eventHandoffLink"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
+ <button
+ type="button"
+ class="pill-btn lifespan-pill"
+ data-hours="24"
+ onclick="selectLifespan(this, 24)"
>
- </code>
+ 📅 24 Hours
+ </button>
<button
type="button"
- class="btn-outline"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyEventHandoff('link')"
+ class="pill-btn lifespan-pill"
+ data-hours="168"
+ onclick="selectLifespan(this, 168)"
>
- Copy URL
+ 🗓️ 7 Days
</button>
</div>
+ <input type="hidden" id="delegateHours" value="1" />
</div>
- {/* CLI 1-Liner */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- Terminal 1-Liner (Environment Injector)
+ {/* Access Mode Presets */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Access Mode & Permissions
</label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="eventHandoffCli"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.8rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
+ <button
+ type="button"
+ class="pill-btn mode-pill active"
+ data-mode="read_only"
+ onclick="selectMode(this, 'read_only')"
+ >
+ 👁️ Read-Only (Audit & Inspection)
+ </button>
+ <button
+ type="button"
+ class="pill-btn mode-pill"
+ data-mode="operator"
+ onclick="selectMode(this, 'operator')"
>
- </code>
+ ⚡ Operator (App Actions)
+ </button>
+ {isAdmin && (
+ <button
+ type="button"
+ class="pill-btn mode-pill"
+ data-mode="admin"
+ onclick="selectMode(this, 'admin')"
+ >
+ 👑 Full Admin
+ </button>
+ )}
<button
type="button"
- class="btn-outline"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyEventHandoff('cli')"
+ class="pill-btn mode-pill"
+ data-mode="custom"
+ onclick="selectMode(this, 'custom')"
>
- Copy 1-Liner
+ 🛠️ Custom Scopes
</button>
</div>
+ <input type="hidden" id="delegateMode" value="read_only" />
</div>
- </div>
- <button
- type="button"
- class="btn-outline"
- style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
- onclick="closeEventHandoffModal()"
- >
- Done & Refresh Cockpit
- </button>
- </div>
- </div>
-
- {/* Delegate Session Drawer */}
- <div
- id="delegateDrawer"
- class="card"
- style="display: none; border-left: 4px solid var(--primary); margin-bottom: 1.5rem;"
- >
- <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
- <div>
- <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
- Delegate New 1:1 Pass / Session
- </h3>
- <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0;">
- Mint an isolated, scoped child session for 1-click magic links,
- CLI tools, team members, or AI assistants without exposing your
- primary passkeys.
- </p>
- </div>
- <button
- type="button"
- onclick="closeDelegateDrawer()"
- style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
- >
- &times;
- </button>
- </div>
-
- <form
- id="delegateForm"
- onsubmit="handleDelegateSession(event)"
- style="margin-top: 1rem;"
- >
- {/* Label Input */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
- Session Label / Purpose *
- </label>
- <input
- type="text"
- id="delegateLabel"
- placeholder="e.g. Friend Demo Pass, Antigravity Assistant, CI/CD Runner, Terminal CLI"
- required
- style="width: 100%;"
- />
- </div>
-
- {/* Direct 1-Click Destination (App Selection) */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
- 1-Click Destination App (Optional)
- </label>
- <select
- id="delegateTargetApp"
- onchange="handleTargetAppChange(this.value)"
- style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
+ {/* Progressive Disclosure: Specific App & Capability Scopes */}
+ <details
+ id="customScopesSection"
+ style="margin-bottom: 1.25rem; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 0.75rem 1rem; background: var(--surface-muted);"
>
- <option value="">Auth-Yes Central Dashboard (Default)</option>
- {apps.map((app) => (
- <option value={app.name}>
- {app.name} ({app.domain || "Internal App"})
- </option>
- ))}
- </select>
- <p style="font-size: 0.75rem; color: var(--text-muted); margin: 0.35rem 0 0 0;">
- Selecting an app scopes the pass and automatically redirects the
- recipient to that app upon 1-click redemption.
- </p>
- </div>
+ <summary style="cursor: pointer; font-weight: 600; font-size: 0.875rem; color: var(--text-primary);">
+ ▸ Customize Specific App Grants & Capabilities (Optional)
+ </summary>
+ <div style="margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;">
+ <p style="margin: 0 0 0.5rem 0; font-size: 0.8rem; color: var(--text-secondary);">
+ Select which subsidiary microservices and IAM capabilities
+ this delegated token is permitted to access:
+ </p>
- {/* Lifespan Presets */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
- Lifespan & Auto-Expiration
- </label>
- <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
- <button
- type="button"
- class="pill-btn lifespan-pill active"
- data-hours="1"
- onclick="selectLifespan(this, 1)"
- >
- ⚡ 1 Hour (Quick Task)
- </button>
- <button
- type="button"
- class="pill-btn lifespan-pill"
- data-hours="12"
- onclick="selectLifespan(this, 12)"
- >
- 🛠️ 12 Hours (Work Session)
- </button>
- <button
- type="button"
- class="pill-btn lifespan-pill"
- data-hours="24"
- onclick="selectLifespan(this, 24)"
- >
- 📅 24 Hours
- </button>
- <button
- type="button"
- class="pill-btn lifespan-pill"
- data-hours="168"
- onclick="selectLifespan(this, 168)"
- >
- 🗓️ 7 Days
- </button>
- </div>
- <input type="hidden" id="delegateHours" value="1" />
- </div>
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
+ <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
+ <input
+ type="checkbox"
+ class="scope-checkbox"
+ value="read:audit"
+ checked
+ />
+ Read Audit Ledger
+ </label>
+ <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
+ <input
+ type="checkbox"
+ class="scope-checkbox"
+ value="read:users"
+ checked
+ />
+ Read Users List
+ </label>
+ <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
+ <input
+ type="checkbox"
+ class="scope-checkbox"
+ value="read:apps"
+ checked
+ />
+ Read Apps Registry
+ </label>
+ {apps.map((app) => (
+ <label
+ key={app.id}
+ style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
+ >
+ <input
+ type="checkbox"
+ class="scope-checkbox"
+ value={`app:${app.name}`}
+ />
+ {app.name} ({app.domain || "Internal"})
+ </label>
+ ))}
+ </div>
+ </div>
+ </details>
- {/* Access Mode Presets */}
- <div style="margin-bottom: 1.25rem;">
- <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
- Access Mode & Permissions
- </label>
- <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
- <button
- type="button"
- class="pill-btn mode-pill active"
- data-mode="read_only"
- onclick="selectMode(this, 'read_only')"
- >
- 👁️ Read-Only (Audit & Inspection)
- </button>
+ <div style="display: flex; gap: 0.75rem;">
<button
- type="button"
- class="pill-btn mode-pill"
- data-mode="operator"
- onclick="selectMode(this, 'operator')"
+ type="submit"
+ id="submitDelegateBtn"
+ class="btn-primary"
+ style="min-height: 42px;"
>
- ⚡ Operator (App Actions)
+ Mint & Delegate Session
</button>
- {isAdmin && (
- <button
- type="button"
- class="pill-btn mode-pill"
- data-mode="admin"
- onclick="selectMode(this, 'admin')"
- >
- 👑 Full Admin
- </button>
- )}
<button
type="button"
- class="pill-btn mode-pill"
- data-mode="custom"
- onclick="selectMode(this, 'custom')"
+ class="btn-outline"
+ onclick="closeDelegateDrawer()"
+ style="min-height: 42px;"
>
- 🛠️ Custom Scopes
+ Cancel
</button>
</div>
- <input type="hidden" id="delegateMode" value="read_only" />
- </div>
+ </form>
- {/* Progressive Disclosure: Specific App & Capability Scopes */}
- <details
- id="customScopesSection"
- style="margin-bottom: 1.25rem; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 0.75rem 1rem; background: var(--surface-muted);"
+ {/* Hand-Off Card (Revealed upon generation) */}
+ <div
+ id="handoffModal"
+ style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 1px solid var(--primary); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
- <summary style="cursor: pointer; font-weight: 600; font-size: 0.875rem; color: var(--text-primary);">
- ▸ Customize Specific App Grants & Capabilities (Optional)
- </summary>
- <div style="margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;">
- <p style="margin: 0 0 0.5rem 0; font-size: 0.8rem; color: var(--text-secondary);">
- Select which subsidiary microservices and IAM capabilities this
- delegated token is permitted to access:
- </p>
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
+ <span style="font-size: 1.2rem;">🔑</span>
+ <strong style="color: var(--text-primary); font-size: 1rem;">
+ Session Delegated Successfully!
+ </strong>
+ </div>
+ <span class="badge badge-success">Active Now</span>
+ </div>
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
- <input
- type="checkbox"
- class="scope-checkbox"
- value="read:audit"
- checked
- />
- Read Audit Ledger
+ <p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
+ Copy this token, 1-click magic link, or CLI export string. You can
+ revoke or extend this session anytime from the active session
+ cards below.
+ </p>
+
+ <div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem;">
+ {/* 1-Click Magic Link */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ 1-Click Magic Link (Web / Friend / Interview)
</label>
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
- <input
- type="checkbox"
- class="scope-checkbox"
- value="read:users"
- checked
- />
- Read Users List
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="handoffMagicLinkText"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
+ >
+ </code>
+ <button
+ type="button"
+ class="btn-primary"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyHandoff('magic')"
+ >
+ Copy Link
+ </button>
+ </div>
+ </div>
+
+ {/* 1-Tap Copy CLI */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ Terminal Environment Export (CLI)
</label>
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
- <input
- type="checkbox"
- class="scope-checkbox"
- value="read:apps"
- checked
- />
- Read Apps Registry
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="handoffCliText"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
+ >
+ </code>
+ <button
+ type="button"
+ class="btn-outline"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyHandoff('cli')"
+ >
+ Copy CLI
+ </button>
+ </div>
+ </div>
+
+ {/* 1-Tap Copy cURL Header */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ HTTP Authorization Header
</label>
- {apps.map((app) => (
- <label
- key={app.id}
- style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="handoffCurlText"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
- <input
- type="checkbox"
- class="scope-checkbox"
- value={`app:${app.name}`}
- />
- {app.name} ({app.domain || "Internal"})
- </label>
- ))}
+ </code>
+ <button
+ type="button"
+ class="btn-outline"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyHandoff('curl')"
+ >
+ Copy Header
+ </button>
+ </div>
</div>
</div>
- </details>
- <div style="display: flex; gap: 0.75rem;">
- <button
- type="submit"
- id="submitDelegateBtn"
- class="btn-primary"
- style="min-height: 42px;"
- >
- Mint & Delegate Session
- </button>
<button
type="button"
class="btn-outline"
- onclick="closeDelegateDrawer()"
- style="min-height: 42px;"
+ style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
+ onclick="closeHandoffModal()"
>
- Cancel
+ Done (Session is Active)
</button>
</div>
- </form>
-
- {/* Hand-Off Card (Revealed upon generation) */}
- <div
- id="handoffModal"
- style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 1px solid var(--primary); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
- >
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
- <div style="display: flex; align-items: center; gap: 0.5rem;">
- <span style="font-size: 1.2rem;">🔑</span>
- <strong style="color: var(--text-primary); font-size: 1rem;">
- Session Delegated Successfully!
- </strong>
- </div>
- <span class="badge badge-success">Active Now</span>
- </div>
+ </div>
- <p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
- Copy this token, 1-click magic link, or CLI export string. You can
- revoke or extend this session anytime from the active session cards
- below.
+ <div id="tabWorkshopPass" style="display: none;">
+ <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;">
+ Generate a shared event pass with a universal 6-digit PIN, vanity
+ link (<code style="font-size: 0.8rem;">/e/slug</code>), and CLI
+ 1-liner for workshops, hackathons, and multi-user kiosks.
</p>
- <div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem;">
- {/* 1-Click Magic Link */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- 1-Click Magic Link (Web / Friend / Interview)
+ <form
+ id="eventForm"
+ onsubmit="handleCreateEvent(event)"
+ style="margin-top: 1rem;"
+ >
+ {/* Event Name */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Workshop / Event Name *
</label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="handoffMagicLinkText"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
+ <input
+ type="text"
+ id="eventName"
+ placeholder="e.g. Deno & SPIRE Zero-Trust Workshop, Elite Fleet Live Demo"
+ required
+ style="width: 100%;"
+ />
+ </div>
+
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.25rem;">
+ {/* Target App */}
+ <div>
+ <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Target App Destination
+ </label>
+ <select
+ id="eventAppId"
+ style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
- </code>
- <button
- type="button"
- class="btn-primary"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyHandoff('magic')"
+ <option value="">Universal Sandbox (Default)</option>
+ {apps.map((app) => (
+ <option value={app.id}>
+ {app.name} ({app.domain || "Internal"})
+ </option>
+ ))}
+ </select>
+ </div>
+
+ {/* Guest Role */}
+ <div>
+ <label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Attendee Access Role
+ </label>
+ <select
+ id="eventRole"
+ style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
- Copy Link
- </button>
+ <option value="viewer">Viewer (Read-Only Demo)</option>
+ <option value="operator">Operator (Workshop Actions)</option>
+ <option value="user">Standard User</option>
+ </select>
</div>
</div>
- {/* 1-Tap Copy CLI */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- Terminal Environment Export (CLI)
+ {/* Seat Capacity Presets */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Seat Capacity Limit
</label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="handoffCliText"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
+ <button
+ type="button"
+ class="pill-btn seat-pill"
+ onclick="selectSeats(this, 25)"
+ >
+ 25 Seats
+ </button>
+ <button
+ type="button"
+ class="pill-btn seat-pill active"
+ onclick="selectSeats(this, 50)"
+ >
+ 50 Seats
+ </button>
+ <button
+ type="button"
+ class="pill-btn seat-pill"
+ onclick="selectSeats(this, 100)"
>
- </code>
+ 100 Seats
+ </button>
<button
type="button"
- class="btn-outline"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyHandoff('cli')"
+ class="pill-btn seat-pill"
+ onclick="selectSeats(this, 0)"
>
- Copy CLI
+ ∞ Unlimited
</button>
+ <input
+ type="number"
+ id="eventMaxSeats"
+ value="50"
+ min="0"
+ max="10000"
+ style="width: 100px; padding: 0.35rem 0.5rem; text-align: center;"
+ title="Max seats (0 = unlimited)"
+ />
</div>
</div>
- {/* 1-Tap Copy cURL Header */}
- <div>
- <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
- HTTP Authorization Header
+ {/* Duration Presets */}
+ <div style="margin-bottom: 1.25rem;">
+ <label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
+ Event Duration & Expiration
</label>
- <div style="display: flex; gap: 0.5rem;">
- <code
- id="handoffCurlText"
- style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
+ <button
+ type="button"
+ class="pill-btn event-lifespan-pill"
+ onclick="selectEventLifespan(this, 1)"
+ >
+ ⚡ 1 Hour
+ </button>
+ <button
+ type="button"
+ class="pill-btn event-lifespan-pill active"
+ onclick="selectEventLifespan(this, 3)"
>
- </code>
+ 🛠️ 3 Hours (Standard Workshop)
+ </button>
+ <button
+ type="button"
+ class="pill-btn event-lifespan-pill"
+ onclick="selectEventLifespan(this, 12)"
+ >
+ 📅 12 Hours (All-Day Hackathon)
+ </button>
<button
type="button"
- class="btn-outline"
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
- onclick="copyHandoff('curl')"
+ class="pill-btn event-lifespan-pill"
+ onclick="selectEventLifespan(this, 24)"
>
- Copy Header
+ 🗓️ 24 Hours
</button>
</div>
+ <input type="hidden" id="eventLifespanHours" value="3" />
</div>
- </div>
- <button
- type="button"
- class="btn-outline"
- style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
- onclick="closeHandoffModal()"
+ {/* Custom Codes Accordion (Optional) */}
+ <details style="margin-bottom: 1.5rem; background: var(--surface-muted); padding: 0.75rem 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle);">
+ <summary style="font-size: 0.875rem; font-weight: 600; color: var(--text-primary); cursor: pointer;">
+ ▸ Custom Vanity Slug & Custom PIN Code (Optional)
+ </summary>
+ <div style="margin-top: 0.75rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
+ <div>
+ <label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
+ Vanity Slug (e.g. /e/deno-lab)
+ </label>
+ <input
+ type="text"
+ id="eventSlug"
+ placeholder="auto-generated if blank"
+ style="width: 100%; font-size: 0.85rem;"
+ />
+ </div>
+ <div>
+ <label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
+ Custom 6-Digit PIN (e.g. 749-123)
+ </label>
+ <input
+ type="text"
+ id="eventPinCode"
+ placeholder="auto-generated if blank"
+ style="width: 100%; font-size: 0.85rem;"
+ />
+ </div>
+ </div>
+ </details>
+
+ <div style="display: flex; gap: 0.75rem;">
+ <button
+ type="submit"
+ id="submitEventBtn"
+ class="btn-primary"
+ style="min-height: 42px; background: var(--success); border-color: var(--success);"
+ >
+ 🎟️ Launch Workshop Pass
+ </button>
+ <button
+ type="button"
+ class="btn-outline"
+ onclick="closeDelegateDrawer()"
+ style="min-height: 42px;"
+ >
+ Cancel
+ </button>
+ </div>
+ </form>
+
+ {/* Event Launch Success Modal / Card */}
+ <div
+ id="eventHandoffModal"
+ style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 2px solid var(--success); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
- Done (Session is Active)
- </button>
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
+ <span style="font-size: 1.3rem;">🎟️</span>
+ <strong
+ id="createdEventTitle"
+ style="color: var(--text-primary); font-size: 1.1rem;"
+ >
+ Workshop Pass Live!
+ </strong>
+ </div>
+ <span class="badge badge-success">Active Now</span>
+ </div>
+
+ <p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
+ Share the 6-digit PIN, 1-click link, or CLI 1-liner with
+ attendees. You can monitor live claimed seats and trigger the
+ master kill switch from the Event Cockpit below.
+ </p>
+
+ <div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem;">
+ {/* PIN Code */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ 6-Digit Universal PIN (for /join or Kiosk Entry)
+ </label>
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="eventHandoffPin"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 1.1rem; text-align: center; letter-spacing: 3px; font-weight: 700; color: var(--primary);"
+ >
+ </code>
+ <button
+ type="button"
+ class="btn-primary"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyEventHandoff('pin')"
+ >
+ Copy PIN
+ </button>
+ </div>
+ </div>
+
+ {/* 1-Click Vanity Link */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ Direct 1-Click Workshop Entrance URL
+ </label>
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="eventHandoffLink"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
+ >
+ </code>
+ <button
+ type="button"
+ class="btn-outline"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyEventHandoff('link')"
+ >
+ Copy URL
+ </button>
+ </div>
+ </div>
+
+ {/* CLI 1-Liner */}
+ <div>
+ <label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
+ Terminal 1-Liner (Environment Injector)
+ </label>
+ <div style="display: flex; gap: 0.5rem;">
+ <code
+ id="eventHandoffCli"
+ style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.8rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
+ >
+ </code>
+ <button
+ type="button"
+ class="btn-outline"
+ style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
+ onclick="copyEventHandoff('cli')"
+ >
+ Copy 1-Liner
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <button
+ type="button"
+ class="btn-outline"
+ style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
+ onclick="closeEventHandoffModal()"
+ >
+ Done & Refresh Cockpit
+ </button>
+ </div>
</div>
</div>
@@ -1178,6 +1174,22 @@ export const SessionsPage = ({
border-color: var(--primary);
color: var(--primary);
}
+ .delegate-tab-btn {
+ flex: 1;
+ padding: 0.5rem 0.75rem;
+ border-radius: var(--radius-sm);
+ border: none;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s;
+ background: transparent;
+ color: var(--text-secondary);
+ }
+ .delegate-tab-btn.active {
+ background: var(--surface-card);
+ color: var(--primary);
+ box-shadow: var(--shadow-xs);
+ }
`}
</style>
@@ -1197,8 +1209,6 @@ export const SessionsPage = ({
}
function openDelegateDrawer() {
- const eventDrawer = document.getElementById('eventDrawer');
- if (eventDrawer) eventDrawer.style.display = 'none';
document.getElementById('delegateDrawer').style.display = 'block';
document.getElementById('delegateDrawer').scrollIntoView({ behavior: 'smooth' });
}
@@ -1207,15 +1217,18 @@ export const SessionsPage = ({
document.getElementById('delegateDrawer').style.display = 'none';
}
- function openEventDrawer() {
- const delegateDrawer = document.getElementById('delegateDrawer');
- if (delegateDrawer) delegateDrawer.style.display = 'none';
- document.getElementById('eventDrawer').style.display = 'block';
- document.getElementById('eventDrawer').scrollIntoView({ behavior: 'smooth' });
- }
+ function switchDelegateTab(tabId) {
+ document.getElementById('tabDirectPass').style.display = 'none';
+ document.getElementById('tabWorkshopPass').style.display = 'none';
+ document.getElementById('tabBtnDirectPass').classList.remove('active');
+ document.getElementById('tabBtnWorkshopPass').classList.remove('active');
- function closeEventDrawer() {
- document.getElementById('eventDrawer').style.display = 'none';
+ document.getElementById(tabId).style.display = 'block';
+ if (tabId === 'tabDirectPass') {
+ document.getElementById('tabBtnDirectPass').classList.add('active');
+ } else {
+ document.getElementById('tabBtnWorkshopPass').classList.add('active');
+ }
}
function selectSeats(btn, count) {