Compare commits
2 Commits
aa6b731fcf
...
5de9bdf18c
| Author | SHA1 | Date | |
|---|---|---|---|
| 5de9bdf18c | |||
|
|
64a9b2cd1d |
@ -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,37 +152,374 @@ 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>
|
||||
<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 Multi-Claim Workshop / Event Pass
|
||||
Create New Pass / Session
|
||||
</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()"
|
||||
onclick="closeDelegateDrawer()"
|
||||
style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
id="tabBtnDirectPass"
|
||||
class="delegate-tab-btn active"
|
||||
onclick="switchDelegateTab('tabDirectPass')"
|
||||
>
|
||||
🔑 1:1 Direct Pass
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="tabBtnWorkshopPass"
|
||||
class="delegate-tab-btn"
|
||||
onclick="switchDelegateTab('tabWorkshopPass')"
|
||||
>
|
||||
🎟️ Multi-Claim Workshop Pass
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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;"
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* 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>
|
||||
<button
|
||||
type="button"
|
||||
class="pill-btn mode-pill"
|
||||
data-mode="operator"
|
||||
onclick="selectMode(this, 'operator')"
|
||||
>
|
||||
⚡ 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="pill-btn mode-pill"
|
||||
data-mode="custom"
|
||||
onclick="selectMode(this, 'custom')"
|
||||
>
|
||||
🛠️ Custom Scopes
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="delegateMode" value="read_only" />
|
||||
</div>
|
||||
|
||||
{/* 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);"
|
||||
>
|
||||
<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: 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>
|
||||
|
||||
<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;"
|
||||
>
|
||||
Cancel
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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);"
|
||||
>
|
||||
</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>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
|
||||
onclick="closeHandoffModal()"
|
||||
>
|
||||
Done (Session is Active)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<form
|
||||
id="eventForm"
|
||||
onsubmit="handleCreateEvent(event)"
|
||||
@ -373,7 +701,7 @@ export const SessionsPage = ({
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
onclick="closeEventDrawer()"
|
||||
onclick="closeDelegateDrawer()"
|
||||
style="min-height: 42px;"
|
||||
>
|
||||
Cancel
|
||||
@ -400,9 +728,9 @@ export const SessionsPage = ({
|
||||
</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.
|
||||
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;">
|
||||
@ -483,338 +811,6 @@ export const SessionsPage = ({
|
||||
</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;"
|
||||
>
|
||||
×
|
||||
</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;"
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* 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>
|
||||
<button
|
||||
type="button"
|
||||
class="pill-btn mode-pill"
|
||||
data-mode="operator"
|
||||
onclick="selectMode(this, 'operator')"
|
||||
>
|
||||
⚡ 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="pill-btn mode-pill"
|
||||
data-mode="custom"
|
||||
onclick="selectMode(this, 'custom')"
|
||||
>
|
||||
🛠️ Custom Scopes
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="delegateMode" value="read_only" />
|
||||
</div>
|
||||
|
||||
{/* 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);"
|
||||
>
|
||||
<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: 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>
|
||||
|
||||
<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;"
|
||||
>
|
||||
Cancel
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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);"
|
||||
>
|
||||
</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>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
|
||||
onclick="closeHandoffModal()"
|
||||
>
|
||||
Done (Session is Active)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Edit Scopes Modal */}
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user