import { DeesElement, html, customElement, css, type TemplateResult } from '@design.estate/dees-element'; import { idpElementStyles } from './tokens.js'; import './idp-badge.js'; import './idp-button.js'; import './idp-icon.js'; type TDashboardStat = { label: string; value: string; unit?: string; delta: string; sub: string; accent: string; sparkColor: string; spark: number[]; live?: boolean; }; declare global { interface HTMLElementTagNameMap { 'idp-dashboard-window': IdpDashboardWindow; } } @customElement('idp-dashboard-window') export class IdpDashboardWindow extends DeesElement { public static demo = () => html``; public static demoGroups = ['idp.global v3 composed surfaces']; public static styles = [ ...idpElementStyles, css` :host { display: block; } .dash { position: relative; overflow: hidden; border: 1px solid var(--idp-border); border-radius: 14px; background: var(--idp-bg-2); color: var(--idp-fg); box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 40px 80px -20px rgba(0,0,0,0.70), 0 8px 24px rgba(0,0,0,0.35); } .chrome, .appbar, .bottom { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--idp-border-soft); background: rgba(255,255,255,0.02); color: var(--idp-muted-fg); font-family: var(--idp-mono); font-size: 11px; } .chrome { padding: 11px 14px; } .tdot { width: 11px; height: 11px; border-radius: 50%; } .red { background: #ff5f57; } .yellow { background: #ffbd2e; } .green { background: #28c840; } .url, .org, .search { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--idp-border-soft); border-radius: 5px; background: var(--idp-bg); color: var(--idp-fg-3, var(--idp-muted-fg)); } .url { margin-left: 12px; padding: 4px 10px; } .status { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; } .live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--idp-ok); box-shadow: 0 0 8px var(--idp-ok); animation: pulse 1.6s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } } .appbar { justify-content: space-between; height: 44px; padding: 0 14px; background: var(--idp-bg); } .appbar-left, .appbar-right { display: flex; align-items: center; gap: 12px; } .logo { display: inline-flex; align-items: center; gap: 7px; color: var(--idp-fg); font-family: var(--idp-display); font-size: 13px; font-weight: 700; letter-spacing: -0.015em; } .logo-dot { width: 6px; height: 6px; border-radius: 999px; background: var(--idp-accent); box-shadow: 0 0 12px var(--idp-accent); } .divider { width: 1px; height: 18px; background: var(--idp-border); } .org { padding: 4px 10px 4px 4px; background: var(--idp-bg-2); font-size: 12px; } .avatar-sm { width: 20px; height: 20px; display: inline-grid; place-items: center; border-radius: 4px; background: var(--idp-accent); color: #fff; font-family: var(--idp-mono); font-size: 10px; font-weight: 700; } .search { min-width: 240px; height: 28px; padding: 0 10px; background: var(--idp-bg-2); } .kbd { margin-left: auto; padding: 0 5px; border: 1px solid var(--idp-border); border-radius: 3px; background: var(--idp-bg); font-size: 10px; } .user-avatar { width: 28px; height: 28px; display: inline-grid; place-items: center; border: 1px solid rgba(59,130,246,0.4); border-radius: 50%; background: rgba(0,80,185,0.25); color: var(--idp-accent-hover); font-family: var(--idp-mono); font-size: 10px; font-weight: 700; } .shell { display: grid; grid-template-columns: 200px 1fr; min-height: 580px; } aside { display: flex; flex-direction: column; gap: 1px; padding: 12px 8px; border-right: 1px solid var(--idp-border-soft); background: var(--idp-bg); } .side-label { padding: 12px 10px 6px; color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%); font-family: var(--idp-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; } .side-nav { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: 5px; color: var(--idp-muted-fg); font-size: 12.5px; } .side-nav.active { background: rgba(0,80,185,0.18); color: var(--idp-fg); } .nav-icon { width: 18px; display: inline-flex; justify-content: center; color: currentColor; } .side-nav.active .nav-icon { color: var(--idp-accent-hover); } main { min-width: 0; padding: 22px 24px; background: var(--idp-bg); } .head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 18px; } h3 { margin: 0; font-family: var(--idp-display); font-size: 22px; font-weight: 650; letter-spacing: -0.02em; } .sub { margin-top: 2px; color: var(--idp-muted-fg); font-size: 12.5px; } .actions { display: flex; gap: 8px; } .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; } .stat, .card { border: 1px solid var(--idp-border-soft); background: var(--idp-bg-2); } .stat { position: relative; min-height: 132px; display: flex; flex-direction: column; gap: 4px; overflow: hidden; padding: 18px 20px 14px; border-radius: 10px; } .stat::before { content: ''; position: absolute; inset: 0 0 auto; height: 2px; background: var(--stat-accent); } .stat-label { color: var(--idp-muted-fg); font-size: 11.5px; font-weight: 500; letter-spacing: 0.02em; } .stat-val { color: var(--idp-fg); font-family: var(--idp-display); font-size: 30px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.025em; line-height: 1.1; } .stat-val span { margin-left: 2px; color: var(--idp-muted-fg); font-size: 14px; font-weight: 500; } .stat-sub { color: var(--idp-fg-3); font-family: var(--idp-mono); font-size: 11px; } .stat-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 8px; margin-top: auto; } .delta { display: inline-flex; align-items: center; gap: 4px; color: var(--idp-ok); font-family: var(--idp-mono); font-size: 11px; font-weight: 600; } .sparkline { width: 84px; opacity: 0.85; } .sparkline svg { width: 100%; height: 22px; display: block; } .grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; } .card { overflow: hidden; } .card-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--idp-border-soft); } .card-title { font-size: 13px; font-weight: 600; } table { width: 100%; border-collapse: collapse; } th, td { padding: 10px 16px; border-bottom: 1px solid var(--idp-border-soft); text-align: left; font-size: 12.5px; } th { color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%); font-family: var(--idp-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; } .user { display: flex; align-items: center; gap: 8px; } .row-avatar { width: 22px; height: 22px; display: inline-grid; place-items: center; border: 1px solid var(--idp-border); border-radius: 50%; background: var(--idp-card-2); color: var(--idp-accent-hover); font-family: var(--idp-mono); font-size: 9.5px; font-weight: 700; } .row-name { color: var(--idp-fg); font-weight: 500; } .row-email, .dim { color: var(--idp-muted-fg); font-family: var(--idp-mono); font-size: 11px; } .feed-item { display: grid; grid-template-columns: 14px 1fr auto; gap: 12px; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--idp-border-soft); } .feed-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--idp-accent-hover); } .feed-dot.ok { background: var(--idp-ok); } .feed-text { color: var(--idp-fg-3, var(--idp-muted-fg)); font-size: 12.5px; } .feed-text strong { color: var(--idp-fg); font-weight: 500; } .feed-meta { color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%); font-family: var(--idp-mono); font-size: 10.5px; } .bottom { height: 28px; padding: 0 14px; border-top: 1px solid var(--idp-border-soft); border-bottom: 0; background: var(--idp-bg); } .bottom .divider { height: 12px; } .grow { flex: 1; } @media (max-width: 900px) { .shell { grid-template-columns: 1fr; } aside, .search { display: none; } .stats { grid-template-columns: repeat(2, 1fr); } .grid { grid-template-columns: 1fr; } } @media (max-width: 560px) { .stats { grid-template-columns: 1fr; } .head { align-items: flex-start; flex-direction: column; } th:nth-child(3), td:nth-child(3), th:nth-child(4), td:nth-child(4) { display: none; } } `, ]; private stats: TDashboardStat[] = [ { label: 'Identities', value: '2,847', delta: '↑ 12% wk', sub: '142 added this week', accent: 'var(--idp-chart-1)', sparkColor: 'var(--idp-spark-up)', spark: [10, 12, 11, 14, 13, 16, 15, 18, 19] }, { label: 'Active devices', value: '9,140', delta: '↑ 4.2%', sub: '3.2 avg / identity', accent: 'var(--idp-chart-2)', sparkColor: 'var(--idp-spark-up)', spark: [12, 13, 11, 14, 13, 15, 14, 16, 17] }, { label: 'Avg approval', value: '0.8', unit: 's', delta: '↓ 60ms faster', sub: 'p95 - all regions', accent: 'var(--idp-chart-5)', sparkColor: 'var(--idp-spark-info)', spark: [16, 14, 17, 12, 15, 13, 11, 9, 7] }, { label: 'Cardano anchors', value: '12,408', delta: 'synced 4s ago', sub: 'block #9 841 222', accent: 'var(--idp-info)', sparkColor: 'var(--idp-spark-up)', spark: [8, 9, 11, 10, 13, 12, 15, 16, 18], live: true }, ]; private approvals = [ ['Jane Doe', 'jane@lossless.com', 'OAuth - GitHub', 'iPhone 15 Pro', 'approved', 'ok'], ['Alex Brown', 'alex@lossless.com', 'CLI login', 'MacBook Pro', 'pending', 'warn'], ['Sam Chen', 'sam@lossless.com', 'NFC tap - door 4F', 'iPhone 14', 'approved', 'ok'], ['Unknown device', 'Lagos - NG', 'Web login', 'Chrome 132', 'denied', 'error'], ['Maria K.', 'maria@lossless.com', 'Key rotation', 'Apple Watch S9', 'on-chain', 'accent'], ]; private feed = [ ['Identity created', 'did:idp:0x9b12...f034', 'block #9 841 222', ''], ['Anchor confirmed', '12 blocks deep', '2m', 'ok'], ['Key rotation', 'did:idp:0x4a3f...c819', 'block #9 841 221', ''], ['OAuth scope updated', 'github repo:read', '5m', 'ok'], ['Device registered', 'MacBook Pro pending', '7m', ''], ]; private workspaceNav = [ ['Overview', 'grid'], ['Identities', 'user'], ['Approvals', 'bell'], ['OAuth clients', 'key'], ['Devices', 'monitor'], ['Audit log', 'clock'], ]; private chainNav = [ ['Cardano sync', 'wallet'], ['Anchors', 'shield'], ]; private renderSparkline(data: number[], color: string): TemplateResult { const max = Math.max(...data); const min = Math.min(...data); const range = max - min || 1; const width = 100; const height = 22; const points = data.map((valueArg, indexArg) => { const x = (indexArg / (data.length - 1)) * width; const y = height - ((valueArg - min) / range) * (height - 4) - 2; return `${x},${y}`; }).join(' '); const area = `0,${height} ${points} ${width},${height}`; return html``; } private renderStat(statArg: TDashboardStat): TemplateResult { return html`
${statArg.label}
${statArg.value}${statArg.unit ? html`${statArg.unit}` : html``}
${statArg.sub}
${statArg.live ? html`` : html``}${statArg.delta}
${this.renderSparkline(statArg.spark, statArg.sparkColor)}
`; } public render(): TemplateResult { return html`
console.idp.global / dashboard eu-west-1 - 38ms
LLossless GmbH
Search identities, devices Cmd+K AM

Overview

Identity activity across @lossless - last 7 days
ExportNew identity
${this.stats.map((statArg) => this.renderStat(statArg))}
Recent approvals142 total
${this.approvals.map((rowArg) => html` `)}
UserActionDeviceStatus
${rowArg[0].slice(0, 2).toUpperCase()}
${rowArg[0]}
${rowArg[1]}
${rowArg[2]} ${rowArg[3]} ${rowArg[4]}
Cardano feedlive
${this.feed.map((itemArg) => html`
${itemArg[0]} - ${itemArg[1]}
${itemArg[2]}
`)}
API - 38msv3.81.0block #9 841 222 - confirmed
`; } }