@idp.global/catalog 1.2.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +21 -0
- package/dist_bundle/bundle.js +4428 -1496
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/idp-admin-overview.d.ts +28 -0
- package/dist_ts_web/elements/idp-admin-overview.js +460 -0
- package/dist_ts_web/elements/idp-admin-shared.d.ts +18 -0
- package/dist_ts_web/elements/idp-admin-shared.js +404 -0
- package/dist_ts_web/elements/idp-admin-shell.d.ts +96 -11
- package/dist_ts_web/elements/idp-admin-shell.js +442 -816
- package/dist_ts_web/elements/idp-admin-sidebar.d.ts +42 -0
- package/dist_ts_web/elements/idp-admin-sidebar.js +700 -0
- package/dist_ts_web/elements/idp-app-pair.d.ts +19 -0
- package/dist_ts_web/elements/idp-app-pair.js +187 -0
- package/dist_ts_web/elements/idp-auth-card.d.ts +17 -0
- package/dist_ts_web/elements/idp-auth-card.js +180 -0
- package/dist_ts_web/elements/idp-avatar.d.ts +17 -0
- package/dist_ts_web/elements/idp-avatar.js +151 -0
- package/dist_ts_web/elements/idp-badge.d.ts +2 -0
- package/dist_ts_web/elements/idp-badge.js +12 -3
- package/dist_ts_web/elements/idp-brand-mark.d.ts +16 -0
- package/dist_ts_web/elements/idp-brand-mark.js +139 -0
- package/dist_ts_web/elements/idp-button.d.ts +3 -0
- package/dist_ts_web/elements/idp-button.js +47 -3
- package/dist_ts_web/elements/idp-consent-card.d.ts +34 -0
- package/dist_ts_web/elements/idp-consent-card.js +217 -0
- package/dist_ts_web/elements/idp-delegation-section.d.ts +59 -0
- package/dist_ts_web/elements/idp-delegation-section.js +609 -0
- package/dist_ts_web/elements/idp-form-submit.d.ts +2 -1
- package/dist_ts_web/elements/idp-form-submit.js +11 -2
- package/dist_ts_web/elements/idp-form.js +2 -2
- package/dist_ts_web/elements/idp-input.d.ts +4 -0
- package/dist_ts_web/elements/idp-input.js +68 -15
- package/dist_ts_web/elements/idp-option-row.d.ts +25 -0
- package/dist_ts_web/elements/idp-option-row.js +267 -0
- package/dist_ts_web/elements/idp-otp-input.d.ts +32 -0
- package/dist_ts_web/elements/idp-otp-input.js +274 -0
- package/dist_ts_web/elements/idp-progress-bar.d.ts +14 -0
- package/dist_ts_web/elements/idp-progress-bar.js +127 -0
- package/dist_ts_web/elements/idp-signed-in-pill.d.ts +15 -0
- package/dist_ts_web/elements/idp-signed-in-pill.js +113 -0
- package/dist_ts_web/elements/idp-spinner.d.ts +13 -0
- package/dist_ts_web/elements/idp-spinner.js +109 -0
- package/dist_ts_web/elements/idp-status-icon.d.ts +18 -0
- package/dist_ts_web/elements/idp-status-icon.js +136 -0
- package/dist_ts_web/elements/index.d.ts +15 -0
- package/dist_ts_web/elements/index.js +16 -1
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/idp-admin-overview.ts +388 -0
- package/ts_web/elements/idp-admin-shared.ts +419 -0
- package/ts_web/elements/idp-admin-shell.ts +514 -871
- package/ts_web/elements/idp-admin-sidebar.ts +646 -0
- package/ts_web/elements/idp-app-pair.ts +120 -0
- package/ts_web/elements/idp-auth-card.ts +113 -0
- package/ts_web/elements/idp-avatar.ts +84 -0
- package/ts_web/elements/idp-badge.ts +5 -1
- package/ts_web/elements/idp-brand-mark.ts +76 -0
- package/ts_web/elements/idp-button.ts +36 -1
- package/ts_web/elements/idp-consent-card.ts +146 -0
- package/ts_web/elements/idp-delegation-section.ts +576 -0
- package/ts_web/elements/idp-form-submit.ts +5 -1
- package/ts_web/elements/idp-form.ts +1 -1
- package/ts_web/elements/idp-input.ts +58 -13
- package/ts_web/elements/idp-option-row.ts +184 -0
- package/ts_web/elements/idp-otp-input.ts +204 -0
- package/ts_web/elements/idp-progress-bar.ts +69 -0
- package/ts_web/elements/idp-signed-in-pill.ts +55 -0
- package/ts_web/elements/idp-spinner.ts +56 -0
- package/ts_web/elements/idp-status-icon.ts +76 -0
- package/ts_web/elements/index.ts +15 -0
|
@@ -3,6 +3,13 @@ import { idpElementStyles } from './tokens.js';
|
|
|
3
3
|
import './idp-badge.js';
|
|
4
4
|
import './idp-icon.js';
|
|
5
5
|
import './idp-data-table.js';
|
|
6
|
+
import './idp-avatar.js';
|
|
7
|
+
import './idp-toggle.js';
|
|
8
|
+
import './idp-delegation-section.js';
|
|
9
|
+
import './idp-admin-sidebar.js';
|
|
10
|
+
import './idp-admin-overview.js';
|
|
11
|
+
import { idpAdminSharedStyles, renderAdminKpi, renderAdminStateCard, formatAdminTimeAgo, type TIdpAdminKpi } from './idp-admin-shared.js';
|
|
12
|
+
import type { IIdpDelegation, IIdpDelegationApp } from './idp-delegation-section.js';
|
|
6
13
|
|
|
7
14
|
declare global {
|
|
8
15
|
interface HTMLElementTagNameMap {
|
|
@@ -10,25 +17,6 @@ declare global {
|
|
|
10
17
|
}
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
type TNavItem = {
|
|
14
|
-
id: TIdpAdminPage;
|
|
15
|
-
label: string;
|
|
16
|
-
icon: string;
|
|
17
|
-
badge?: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type TKpi = {
|
|
21
|
-
label: string;
|
|
22
|
-
value: string;
|
|
23
|
-
unit?: string;
|
|
24
|
-
delta: string;
|
|
25
|
-
deltaKind: 'up' | 'live';
|
|
26
|
-
sub: string;
|
|
27
|
-
accent: string;
|
|
28
|
-
spark: number[];
|
|
29
|
-
sparkColor: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
20
|
export type TIdpAdminPage =
|
|
33
21
|
| 'overview'
|
|
34
22
|
| 'profile'
|
|
@@ -58,6 +46,10 @@ export interface IIdpAdminOrg {
|
|
|
58
46
|
name: string;
|
|
59
47
|
slug: string;
|
|
60
48
|
myRole?: string;
|
|
49
|
+
plan?: string;
|
|
50
|
+
status?: string;
|
|
51
|
+
memberCount?: number;
|
|
52
|
+
createdAt?: number;
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
export interface IIdpAdminNavigateEventDetail {
|
|
@@ -69,6 +61,79 @@ export interface IIdpAdminOrgSelectEventDetail {
|
|
|
69
61
|
org: IIdpAdminOrg | null;
|
|
70
62
|
}
|
|
71
63
|
|
|
64
|
+
export interface IIdpAdminLogoutEventDetail {
|
|
65
|
+
everywhere: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface IIdpAdminAccountSwitchEventDetail {
|
|
69
|
+
account: IIdpAdminUser;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface IIdpAdminOrgCreateEventDetail {
|
|
73
|
+
name: string;
|
|
74
|
+
slug: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IIdpAdminGaUser {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
email: string;
|
|
81
|
+
status: string;
|
|
82
|
+
isGlobalAdmin: boolean;
|
|
83
|
+
orgCount: number;
|
|
84
|
+
createdAt: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface IIdpAdminGaUserActionEventDetail {
|
|
88
|
+
userId: string;
|
|
89
|
+
action: 'suspend' | 'unsuspend';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface IIdpAdminGaOrgActionEventDetail {
|
|
93
|
+
orgId: string;
|
|
94
|
+
action: 'suspend' | 'unsuspend';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface IIdpAdminGaAppActionEventDetail {
|
|
98
|
+
appId: string;
|
|
99
|
+
action: 'approve' | 'reject' | 'suspend' | 'reinstate';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface IIdpAdminOverviewSeriesPoint {
|
|
103
|
+
/** epoch-ms timestamp or Date-parseable string; plain clock times like '14:00' are not chartable */
|
|
104
|
+
x: string | number;
|
|
105
|
+
y: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface IIdpAdminOverviewThreat {
|
|
109
|
+
severity: 'error' | 'warning' | 'info';
|
|
110
|
+
title: string;
|
|
111
|
+
meta: string;
|
|
112
|
+
action?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface IIdpAdminOverviewRegion {
|
|
116
|
+
name: string;
|
|
117
|
+
code: string;
|
|
118
|
+
count: number;
|
|
119
|
+
share: number;
|
|
120
|
+
kind?: 'home' | 'risk';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface IIdpAdminOverviewFeedItem {
|
|
124
|
+
tone: 'ok' | 'info' | 'warn' | 'error';
|
|
125
|
+
text: string;
|
|
126
|
+
meta: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface IIdpAdminOverviewData {
|
|
130
|
+
chartLabel?: string;
|
|
131
|
+
chartSeries?: { name: string; data: IIdpAdminOverviewSeriesPoint[] }[];
|
|
132
|
+
threats?: IIdpAdminOverviewThreat[];
|
|
133
|
+
regions?: IIdpAdminOverviewRegion[];
|
|
134
|
+
feed?: IIdpAdminOverviewFeedItem[];
|
|
135
|
+
}
|
|
136
|
+
|
|
72
137
|
export interface IIdpAdminSession {
|
|
73
138
|
id: string;
|
|
74
139
|
deviceName: string;
|
|
@@ -274,11 +339,95 @@ export interface IIdpAdminPasskeyEventDetail {
|
|
|
274
339
|
|
|
275
340
|
@customElement('idp-admin-shell')
|
|
276
341
|
export class IdpAdminShell extends DeesElement {
|
|
277
|
-
public static demo = () =>
|
|
278
|
-
|
|
342
|
+
public static demo = () => {
|
|
343
|
+
const now = Date.now();
|
|
344
|
+
const hours = Array.from({ length: 24 }, (unused, indexArg) => indexArg);
|
|
345
|
+
return html`
|
|
346
|
+
<idp-admin-shell
|
|
347
|
+
global-admin
|
|
348
|
+
.user=${{ name: 'Alex Mercer', email: 'alex@lossless.com' }}
|
|
349
|
+
.accounts=${[
|
|
350
|
+
{ name: 'Alex Mercer', email: 'alex.mercer@gmail.com' },
|
|
351
|
+
]}
|
|
352
|
+
.orgs=${[
|
|
353
|
+
{ id: 'org_foss', name: 'Lossless GmbH', slug: 'lossless', myRole: 'owner', plan: 'Pro', status: 'active', memberCount: 5, createdAt: now - 86400000 * 200 },
|
|
354
|
+
{ id: 'org_task', name: 'Task VC', slug: 'task', myRole: 'admin', plan: 'Pro', status: 'active', memberCount: 3, createdAt: now - 86400000 * 140 },
|
|
355
|
+
{ id: 'org_sus', name: 'Suspended Co.', slug: 'suspended', myRole: 'viewer', plan: 'Pro', status: 'suspended', memberCount: 2, createdAt: now - 86400000 * 80 },
|
|
356
|
+
]}
|
|
357
|
+
.sessions=${[
|
|
358
|
+
{ id: 's1', deviceName: 'MacBook Pro', browser: 'Safari 18', os: 'macOS', ip: '84.132.10.4', lastActive: now - 120000, createdAt: now - 86400000 * 3, isCurrent: true },
|
|
359
|
+
{ id: 's2', deviceName: 'iPhone 15 Pro', browser: 'Safari', os: 'iOS 18', ip: '84.132.10.4', lastActive: now - 3600000, createdAt: now - 86400000 * 10, isCurrent: false },
|
|
360
|
+
{ id: 's3', deviceName: 'Work laptop', browser: 'Chrome 132', os: 'Windows 11', ip: '13.48.2.1', lastActive: now - 86400000, createdAt: now - 86400000 * 30, isCurrent: false },
|
|
361
|
+
]}
|
|
362
|
+
.gaUsers=${[
|
|
363
|
+
{ id: 'u01', name: 'Alex Mercer', email: 'alex@lossless.com', status: 'active', isGlobalAdmin: true, orgCount: 2, createdAt: now - 86400000 * 200 },
|
|
364
|
+
{ id: 'u02', name: 'Jordan Kim', email: 'jordan@lossless.com', status: 'active', isGlobalAdmin: false, orgCount: 1, createdAt: now - 86400000 * 100 },
|
|
365
|
+
{ id: 'u05', name: 'Dana Walsh', email: 'dana@suspended.de', status: 'suspended', isGlobalAdmin: false, orgCount: 0, createdAt: now - 86400000 * 60 },
|
|
366
|
+
{ id: 'u06', name: 'Morgan Lee', email: 'morgan@newuser.com', status: 'new', isGlobalAdmin: false, orgCount: 0, createdAt: now - 86400000 },
|
|
367
|
+
]}
|
|
368
|
+
.adminApps=${[
|
|
369
|
+
{ id: 'app_foss', name: 'foss.global', type: 'global', category: 'Productivity', status: 'active' },
|
|
370
|
+
{ id: 'app_p1', name: 'Acme HR Portal', type: 'partner', category: 'HR', connectionCount: 412, status: 'approved' },
|
|
371
|
+
{ id: 'app_p2', name: 'DevOps Suite', type: 'partner', category: 'DevOps', connectionCount: 87, status: 'pending_review' },
|
|
372
|
+
]}
|
|
373
|
+
.overviewData=${{
|
|
374
|
+
chartLabel: 'Approval activity',
|
|
375
|
+
chartSeries: [
|
|
376
|
+
{ name: 'Approvals', data: hours.map((hourArg) => ({ x: now - (23 - hourArg) * 3600000, y: [22, 18, 14, 10, 8, 7, 9, 14, 22, 38, 52, 64, 71, 74, 70, 65, 58, 52, 45, 38, 32, 28, 25, 23][hourArg] })) },
|
|
377
|
+
{ name: 'OAuth grants', data: hours.map((hourArg) => ({ x: now - (23 - hourArg) * 3600000, y: [16, 12, 9, 7, 6, 5, 7, 11, 18, 30, 42, 50, 56, 58, 56, 52, 47, 41, 36, 30, 26, 23, 20, 18][hourArg] })) },
|
|
378
|
+
],
|
|
379
|
+
threats: [
|
|
380
|
+
{ severity: 'error', title: 'Repeated denied logins', meta: '5 attempts · Lagos, NG · last 1h', action: 'Block IP' },
|
|
381
|
+
{ severity: 'warning', title: 'Stale device key', meta: 'sam@ · Apple Watch · 9 mo old', action: 'Rotate' },
|
|
382
|
+
{ severity: 'info', title: 'Pending key rotations', meta: '3 identities · Cardano awaiting', action: 'Run' },
|
|
383
|
+
],
|
|
384
|
+
regions: [
|
|
385
|
+
{ name: 'Berlin, DE', code: 'DE', count: 1284, share: 1, kind: 'home' },
|
|
386
|
+
{ name: 'Amsterdam, NL', code: 'NL', count: 642, share: 0.5 },
|
|
387
|
+
{ name: 'San Francisco, US', code: 'US', count: 521, share: 0.41 },
|
|
388
|
+
{ name: 'Lagos, NG', code: 'NG', count: 12, share: 0.01, kind: 'risk' },
|
|
389
|
+
],
|
|
390
|
+
feed: [
|
|
391
|
+
{ tone: 'info', text: 'Identity created · did:idp:0x9b12…f034', meta: 'block #9 841 222' },
|
|
392
|
+
{ tone: 'ok', text: 'Anchor confirmed · 12 blocks deep', meta: '2m' },
|
|
393
|
+
{ tone: 'warn', text: 'Device registered · MacBook Pro · pending', meta: '7m' },
|
|
394
|
+
],
|
|
395
|
+
}}
|
|
396
|
+
.delegationApps=${[
|
|
397
|
+
{
|
|
398
|
+
id: 'foss',
|
|
399
|
+
name: 'foss.global',
|
|
400
|
+
perms: [
|
|
401
|
+
{ id: 'projects:read', label: 'View projects', hint: 'Read-only access to your projects' },
|
|
402
|
+
{ id: 'projects:write', label: 'Manage projects', hint: 'Create, edit and archive projects' },
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: 'task',
|
|
407
|
+
name: 'task.vc',
|
|
408
|
+
perms: [
|
|
409
|
+
{ id: 'tasks:read', label: 'View tasks', hint: 'See your tasks and boards' },
|
|
410
|
+
{ id: 'tasks:write', label: 'Act on tasks', hint: 'Create, assign and complete tasks as you' },
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
]}
|
|
414
|
+
.delegations=${[
|
|
415
|
+
{
|
|
416
|
+
id: 'd1',
|
|
417
|
+
account: { name: 'Jordan Kim', email: 'jordan@lossless.com' },
|
|
418
|
+
grants: { task: ['tasks:read', 'tasks:write'] },
|
|
419
|
+
expiresAt: now + 86400000 * 24,
|
|
420
|
+
createdAt: now - 86400000 * 6,
|
|
421
|
+
},
|
|
422
|
+
]}
|
|
423
|
+
></idp-admin-shell>
|
|
424
|
+
`;
|
|
425
|
+
};
|
|
426
|
+
public static demoGroups = ['idp.global v5 full pages'];
|
|
279
427
|
|
|
280
428
|
public static styles = [
|
|
281
429
|
...idpElementStyles,
|
|
430
|
+
idpAdminSharedStyles,
|
|
282
431
|
css`
|
|
283
432
|
:host {
|
|
284
433
|
display: block;
|
|
@@ -297,78 +446,9 @@ export class IdpAdminShell extends DeesElement {
|
|
|
297
446
|
border: 0;
|
|
298
447
|
background: var(--idp-bg);
|
|
299
448
|
}
|
|
300
|
-
|
|
449
|
+
idp-admin-sidebar {
|
|
301
450
|
height: 100%;
|
|
302
451
|
min-height: 0;
|
|
303
|
-
display: flex;
|
|
304
|
-
flex-direction: column;
|
|
305
|
-
overflow: hidden;
|
|
306
|
-
border-right: 1px solid var(--idp-border);
|
|
307
|
-
background: var(--idp-bg);
|
|
308
|
-
}
|
|
309
|
-
.logo-block {
|
|
310
|
-
padding: 14px 16px 10px;
|
|
311
|
-
border-bottom: 1px solid var(--idp-border);
|
|
312
|
-
}
|
|
313
|
-
.logo {
|
|
314
|
-
display: flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
gap: 8px;
|
|
317
|
-
}
|
|
318
|
-
.logo-icon {
|
|
319
|
-
width: 26px;
|
|
320
|
-
height: 26px;
|
|
321
|
-
display: flex;
|
|
322
|
-
align-items: center;
|
|
323
|
-
justify-content: center;
|
|
324
|
-
flex-shrink: 0;
|
|
325
|
-
border-radius: 7px;
|
|
326
|
-
background: var(--idp-primary);
|
|
327
|
-
color: var(--idp-primary-fg);
|
|
328
|
-
}
|
|
329
|
-
.logo-text {
|
|
330
|
-
color: var(--idp-fg);
|
|
331
|
-
font-size: 13px;
|
|
332
|
-
font-weight: 700;
|
|
333
|
-
letter-spacing: -0.3px;
|
|
334
|
-
}
|
|
335
|
-
.nav-wrap {
|
|
336
|
-
flex: 1;
|
|
337
|
-
min-height: 0;
|
|
338
|
-
overflow: auto;
|
|
339
|
-
padding: 10px 8px;
|
|
340
|
-
}
|
|
341
|
-
.state-card {
|
|
342
|
-
padding: 42px 24px;
|
|
343
|
-
border: 1px solid var(--idp-border);
|
|
344
|
-
border-radius: var(--idp-radius);
|
|
345
|
-
background: var(--idp-card);
|
|
346
|
-
color: var(--idp-muted-fg);
|
|
347
|
-
text-align: center;
|
|
348
|
-
}
|
|
349
|
-
.state-icon {
|
|
350
|
-
width: 38px;
|
|
351
|
-
height: 38px;
|
|
352
|
-
display: inline-flex;
|
|
353
|
-
align-items: center;
|
|
354
|
-
justify-content: center;
|
|
355
|
-
margin-bottom: 12px;
|
|
356
|
-
border: 1px solid var(--idp-border);
|
|
357
|
-
border-radius: 10px;
|
|
358
|
-
background: var(--idp-muted);
|
|
359
|
-
color: var(--idp-muted-fg);
|
|
360
|
-
}
|
|
361
|
-
.state-title {
|
|
362
|
-
margin-bottom: 4px;
|
|
363
|
-
color: var(--idp-fg);
|
|
364
|
-
font-size: 14px;
|
|
365
|
-
font-weight: 600;
|
|
366
|
-
}
|
|
367
|
-
.state-description {
|
|
368
|
-
max-width: 420px;
|
|
369
|
-
margin: 0 auto;
|
|
370
|
-
font-size: 12px;
|
|
371
|
-
line-height: 1.5;
|
|
372
452
|
}
|
|
373
453
|
.notice-card {
|
|
374
454
|
display: flex;
|
|
@@ -383,219 +463,48 @@ export class IdpAdminShell extends DeesElement {
|
|
|
383
463
|
.notice-card .muted {
|
|
384
464
|
color: var(--idp-muted-fg);
|
|
385
465
|
}
|
|
386
|
-
.
|
|
387
|
-
margin-bottom: 20px;
|
|
388
|
-
}
|
|
389
|
-
.nav-title {
|
|
390
|
-
padding: 0 8px;
|
|
391
|
-
margin-bottom: 4px;
|
|
392
|
-
color: var(--idp-muted-fg);
|
|
393
|
-
font-size: 10px;
|
|
394
|
-
font-weight: 600;
|
|
395
|
-
letter-spacing: 0.8px;
|
|
396
|
-
text-transform: uppercase;
|
|
397
|
-
}
|
|
398
|
-
.nav-list {
|
|
399
|
-
display: grid;
|
|
400
|
-
}
|
|
401
|
-
.nav-item,
|
|
402
|
-
.org-switch,
|
|
403
|
-
.plain-button {
|
|
404
|
-
border: 0;
|
|
405
|
-
font-family: inherit;
|
|
406
|
-
}
|
|
407
|
-
.nav-item {
|
|
408
|
-
width: 100%;
|
|
409
|
-
display: flex;
|
|
410
|
-
align-items: center;
|
|
411
|
-
gap: 8px;
|
|
412
|
-
padding: 6px 8px;
|
|
413
|
-
border-radius: 7px;
|
|
414
|
-
background: transparent;
|
|
415
|
-
color: var(--idp-muted-fg);
|
|
416
|
-
font-size: 13px;
|
|
417
|
-
font-weight: 400;
|
|
418
|
-
text-align: left;
|
|
419
|
-
}
|
|
420
|
-
.nav-item.active {
|
|
421
|
-
background: var(--idp-muted);
|
|
422
|
-
color: var(--idp-fg);
|
|
423
|
-
font-weight: 500;
|
|
424
|
-
}
|
|
425
|
-
.nav-item idp-icon {
|
|
426
|
-
color: currentColor;
|
|
427
|
-
}
|
|
428
|
-
.nav-item.active idp-icon {
|
|
429
|
-
color: var(--idp-accent);
|
|
430
|
-
}
|
|
431
|
-
.org-switch-wrap {
|
|
432
|
-
position: relative;
|
|
433
|
-
margin-bottom: 2px;
|
|
434
|
-
}
|
|
435
|
-
.org-switch {
|
|
436
|
-
width: 100%;
|
|
466
|
+
.logout-everywhere-row {
|
|
437
467
|
display: flex;
|
|
438
468
|
align-items: center;
|
|
439
|
-
gap:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
background: transparent;
|
|
443
|
-
color: var(--idp-muted-fg);
|
|
444
|
-
cursor: pointer;
|
|
445
|
-
}
|
|
446
|
-
.org-switch:hover {
|
|
447
|
-
background: var(--idp-muted);
|
|
448
|
-
}
|
|
449
|
-
.org-switch.open {
|
|
450
|
-
background: var(--idp-muted);
|
|
451
|
-
}
|
|
452
|
-
.org-switch.open .org-name {
|
|
453
|
-
color: var(--idp-accent);
|
|
454
|
-
}
|
|
455
|
-
.org-switch.open idp-icon {
|
|
456
|
-
transform: rotate(180deg);
|
|
457
|
-
}
|
|
458
|
-
.org-menu {
|
|
459
|
-
position: absolute;
|
|
460
|
-
top: calc(100% + 4px);
|
|
461
|
-
left: 0;
|
|
462
|
-
right: 0;
|
|
463
|
-
z-index: 40;
|
|
464
|
-
overflow: hidden;
|
|
469
|
+
gap: 10px;
|
|
470
|
+
margin-top: 14px;
|
|
471
|
+
padding: 10px 12px;
|
|
465
472
|
border: 1px solid var(--idp-border);
|
|
466
|
-
border-radius:
|
|
467
|
-
|
|
468
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
469
|
-
}
|
|
470
|
-
.org-menu-title {
|
|
471
|
-
padding: 6px 8px 3px;
|
|
472
|
-
color: var(--idp-muted-fg);
|
|
473
|
-
font-size: 10px;
|
|
474
|
-
font-weight: 600;
|
|
475
|
-
letter-spacing: 0.7px;
|
|
476
|
-
text-transform: uppercase;
|
|
477
|
-
}
|
|
478
|
-
.org-menu-item,
|
|
479
|
-
.org-create {
|
|
480
|
-
width: 100%;
|
|
481
|
-
display: flex;
|
|
482
|
-
align-items: center;
|
|
483
|
-
gap: 8px;
|
|
484
|
-
border: 0;
|
|
485
|
-
background: transparent;
|
|
486
|
-
font-family: inherit;
|
|
487
|
-
text-align: left;
|
|
488
|
-
}
|
|
489
|
-
.org-menu-item {
|
|
490
|
-
padding: 6px 8px;
|
|
491
|
-
cursor: pointer;
|
|
492
|
-
}
|
|
493
|
-
.org-menu-item.selected {
|
|
494
|
-
background: var(--idp-accent-soft);
|
|
495
|
-
}
|
|
496
|
-
.org-menu-item .org-avatar {
|
|
497
|
-
width: 20px;
|
|
498
|
-
height: 20px;
|
|
499
|
-
font-size: 7px;
|
|
500
|
-
}
|
|
501
|
-
.org-menu-name {
|
|
502
|
-
overflow: hidden;
|
|
473
|
+
border-radius: 9px;
|
|
474
|
+
font-size: 12.5px;
|
|
503
475
|
color: var(--idp-fg);
|
|
504
|
-
font-size: 12px;
|
|
505
|
-
font-weight: 400;
|
|
506
|
-
text-overflow: ellipsis;
|
|
507
|
-
white-space: nowrap;
|
|
508
|
-
}
|
|
509
|
-
.org-menu-item.selected .org-menu-name {
|
|
510
|
-
color: var(--idp-accent);
|
|
511
|
-
}
|
|
512
|
-
.org-menu-role {
|
|
513
|
-
color: var(--idp-muted-fg);
|
|
514
|
-
font-size: 10px;
|
|
515
|
-
}
|
|
516
|
-
.org-menu-divider {
|
|
517
|
-
height: 1px;
|
|
518
|
-
margin: 3px 0;
|
|
519
|
-
background: var(--idp-border);
|
|
520
|
-
}
|
|
521
|
-
.org-create {
|
|
522
|
-
padding: 6px 8px 8px;
|
|
523
|
-
color: var(--idp-muted-fg);
|
|
524
476
|
cursor: pointer;
|
|
525
|
-
font-size: 12px;
|
|
526
477
|
}
|
|
527
|
-
.
|
|
528
|
-
|
|
529
|
-
|
|
478
|
+
.toast {
|
|
479
|
+
position: fixed;
|
|
480
|
+
right: 20px;
|
|
481
|
+
bottom: 20px;
|
|
482
|
+
z-index: 60;
|
|
530
483
|
display: flex;
|
|
531
484
|
align-items: center;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
border-radius:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
flex: 1;
|
|
539
|
-
min-width: 0;
|
|
540
|
-
overflow: hidden;
|
|
541
|
-
color: var(--idp-muted-fg);
|
|
485
|
+
gap: 10px;
|
|
486
|
+
max-width: 320px;
|
|
487
|
+
padding: 10px 14px;
|
|
488
|
+
border-radius: 9px;
|
|
489
|
+
border: 1px solid;
|
|
490
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.07);
|
|
542
491
|
font-size: 13px;
|
|
543
|
-
font-weight:
|
|
544
|
-
text-align: left;
|
|
545
|
-
text-overflow: ellipsis;
|
|
546
|
-
white-space: nowrap;
|
|
547
|
-
}
|
|
548
|
-
.org-avatar,
|
|
549
|
-
.user-avatar {
|
|
550
|
-
display: flex;
|
|
551
|
-
align-items: center;
|
|
552
|
-
justify-content: center;
|
|
553
|
-
flex-shrink: 0;
|
|
554
|
-
font-family: var(--idp-mono);
|
|
555
|
-
font-weight: 700;
|
|
556
|
-
}
|
|
557
|
-
.org-avatar {
|
|
558
|
-
width: 14px;
|
|
559
|
-
height: 14px;
|
|
560
|
-
border: 1px solid var(--idp-border);
|
|
561
|
-
border-radius: 6px;
|
|
562
|
-
background: var(--idp-accent-soft);
|
|
563
|
-
color: var(--idp-accent);
|
|
564
|
-
font-size: 5px;
|
|
565
|
-
}
|
|
566
|
-
.user-footer {
|
|
567
|
-
display: flex;
|
|
568
|
-
align-items: center;
|
|
569
|
-
gap: 9px;
|
|
570
|
-
padding: 10px 12px;
|
|
571
|
-
border-top: 1px solid var(--idp-border);
|
|
572
|
-
}
|
|
573
|
-
.user-avatar {
|
|
574
|
-
width: 26px;
|
|
575
|
-
height: 26px;
|
|
576
|
-
border-radius: 50%;
|
|
577
|
-
background: oklch(0.85 0.08 240);
|
|
578
|
-
color: oklch(0.3 0.08 240);
|
|
579
|
-
font-size: 9px;
|
|
580
|
-
}
|
|
581
|
-
.user-meta {
|
|
582
|
-
flex: 1;
|
|
583
|
-
min-width: 0;
|
|
492
|
+
font-weight: 500;
|
|
584
493
|
}
|
|
585
|
-
.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
white-space: nowrap;
|
|
494
|
+
.toast.ok {
|
|
495
|
+
background: var(--idp-ok-bg);
|
|
496
|
+
border-color: var(--idp-ok-border);
|
|
497
|
+
color: var(--idp-ok);
|
|
590
498
|
}
|
|
591
|
-
.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
499
|
+
.toast.warn {
|
|
500
|
+
background: var(--idp-warn-bg);
|
|
501
|
+
border-color: var(--idp-warn-border);
|
|
502
|
+
color: var(--idp-warn);
|
|
595
503
|
}
|
|
596
|
-
.
|
|
597
|
-
|
|
598
|
-
|
|
504
|
+
.toast.error {
|
|
505
|
+
background: var(--idp-error-bg);
|
|
506
|
+
border-color: var(--idp-error-border);
|
|
507
|
+
color: var(--idp-error);
|
|
599
508
|
}
|
|
600
509
|
main {
|
|
601
510
|
height: 100%;
|
|
@@ -604,146 +513,6 @@ export class IdpAdminShell extends DeesElement {
|
|
|
604
513
|
overflow: auto;
|
|
605
514
|
background: var(--idp-bg);
|
|
606
515
|
}
|
|
607
|
-
.page-head {
|
|
608
|
-
display: flex;
|
|
609
|
-
align-items: flex-end;
|
|
610
|
-
justify-content: space-between;
|
|
611
|
-
gap: 16px;
|
|
612
|
-
flex-wrap: wrap;
|
|
613
|
-
padding: 24px 28px 18px;
|
|
614
|
-
border-bottom: 1px solid var(--idp-border);
|
|
615
|
-
background: var(--idp-bg);
|
|
616
|
-
}
|
|
617
|
-
.eyebrow-row {
|
|
618
|
-
display: flex;
|
|
619
|
-
align-items: center;
|
|
620
|
-
gap: 8px;
|
|
621
|
-
margin-bottom: 4px;
|
|
622
|
-
}
|
|
623
|
-
.eyebrow {
|
|
624
|
-
color: var(--idp-muted-fg);
|
|
625
|
-
font-family: var(--idp-mono);
|
|
626
|
-
font-size: 10px;
|
|
627
|
-
font-weight: 600;
|
|
628
|
-
letter-spacing: 0.08em;
|
|
629
|
-
text-transform: uppercase;
|
|
630
|
-
}
|
|
631
|
-
.live-pill {
|
|
632
|
-
display: inline-flex;
|
|
633
|
-
align-items: center;
|
|
634
|
-
gap: 4px;
|
|
635
|
-
padding: 1px 7px;
|
|
636
|
-
border: 1px solid var(--idp-ok-border);
|
|
637
|
-
border-radius: 999px;
|
|
638
|
-
background: var(--idp-ok-bg);
|
|
639
|
-
color: var(--idp-ok);
|
|
640
|
-
font-family: var(--idp-mono);
|
|
641
|
-
font-size: 10px;
|
|
642
|
-
font-weight: 500;
|
|
643
|
-
}
|
|
644
|
-
.live-dot,
|
|
645
|
-
.feed-dot {
|
|
646
|
-
width: 6px;
|
|
647
|
-
height: 6px;
|
|
648
|
-
border-radius: 50%;
|
|
649
|
-
background: var(--idp-ok);
|
|
650
|
-
box-shadow: 0 0 6px var(--idp-ok);
|
|
651
|
-
animation: idp-blink 1.6s ease-in-out infinite;
|
|
652
|
-
}
|
|
653
|
-
@keyframes idp-blink {
|
|
654
|
-
0%, 100% { opacity: 1; }
|
|
655
|
-
50% { opacity: 0.35; }
|
|
656
|
-
}
|
|
657
|
-
h1,
|
|
658
|
-
h2 {
|
|
659
|
-
margin: 0;
|
|
660
|
-
color: var(--idp-fg);
|
|
661
|
-
font-family: var(--idp-display);
|
|
662
|
-
letter-spacing: -0.025em;
|
|
663
|
-
}
|
|
664
|
-
h1 {
|
|
665
|
-
font-size: 26px;
|
|
666
|
-
font-weight: 700;
|
|
667
|
-
line-height: 1.1;
|
|
668
|
-
}
|
|
669
|
-
.lead {
|
|
670
|
-
margin-top: 4px;
|
|
671
|
-
color: var(--idp-muted-fg);
|
|
672
|
-
font-size: 13px;
|
|
673
|
-
}
|
|
674
|
-
.lead code,
|
|
675
|
-
.mono {
|
|
676
|
-
color: var(--idp-info);
|
|
677
|
-
font-family: var(--idp-mono);
|
|
678
|
-
}
|
|
679
|
-
.page-actions {
|
|
680
|
-
display: flex;
|
|
681
|
-
align-items: center;
|
|
682
|
-
gap: 12px;
|
|
683
|
-
}
|
|
684
|
-
.tabs {
|
|
685
|
-
display: inline-flex;
|
|
686
|
-
gap: 1px;
|
|
687
|
-
padding: 2px;
|
|
688
|
-
border: 1px solid var(--idp-border);
|
|
689
|
-
border-radius: 6px;
|
|
690
|
-
background: var(--idp-bg-2);
|
|
691
|
-
}
|
|
692
|
-
.tab {
|
|
693
|
-
padding: 4px 10px;
|
|
694
|
-
border-radius: 4px;
|
|
695
|
-
color: var(--idp-muted-fg);
|
|
696
|
-
font-family: var(--idp-mono);
|
|
697
|
-
font-size: 11px;
|
|
698
|
-
font-weight: 500;
|
|
699
|
-
}
|
|
700
|
-
.tab.active {
|
|
701
|
-
background: var(--idp-muted);
|
|
702
|
-
color: var(--idp-fg);
|
|
703
|
-
}
|
|
704
|
-
.plain-button {
|
|
705
|
-
display: inline-flex;
|
|
706
|
-
align-items: center;
|
|
707
|
-
gap: 6px;
|
|
708
|
-
height: 28px;
|
|
709
|
-
padding: 5px 10px;
|
|
710
|
-
border-radius: 8px;
|
|
711
|
-
background: transparent;
|
|
712
|
-
color: var(--idp-fg);
|
|
713
|
-
font-size: 12px;
|
|
714
|
-
font-weight: 500;
|
|
715
|
-
white-space: nowrap;
|
|
716
|
-
}
|
|
717
|
-
.plain-button.outline {
|
|
718
|
-
border: 1px solid var(--idp-border);
|
|
719
|
-
}
|
|
720
|
-
.plain-button.primary {
|
|
721
|
-
background: var(--idp-accent);
|
|
722
|
-
color: var(--idp-accent-fg);
|
|
723
|
-
}
|
|
724
|
-
.plain-button.destructive {
|
|
725
|
-
background: var(--idp-destructive);
|
|
726
|
-
color: var(--idp-accent-fg);
|
|
727
|
-
}
|
|
728
|
-
.plain-button.ghost {
|
|
729
|
-
color: var(--idp-fg);
|
|
730
|
-
}
|
|
731
|
-
.plain-button:disabled {
|
|
732
|
-
opacity: 0.45;
|
|
733
|
-
cursor: not-allowed;
|
|
734
|
-
}
|
|
735
|
-
.body {
|
|
736
|
-
display: flex;
|
|
737
|
-
flex-direction: column;
|
|
738
|
-
gap: 16px;
|
|
739
|
-
padding: 20px 28px 32px;
|
|
740
|
-
}
|
|
741
|
-
.narrow-body {
|
|
742
|
-
max-width: 700px;
|
|
743
|
-
}
|
|
744
|
-
.wide-body {
|
|
745
|
-
max-width: 860px;
|
|
746
|
-
}
|
|
747
516
|
.section-card {
|
|
748
517
|
margin-bottom: 16px;
|
|
749
518
|
padding: 20px;
|
|
@@ -913,183 +682,54 @@ export class IdpAdminShell extends DeesElement {
|
|
|
913
682
|
}
|
|
914
683
|
.split-row {
|
|
915
684
|
display: flex;
|
|
916
|
-
align-items: center;
|
|
917
|
-
justify-content: space-between;
|
|
918
|
-
gap: 16px;
|
|
919
|
-
}
|
|
920
|
-
.chip-row {
|
|
921
|
-
display: flex;
|
|
922
|
-
flex-wrap: wrap;
|
|
923
|
-
gap: 4px;
|
|
924
|
-
}
|
|
925
|
-
.switch {
|
|
926
|
-
width: 36px;
|
|
927
|
-
height: 20px;
|
|
928
|
-
position: relative;
|
|
929
|
-
flex-shrink: 0;
|
|
930
|
-
border-radius: 10px;
|
|
931
|
-
background: var(--idp-accent);
|
|
932
|
-
}
|
|
933
|
-
.switch::after {
|
|
934
|
-
content: '';
|
|
935
|
-
position: absolute;
|
|
936
|
-
top: 2px;
|
|
937
|
-
left: 18px;
|
|
938
|
-
width: 16px;
|
|
939
|
-
height: 16px;
|
|
940
|
-
border-radius: 8px;
|
|
941
|
-
background: #fff;
|
|
942
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
943
|
-
}
|
|
944
|
-
.sub-tabs {
|
|
945
|
-
width: fit-content;
|
|
946
|
-
display: flex;
|
|
947
|
-
gap: 2px;
|
|
948
|
-
margin-bottom: 16px;
|
|
949
|
-
padding: 3px;
|
|
950
|
-
border-radius: 8px;
|
|
951
|
-
background: var(--idp-muted);
|
|
952
|
-
}
|
|
953
|
-
.sub-tab {
|
|
954
|
-
padding: 5px 12px;
|
|
955
|
-
border-radius: 6px;
|
|
956
|
-
color: var(--idp-muted-fg);
|
|
957
|
-
font-size: 12px;
|
|
958
|
-
}
|
|
959
|
-
.sub-tab.active {
|
|
960
|
-
background: var(--idp-bg);
|
|
961
|
-
color: var(--idp-fg);
|
|
962
|
-
font-weight: 500;
|
|
963
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
|
964
|
-
}
|
|
965
|
-
.kpis {
|
|
966
|
-
display: grid;
|
|
967
|
-
grid-template-columns: repeat(4, 1fr);
|
|
968
|
-
gap: 12px;
|
|
969
|
-
}
|
|
970
|
-
.kpi,
|
|
971
|
-
.card {
|
|
972
|
-
border: 1px solid var(--idp-border);
|
|
973
|
-
background: var(--idp-card);
|
|
974
|
-
}
|
|
975
|
-
.kpi {
|
|
976
|
-
position: relative;
|
|
977
|
-
min-height: 132px;
|
|
978
|
-
display: flex;
|
|
979
|
-
flex-direction: column;
|
|
980
|
-
gap: 4px;
|
|
981
|
-
overflow: hidden;
|
|
982
|
-
padding: 18px 20px 14px;
|
|
983
|
-
border-radius: 10px;
|
|
984
|
-
}
|
|
985
|
-
.kpi::before {
|
|
986
|
-
content: '';
|
|
987
|
-
position: absolute;
|
|
988
|
-
top: 0;
|
|
989
|
-
left: 0;
|
|
990
|
-
right: 0;
|
|
991
|
-
height: 2px;
|
|
992
|
-
background: var(--kpi-accent);
|
|
993
|
-
}
|
|
994
|
-
.kpi-label {
|
|
995
|
-
color: var(--idp-muted-fg);
|
|
996
|
-
font-size: 11.5px;
|
|
997
|
-
font-weight: 500;
|
|
998
|
-
letter-spacing: 0.02em;
|
|
999
|
-
}
|
|
1000
|
-
.kpi-value {
|
|
1001
|
-
color: var(--idp-fg);
|
|
1002
|
-
font-family: var(--idp-display);
|
|
1003
|
-
font-size: 30px;
|
|
1004
|
-
font-weight: 700;
|
|
1005
|
-
font-variant-numeric: tabular-nums;
|
|
1006
|
-
letter-spacing: -0.025em;
|
|
1007
|
-
line-height: 1.1;
|
|
1008
|
-
}
|
|
1009
|
-
.kpi-value span {
|
|
1010
|
-
margin-left: 2px;
|
|
1011
|
-
color: var(--idp-muted-fg);
|
|
1012
|
-
font-size: 14px;
|
|
1013
|
-
font-weight: 500;
|
|
1014
|
-
}
|
|
1015
|
-
.kpi-sub {
|
|
1016
|
-
color: var(--idp-fg-3);
|
|
1017
|
-
font-family: var(--idp-mono);
|
|
1018
|
-
font-size: 11px;
|
|
1019
|
-
}
|
|
1020
|
-
.kpi-foot {
|
|
1021
|
-
display: flex;
|
|
1022
|
-
align-items: flex-end;
|
|
685
|
+
align-items: center;
|
|
1023
686
|
justify-content: space-between;
|
|
1024
|
-
gap:
|
|
1025
|
-
margin-top: auto;
|
|
687
|
+
gap: 16px;
|
|
1026
688
|
}
|
|
1027
|
-
.
|
|
1028
|
-
display:
|
|
1029
|
-
|
|
689
|
+
.chip-row {
|
|
690
|
+
display: flex;
|
|
691
|
+
flex-wrap: wrap;
|
|
1030
692
|
gap: 4px;
|
|
1031
|
-
color: var(--idp-ok);
|
|
1032
|
-
font-family: var(--idp-mono);
|
|
1033
|
-
font-size: 11px;
|
|
1034
|
-
font-weight: 600;
|
|
1035
693
|
}
|
|
1036
|
-
.
|
|
1037
|
-
width:
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
grid-template-columns: minmax(0, 1.6fr) minmax(300px, 1fr);
|
|
1044
|
-
gap: 16px;
|
|
694
|
+
.switch {
|
|
695
|
+
width: 36px;
|
|
696
|
+
height: 20px;
|
|
697
|
+
position: relative;
|
|
698
|
+
flex-shrink: 0;
|
|
699
|
+
border-radius: 10px;
|
|
700
|
+
background: var(--idp-accent);
|
|
1045
701
|
}
|
|
1046
|
-
.
|
|
1047
|
-
|
|
702
|
+
.switch::after {
|
|
703
|
+
content: '';
|
|
704
|
+
position: absolute;
|
|
705
|
+
top: 2px;
|
|
706
|
+
left: 18px;
|
|
707
|
+
width: 16px;
|
|
708
|
+
height: 16px;
|
|
1048
709
|
border-radius: 8px;
|
|
710
|
+
background: #fff;
|
|
711
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
1049
712
|
}
|
|
1050
|
-
.
|
|
713
|
+
.sub-tabs {
|
|
714
|
+
width: fit-content;
|
|
1051
715
|
display: flex;
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
padding:
|
|
1055
|
-
border-
|
|
1056
|
-
|
|
1057
|
-
.card-title {
|
|
1058
|
-
color: var(--idp-fg);
|
|
1059
|
-
font-size: 13px;
|
|
1060
|
-
font-weight: 600;
|
|
1061
|
-
}
|
|
1062
|
-
.feed-dot {
|
|
1063
|
-
display: inline-block;
|
|
1064
|
-
width: 8px;
|
|
1065
|
-
height: 8px;
|
|
1066
|
-
border-radius: 4px;
|
|
1067
|
-
background: var(--dot-color);
|
|
1068
|
-
box-shadow: var(--dot-glow, none);
|
|
716
|
+
gap: 2px;
|
|
717
|
+
margin-bottom: 16px;
|
|
718
|
+
padding: 3px;
|
|
719
|
+
border-radius: 8px;
|
|
720
|
+
background: var(--idp-muted);
|
|
1069
721
|
}
|
|
1070
|
-
.
|
|
722
|
+
.sub-tab {
|
|
723
|
+
padding: 5px 12px;
|
|
724
|
+
border-radius: 6px;
|
|
1071
725
|
color: var(--idp-muted-fg);
|
|
1072
|
-
font-
|
|
1073
|
-
font-size: 11.5px;
|
|
1074
|
-
}
|
|
1075
|
-
.feed-item {
|
|
1076
|
-
display: grid;
|
|
1077
|
-
grid-template-columns: 14px 1fr auto;
|
|
1078
|
-
gap: 12px;
|
|
1079
|
-
align-items: center;
|
|
1080
|
-
padding: 11px 16px;
|
|
1081
|
-
border-bottom: 1px solid var(--idp-border-soft);
|
|
1082
|
-
}
|
|
1083
|
-
.feed-item:last-child {
|
|
1084
|
-
border-bottom: 0;
|
|
1085
|
-
}
|
|
1086
|
-
.feed-text {
|
|
1087
|
-
color: var(--idp-fg-2);
|
|
1088
|
-
font-size: 12.5px;
|
|
726
|
+
font-size: 12px;
|
|
1089
727
|
}
|
|
1090
|
-
.
|
|
728
|
+
.sub-tab.active {
|
|
729
|
+
background: var(--idp-bg);
|
|
1091
730
|
color: var(--idp-fg);
|
|
1092
|
-
font-weight:
|
|
731
|
+
font-weight: 500;
|
|
732
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
|
1093
733
|
}
|
|
1094
734
|
.dialog-backdrop {
|
|
1095
735
|
position: fixed;
|
|
@@ -1190,29 +830,11 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1190
830
|
.shell {
|
|
1191
831
|
grid-template-columns: 1fr;
|
|
1192
832
|
}
|
|
1193
|
-
|
|
833
|
+
idp-admin-sidebar {
|
|
1194
834
|
display: none;
|
|
1195
835
|
}
|
|
1196
|
-
.kpis {
|
|
1197
|
-
grid-template-columns: repeat(2, 1fr);
|
|
1198
|
-
}
|
|
1199
|
-
.primary-grid,
|
|
1200
|
-
.secondary-grid {
|
|
1201
|
-
grid-template-columns: 1fr;
|
|
1202
|
-
}
|
|
1203
836
|
}
|
|
1204
837
|
@media (max-width: 720px) {
|
|
1205
|
-
.page-head,
|
|
1206
|
-
.body {
|
|
1207
|
-
padding-left: 20px;
|
|
1208
|
-
padding-right: 20px;
|
|
1209
|
-
}
|
|
1210
|
-
.page-actions {
|
|
1211
|
-
flex-wrap: wrap;
|
|
1212
|
-
}
|
|
1213
|
-
.kpis {
|
|
1214
|
-
grid-template-columns: 1fr;
|
|
1215
|
-
}
|
|
1216
838
|
.mapping-row {
|
|
1217
839
|
grid-template-columns: 1fr;
|
|
1218
840
|
}
|
|
@@ -1289,8 +911,37 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1289
911
|
@property({ type: String, attribute: 'credential-message' })
|
|
1290
912
|
public accessor credentialMessage = '';
|
|
1291
913
|
|
|
914
|
+
@property({ type: Array })
|
|
915
|
+
public accessor accounts: IIdpAdminUser[] = [];
|
|
916
|
+
|
|
917
|
+
@property({ type: Array })
|
|
918
|
+
public accessor gaUsers: IIdpAdminGaUser[] = [];
|
|
919
|
+
|
|
920
|
+
@property({ type: Object })
|
|
921
|
+
public accessor overviewData: IIdpAdminOverviewData | null = null;
|
|
922
|
+
|
|
923
|
+
@property({ type: Array })
|
|
924
|
+
public accessor delegationApps: IIdpDelegationApp[] = [];
|
|
925
|
+
|
|
926
|
+
@property({ type: Array })
|
|
927
|
+
public accessor delegations: IIdpDelegation[] = [];
|
|
928
|
+
|
|
929
|
+
@state()
|
|
930
|
+
private accessor logoutEverywhere = false;
|
|
931
|
+
|
|
932
|
+
@state()
|
|
933
|
+
private accessor createOrgName = '';
|
|
934
|
+
|
|
935
|
+
@state()
|
|
936
|
+
private accessor createOrgSlug = '';
|
|
937
|
+
|
|
938
|
+
@state()
|
|
939
|
+
private accessor gaUserSearch = '';
|
|
940
|
+
|
|
1292
941
|
@state()
|
|
1293
|
-
private accessor
|
|
942
|
+
private accessor toast: { message: string; tone: 'ok' | 'warn' | 'error' } | null = null;
|
|
943
|
+
|
|
944
|
+
private toastTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1294
945
|
|
|
1295
946
|
@state()
|
|
1296
947
|
private accessor currentPassword = '';
|
|
@@ -1329,7 +980,7 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1329
980
|
private accessor orgSettingsError = '';
|
|
1330
981
|
|
|
1331
982
|
@state()
|
|
1332
|
-
private accessor dialogMode: 'none' | 'role-upsert' | 'role-delete' | 'member-roles' | 'app-role-mappings' = 'none';
|
|
983
|
+
private accessor dialogMode: 'none' | 'role-upsert' | 'role-delete' | 'member-roles' | 'app-role-mappings' | 'create-org' | 'logout-confirm' = 'none';
|
|
1333
984
|
|
|
1334
985
|
@state()
|
|
1335
986
|
private accessor dialogError = '';
|
|
@@ -1358,34 +1009,6 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1358
1009
|
@state()
|
|
1359
1010
|
private accessor dialogAppMappings: IIdpAdminAppRoleMapping[] = [];
|
|
1360
1011
|
|
|
1361
|
-
private workspaceNav: TNavItem[] = [
|
|
1362
|
-
{ id: 'overview', label: 'Overview', icon: 'grid' },
|
|
1363
|
-
];
|
|
1364
|
-
|
|
1365
|
-
private accountNav: TNavItem[] = [
|
|
1366
|
-
{ id: 'profile', label: 'Profile', icon: 'user' },
|
|
1367
|
-
{ id: 'security', label: 'Security', icon: 'shield' },
|
|
1368
|
-
{ id: 'sessions', label: 'Sessions & Devices', icon: 'monitor' },
|
|
1369
|
-
{ id: 'apps', label: 'Connected Apps', icon: 'grid' },
|
|
1370
|
-
];
|
|
1371
|
-
|
|
1372
|
-
private orgNav: TNavItem[] = [
|
|
1373
|
-
{ id: 'org-general', label: 'General', icon: 'building' },
|
|
1374
|
-
{ id: 'org-settings', label: 'Settings', icon: 'settings' },
|
|
1375
|
-
{ id: 'org-members', label: 'Members', icon: 'users' },
|
|
1376
|
-
{ id: 'org-apps', label: 'OAuth Apps', icon: 'box' },
|
|
1377
|
-
];
|
|
1378
|
-
|
|
1379
|
-
private supportNav: TNavItem[] = [
|
|
1380
|
-
{ id: 'support', label: 'Support', icon: 'shield' },
|
|
1381
|
-
];
|
|
1382
|
-
|
|
1383
|
-
private adminNav: TNavItem[] = [
|
|
1384
|
-
{ id: 'ga-users', label: 'All Users', icon: 'users' },
|
|
1385
|
-
{ id: 'ga-orgs', label: 'All Organisations', icon: 'building' },
|
|
1386
|
-
{ id: 'ga-apps', label: 'Platform Apps', icon: 'globe' },
|
|
1387
|
-
];
|
|
1388
|
-
|
|
1389
1012
|
private get safeOrgs(): IIdpAdminOrg[] {
|
|
1390
1013
|
return Array.isArray(this.orgs) ? this.orgs.filter(Boolean) : [];
|
|
1391
1014
|
}
|
|
@@ -1617,7 +1240,6 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1617
1240
|
|
|
1618
1241
|
private setPage(pageArg: TIdpAdminPage) {
|
|
1619
1242
|
this.page = pageArg;
|
|
1620
|
-
this.orgMenuOpen = false;
|
|
1621
1243
|
this.dispatchEvent(new CustomEvent<IIdpAdminNavigateEventDetail>('idp-admin-navigate', {
|
|
1622
1244
|
detail: { page: pageArg },
|
|
1623
1245
|
bubbles: true,
|
|
@@ -1625,26 +1247,76 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1625
1247
|
}));
|
|
1626
1248
|
}
|
|
1627
1249
|
|
|
1628
|
-
private
|
|
1629
|
-
this.
|
|
1630
|
-
|
|
1250
|
+
private handleSidebarNavigate(eventArg: CustomEvent<IIdpAdminNavigateEventDetail>) {
|
|
1251
|
+
this.page = eventArg.detail.page;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
private handleSidebarOrgSelect(eventArg: CustomEvent<IIdpAdminOrgSelectEventDetail>) {
|
|
1255
|
+
this.selectedOrgId = eventArg.detail.orgId;
|
|
1631
1256
|
this.page = 'org-general';
|
|
1632
|
-
this.dispatchEvent(new CustomEvent<IIdpAdminOrgSelectEventDetail>('idp-admin-org-select', {
|
|
1633
|
-
detail: {
|
|
1634
|
-
orgId: orgIdArg,
|
|
1635
|
-
org: this.orgs.find((orgArg) => orgArg.id === orgIdArg) || null,
|
|
1636
|
-
},
|
|
1637
|
-
bubbles: true,
|
|
1638
|
-
composed: true,
|
|
1639
|
-
}));
|
|
1640
1257
|
}
|
|
1641
1258
|
|
|
1642
1259
|
private requestOrgCreate() {
|
|
1643
|
-
this.
|
|
1644
|
-
this.
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1260
|
+
this.createOrgName = '';
|
|
1261
|
+
this.createOrgSlug = '';
|
|
1262
|
+
this.dialogError = '';
|
|
1263
|
+
this.dialogMode = 'create-org';
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
private slugify(valueArg: string): string {
|
|
1267
|
+
return valueArg
|
|
1268
|
+
.toLowerCase()
|
|
1269
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
1270
|
+
.replace(/-+/g, '-')
|
|
1271
|
+
.replace(/^-|-$/g, '');
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
private setCreateOrgName(eventArg: Event) {
|
|
1275
|
+
const value = (eventArg.target as HTMLInputElement).value;
|
|
1276
|
+
this.createOrgName = value;
|
|
1277
|
+
this.createOrgSlug = this.slugify(value);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
private submitCreateOrg() {
|
|
1281
|
+
if (!this.createOrgName.trim()) {
|
|
1282
|
+
this.dialogError = 'Enter an organisation name.';
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
this.dispatchShellEvent<IIdpAdminOrgCreateEventDetail>('idp-admin-org-create', {
|
|
1286
|
+
name: this.createOrgName.trim(),
|
|
1287
|
+
slug: this.createOrgSlug || this.slugify(this.createOrgName),
|
|
1288
|
+
});
|
|
1289
|
+
this.closeDialog();
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
private requestLogout() {
|
|
1293
|
+
this.logoutEverywhere = false;
|
|
1294
|
+
this.dialogMode = 'logout-confirm';
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
private confirmLogout() {
|
|
1298
|
+
const everywhere = this.logoutEverywhere;
|
|
1299
|
+
this.closeDialog();
|
|
1300
|
+
this.dispatchShellEvent<IIdpAdminLogoutEventDetail>('idp-admin-logout', { everywhere });
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
public showToast(messageArg: string, toneArg: 'ok' | 'warn' | 'error' = 'ok') {
|
|
1304
|
+
this.toast = { message: messageArg, tone: toneArg };
|
|
1305
|
+
if (this.toastTimer) {
|
|
1306
|
+
clearTimeout(this.toastTimer);
|
|
1307
|
+
}
|
|
1308
|
+
this.toastTimer = setTimeout(() => {
|
|
1309
|
+
this.toast = null;
|
|
1310
|
+
this.toastTimer = null;
|
|
1311
|
+
}, 3000);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
public override async disconnectedCallback() {
|
|
1315
|
+
if (this.toastTimer) {
|
|
1316
|
+
clearTimeout(this.toastTimer);
|
|
1317
|
+
this.toastTimer = null;
|
|
1318
|
+
}
|
|
1319
|
+
await super.disconnectedCallback();
|
|
1648
1320
|
}
|
|
1649
1321
|
|
|
1650
1322
|
private dispatchShellEvent<TDetail>(eventNameArg: string, detailArg: TDetail) {
|
|
@@ -1656,30 +1328,11 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1656
1328
|
}
|
|
1657
1329
|
|
|
1658
1330
|
private formatTimeAgo(timestampArg?: number): string {
|
|
1659
|
-
|
|
1660
|
-
return 'unknown';
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
const diff = Date.now() - timestampArg;
|
|
1664
|
-
const minutes = Math.floor(diff / 60000);
|
|
1665
|
-
const hours = Math.floor(diff / 3600000);
|
|
1666
|
-
const days = Math.floor(diff / 86400000);
|
|
1667
|
-
|
|
1668
|
-
if (minutes < 1) return 'just now';
|
|
1669
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
1670
|
-
if (hours < 24) return `${hours}h ago`;
|
|
1671
|
-
if (days < 7) return `${days}d ago`;
|
|
1672
|
-
return new Date(timestampArg).toLocaleDateString();
|
|
1331
|
+
return formatAdminTimeAgo(timestampArg);
|
|
1673
1332
|
}
|
|
1674
1333
|
|
|
1675
1334
|
private renderStateCard(titleArg: string, descriptionArg: string, iconArg = 'box'): TemplateResult {
|
|
1676
|
-
return
|
|
1677
|
-
<div class="state-card">
|
|
1678
|
-
<span class="state-icon"><idp-icon name=${iconArg as any} size="17"></idp-icon></span>
|
|
1679
|
-
<div class="state-title">${titleArg}</div>
|
|
1680
|
-
<div class="state-description">${descriptionArg}</div>
|
|
1681
|
-
</div>
|
|
1682
|
-
`;
|
|
1335
|
+
return renderAdminStateCard(titleArg, descriptionArg, iconArg);
|
|
1683
1336
|
}
|
|
1684
1337
|
|
|
1685
1338
|
private renderDataState(emptyTitleArg: string, emptyDescriptionArg: string, iconArg = 'box'): TemplateResult | null {
|
|
@@ -1995,121 +1648,66 @@ export class IdpAdminShell extends DeesElement {
|
|
|
1995
1648
|
`;
|
|
1996
1649
|
}
|
|
1997
1650
|
|
|
1998
|
-
private
|
|
1999
|
-
if (this.dialogMode === 'none') {
|
|
2000
|
-
return html``;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
const dialog = this.dialogMode === 'role-upsert'
|
|
2004
|
-
? this.renderRoleUpsertDialog()
|
|
2005
|
-
: this.dialogMode === 'role-delete'
|
|
2006
|
-
? this.renderRoleDeleteDialog()
|
|
2007
|
-
: this.dialogMode === 'member-roles'
|
|
2008
|
-
? this.renderMemberRolesDialog()
|
|
2009
|
-
: this.renderAppRoleMappingsDialog();
|
|
2010
|
-
return html`<div class="dialog-backdrop" @click=${() => this.closeDialog()}>${dialog}</div>`;
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
private renderNavGroup(items: TNavItem[], active = ''): TemplateResult {
|
|
1651
|
+
private renderCreateOrgDialog(): TemplateResult {
|
|
2014
1652
|
return html`
|
|
2015
|
-
<div class="
|
|
2016
|
-
|
|
2017
|
-
<
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
1653
|
+
<div class="dialog-card" @click=${(eventArg: Event) => eventArg.stopPropagation()}>
|
|
1654
|
+
<div class="dialog-head">
|
|
1655
|
+
<div><div class="dialog-title">Create organisation</div><div class="muted">Organisations group members, roles, and OAuth apps.</div></div>
|
|
1656
|
+
<button class="plain-button ghost" @click=${() => this.closeDialog()}>Close</button>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div class="dialog-body">
|
|
1659
|
+
${this.renderDialogError()}
|
|
1660
|
+
${this.renderFormRow('Organisation name', 'Shown to members and in app consent screens.', html`<input class="input" placeholder="Acme Corp" .value=${this.createOrgName} @input=${(eventArg: Event) => this.setCreateOrgName(eventArg)} />`, true)}
|
|
1661
|
+
${this.renderFormRow('URL slug', 'Public identifier under idp.global/', html`<input class="input" placeholder="acme-corp" .value=${this.createOrgSlug} @input=${(eventArg: Event) => { this.createOrgSlug = this.slugify((eventArg.target as HTMLInputElement).value); }} />`)}
|
|
1662
|
+
</div>
|
|
1663
|
+
<div class="dialog-actions">
|
|
1664
|
+
<button class="plain-button outline" @click=${() => this.closeDialog()}>Cancel</button>
|
|
1665
|
+
<button class="plain-button primary" ?disabled=${!this.createOrgName.trim()} @click=${() => this.submitCreateOrg()}>Create</button>
|
|
1666
|
+
</div>
|
|
2023
1667
|
</div>
|
|
2024
1668
|
`;
|
|
2025
1669
|
}
|
|
2026
1670
|
|
|
2027
|
-
private
|
|
2028
|
-
const
|
|
1671
|
+
private renderLogoutConfirmDialog(): TemplateResult {
|
|
1672
|
+
const otherSessionCount = this.sessions.filter((sessionArg) => !sessionArg.isCurrent).length;
|
|
2029
1673
|
return html`
|
|
2030
|
-
<
|
|
2031
|
-
<div class="
|
|
2032
|
-
<div class="
|
|
2033
|
-
|
|
2034
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2035
|
-
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
|
2036
|
-
</svg>
|
|
2037
|
-
</div>
|
|
2038
|
-
<span class="logo-text">idp.global</span>
|
|
2039
|
-
</div>
|
|
1674
|
+
<div class="dialog-card" @click=${(eventArg: Event) => eventArg.stopPropagation()}>
|
|
1675
|
+
<div class="dialog-head">
|
|
1676
|
+
<div><div class="dialog-title">Sign out?</div></div>
|
|
1677
|
+
<button class="plain-button ghost" @click=${() => this.closeDialog()}>Close</button>
|
|
2040
1678
|
</div>
|
|
2041
|
-
<div class="
|
|
2042
|
-
<div class="
|
|
2043
|
-
<
|
|
2044
|
-
|
|
2045
|
-
<
|
|
2046
|
-
|
|
2047
|
-
<button class="org-switch ${this.orgMenuOpen ? 'open' : ''}" @click=${() => { this.orgMenuOpen = !this.orgMenuOpen; }}>
|
|
2048
|
-
<span class="org-avatar">${currentOrg.name.slice(0, 2).toUpperCase()}</span>
|
|
2049
|
-
<span class="org-name">${currentOrg.name}</span>
|
|
2050
|
-
<idp-icon name="chevron-down" size="10"></idp-icon>
|
|
2051
|
-
</button>
|
|
2052
|
-
${this.orgMenuOpen ? html`
|
|
2053
|
-
<div class="org-menu">
|
|
2054
|
-
<div class="org-menu-title">Switch organisation</div>
|
|
2055
|
-
${this.safeOrgs.map((orgArg) => html`
|
|
2056
|
-
<button class="org-menu-item ${orgArg.id === this.selectedOrgId ? 'selected' : ''}" @click=${() => this.selectOrg(orgArg.id)}>
|
|
2057
|
-
<span class="org-avatar">${orgArg.name.slice(0, 2).toUpperCase()}</span>
|
|
2058
|
-
<span style="flex:1;min-width:0"><span class="org-menu-name">${orgArg.name}</span><span class="org-menu-role">${orgArg.myRole || 'member'}</span></span>
|
|
2059
|
-
${orgArg.id === this.selectedOrgId ? html`<idp-icon name="check" size="11"></idp-icon>` : html``}
|
|
2060
|
-
</button>
|
|
2061
|
-
`)}
|
|
2062
|
-
<div class="org-menu-divider"></div>
|
|
2063
|
-
<button class="org-create" @click=${() => this.requestOrgCreate()}><span class="org-create-icon"><idp-icon name="plus" size="9"></idp-icon></span>Create organisation</button>
|
|
2064
|
-
</div>
|
|
2065
|
-
` : html``}
|
|
2066
|
-
</div>
|
|
2067
|
-
${this.renderNavGroup(this.orgNav, this.page)}
|
|
2068
|
-
</div>
|
|
2069
|
-
<div class="nav-section"><div class="nav-title">Support</div>${this.renderNavGroup(this.supportNav, this.page)}</div>
|
|
2070
|
-
${this.globalAdmin ? html`<div class="nav-section"><div class="nav-title">Global Admin</div>${this.renderNavGroup(this.adminNav, this.page)}</div>` : html``}
|
|
1679
|
+
<div class="dialog-body">
|
|
1680
|
+
<div class="muted">You'll be signed out of idp.global on this device. Apps you've signed into with idp.global keep their own sessions.</div>
|
|
1681
|
+
<label class="logout-everywhere-row">
|
|
1682
|
+
<idp-toggle .checked=${this.logoutEverywhere} @idp-toggle-change=${(eventArg: CustomEvent) => { this.logoutEverywhere = !!eventArg.detail?.checked; }}></idp-toggle>
|
|
1683
|
+
<span>Also sign out of all other devices${otherSessionCount ? html` <span class="muted">(${otherSessionCount} other ${otherSessionCount === 1 ? 'session' : 'sessions'})</span>` : html``}</span>
|
|
1684
|
+
</label>
|
|
2071
1685
|
</div>
|
|
2072
|
-
<div class="
|
|
2073
|
-
<
|
|
2074
|
-
<
|
|
1686
|
+
<div class="dialog-actions">
|
|
1687
|
+
<button class="plain-button outline" @click=${() => this.closeDialog()}>Cancel</button>
|
|
1688
|
+
<button class="plain-button destructive" @click=${() => this.confirmLogout()}>${this.logoutEverywhere ? 'Sign out everywhere' : 'Sign out'}</button>
|
|
2075
1689
|
</div>
|
|
2076
|
-
</
|
|
1690
|
+
</div>
|
|
2077
1691
|
`;
|
|
2078
1692
|
}
|
|
2079
1693
|
|
|
2080
|
-
private
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
const width = 100;
|
|
2085
|
-
const height = 22;
|
|
2086
|
-
const points = data.map((valueArg, indexArg) => {
|
|
2087
|
-
const x = (indexArg / (data.length - 1)) * width;
|
|
2088
|
-
const y = height - ((valueArg - min) / range) * (height - 4) - 2;
|
|
2089
|
-
return `${x},${y}`;
|
|
2090
|
-
}).join(' ');
|
|
2091
|
-
const area = `0,${height} ${points} ${width},${height}`;
|
|
2092
|
-
|
|
2093
|
-
return html`
|
|
2094
|
-
<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none">
|
|
2095
|
-
<polygon points=${area} fill=${color} opacity="0.12"></polygon>
|
|
2096
|
-
<polyline points=${points} fill="none" stroke=${color} stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></polyline>
|
|
2097
|
-
</svg>
|
|
2098
|
-
`;
|
|
2099
|
-
}
|
|
1694
|
+
private renderDialog(): TemplateResult {
|
|
1695
|
+
if (this.dialogMode === 'none') {
|
|
1696
|
+
return html``;
|
|
1697
|
+
}
|
|
2100
1698
|
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
1699
|
+
const dialog = this.dialogMode === 'role-upsert'
|
|
1700
|
+
? this.renderRoleUpsertDialog()
|
|
1701
|
+
: this.dialogMode === 'role-delete'
|
|
1702
|
+
? this.renderRoleDeleteDialog()
|
|
1703
|
+
: this.dialogMode === 'member-roles'
|
|
1704
|
+
? this.renderMemberRolesDialog()
|
|
1705
|
+
: this.dialogMode === 'create-org'
|
|
1706
|
+
? this.renderCreateOrgDialog()
|
|
1707
|
+
: this.dialogMode === 'logout-confirm'
|
|
1708
|
+
? this.renderLogoutConfirmDialog()
|
|
1709
|
+
: this.renderAppRoleMappingsDialog();
|
|
1710
|
+
return html`<div class="dialog-backdrop" @click=${() => this.closeDialog()}>${dialog}</div>`;
|
|
2113
1711
|
}
|
|
2114
1712
|
|
|
2115
1713
|
private renderPageHeader(titleArg: string, descriptionArg: string, actionArg?: TemplateResult): TemplateResult {
|
|
@@ -2149,78 +1747,17 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2149
1747
|
}
|
|
2150
1748
|
|
|
2151
1749
|
private renderOverview(): TemplateResult {
|
|
2152
|
-
const firstName = this.user?.name?.split(' ')[0] || 'there';
|
|
2153
|
-
const activeSessionCount = this.sessions.filter((sessionArg) => sessionArg.isCurrent).length || this.sessions.length;
|
|
2154
|
-
const activePassportCount = this.passportDevices.filter((deviceArg) => deviceArg.status === 'active').length;
|
|
2155
|
-
const kpis: TKpi[] = [
|
|
2156
|
-
{
|
|
2157
|
-
label: 'Organisations',
|
|
2158
|
-
value: String(this.orgs.length),
|
|
2159
|
-
delta: 'live',
|
|
2160
|
-
deltaKind: 'live',
|
|
2161
|
-
spark: [1, 1, 1, 1, 1, 1, 1, Math.max(this.orgs.length, 1)],
|
|
2162
|
-
sparkColor: 'var(--idp-spark-info)',
|
|
2163
|
-
accent: 'var(--idp-chart-1)',
|
|
2164
|
-
sub: 'Memberships visible to this account',
|
|
2165
|
-
},
|
|
2166
|
-
{
|
|
2167
|
-
label: 'Sessions',
|
|
2168
|
-
value: String(this.sessions.length),
|
|
2169
|
-
delta: `${activeSessionCount} active`,
|
|
2170
|
-
deltaKind: 'up',
|
|
2171
|
-
spark: [1, 2, 1, 2, 3, 2, Math.max(this.sessions.length, 1)],
|
|
2172
|
-
sparkColor: 'var(--idp-spark-up)',
|
|
2173
|
-
accent: 'var(--idp-chart-2)',
|
|
2174
|
-
sub: this.sessions.length ? `Latest ${this.formatTimeAgo(Math.max(...this.sessions.map((sessionArg) => sessionArg.lastActive)))}` : 'No active sessions loaded',
|
|
2175
|
-
},
|
|
2176
|
-
{
|
|
2177
|
-
label: 'Passport devices',
|
|
2178
|
-
value: String(this.passportDevices.length),
|
|
2179
|
-
delta: `${activePassportCount} active`,
|
|
2180
|
-
deltaKind: 'live',
|
|
2181
|
-
spark: [1, 1, 1, Math.max(this.passportDevices.length, 1)],
|
|
2182
|
-
sparkColor: 'var(--idp-spark-info)',
|
|
2183
|
-
accent: 'var(--idp-chart-5)',
|
|
2184
|
-
sub: 'Cryptographic credentials',
|
|
2185
|
-
},
|
|
2186
|
-
{
|
|
2187
|
-
label: 'Activity',
|
|
2188
|
-
value: String(this.activities.length),
|
|
2189
|
-
delta: 'live',
|
|
2190
|
-
deltaKind: 'live',
|
|
2191
|
-
spark: [1, 2, 2, 3, Math.max(this.activities.length, 1)],
|
|
2192
|
-
sparkColor: 'var(--idp-spark-info)',
|
|
2193
|
-
accent: 'var(--idp-info)',
|
|
2194
|
-
sub: 'Recent account events',
|
|
2195
|
-
},
|
|
2196
|
-
];
|
|
2197
1750
|
return html`
|
|
2198
|
-
<
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
${this.dataLoading || this.dataError ? this.renderDataState('No overview data', 'The console has not received live account data yet.', 'activity') : html``}
|
|
2209
|
-
<div class="primary-grid">
|
|
2210
|
-
<div class="card">
|
|
2211
|
-
<div class="card-head"><span class="card-title">Recent activity</span><idp-badge>${this.activities.length} events</idp-badge></div>
|
|
2212
|
-
${this.activities.length ? this.activities.slice(0, 8).map((activityArg) => html`
|
|
2213
|
-
<div class="feed-item"><span class="feed-dot" style="--dot-color:var(--idp-info)"></span><div class="feed-text"><strong>${activityArg.action.replace(/_/g, ' ')}</strong> - ${activityArg.description}</div><span class="feed-meta">${this.formatTimeAgo(activityArg.timestamp)}</span></div>
|
|
2214
|
-
`) : this.renderStateCard('No activity yet', 'Activity events will appear here after logins, app changes, and organisation updates.', 'activity')}
|
|
2215
|
-
</div>
|
|
2216
|
-
<div class="card">
|
|
2217
|
-
<div class="card-head"><span class="card-title">Current sessions</span><idp-badge>${this.sessions.length} total</idp-badge></div>
|
|
2218
|
-
${this.sessions.length ? this.sessions.slice(0, 5).map((sessionArg) => html`
|
|
2219
|
-
<div class="feed-item"><span class="feed-dot" style="--dot-color:${sessionArg.isCurrent ? 'var(--idp-ok)' : 'var(--idp-muted-fg)'}"></span><div class="feed-text"><strong>${sessionArg.deviceName || 'Unknown device'}</strong> - <span class="mono">${sessionArg.browser} ${sessionArg.os}</span></div><span class="feed-meta">${this.formatTimeAgo(sessionArg.lastActive)}</span></div>
|
|
2220
|
-
`) : this.renderStateCard('No sessions loaded', 'Active session telemetry is unavailable or there are no sessions.', 'monitor')}
|
|
2221
|
-
</div>
|
|
2222
|
-
</div>
|
|
2223
|
-
</div>
|
|
1751
|
+
<idp-admin-overview
|
|
1752
|
+
.user=${this.user}
|
|
1753
|
+
.orgs=${this.orgs}
|
|
1754
|
+
.sessions=${this.sessions}
|
|
1755
|
+
.activities=${this.activities}
|
|
1756
|
+
.passportDevices=${this.passportDevices}
|
|
1757
|
+
.dataLoading=${this.dataLoading}
|
|
1758
|
+
.dataError=${this.dataError}
|
|
1759
|
+
.overviewData=${this.overviewData}
|
|
1760
|
+
></idp-admin-overview>
|
|
2224
1761
|
`;
|
|
2225
1762
|
}
|
|
2226
1763
|
|
|
@@ -2238,6 +1775,9 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2238
1775
|
${this.renderFormRow('Mobile number', 'Used for SMS verification', html`<input class="input" .value=${this.user?.mobileNumber || ''} disabled />`)}
|
|
2239
1776
|
`)}
|
|
2240
1777
|
${this.renderSectionCard('Account status', '', html`<div class="split-row"><div><div class="section-title">Status</div><div class="muted">Your account is currently ${status}.</div></div><idp-badge variant=${status === 'active' ? 'ok' : status === 'suspended' ? 'error' : 'warn'}>${status}</idp-badge></div>${this.globalAdmin ? html`<div class="divider"></div><div class="split-row"><div><div class="section-title">Global admin</div><div class="muted">You have platform-wide administrative access.</div></div><idp-badge variant="accent">Admin</idp-badge></div>` : html``}`)}
|
|
1778
|
+
${this.delegationApps.length
|
|
1779
|
+
? html`<idp-delegation-section .apps=${this.delegationApps} .delegations=${this.delegations}></idp-delegation-section>`
|
|
1780
|
+
: html``}
|
|
2241
1781
|
</div>
|
|
2242
1782
|
`;
|
|
2243
1783
|
}
|
|
@@ -2411,7 +1951,7 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2411
1951
|
|| searchableText.includes('organisation');
|
|
2412
1952
|
})
|
|
2413
1953
|
.slice(0, 5);
|
|
2414
|
-
const kpis:
|
|
1954
|
+
const kpis: TIdpAdminKpi[] = [
|
|
2415
1955
|
{
|
|
2416
1956
|
label: 'Members',
|
|
2417
1957
|
value: String(this.orgMembers.length),
|
|
@@ -2464,7 +2004,7 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2464
2004
|
<div class="page-actions"><button class="plain-button outline" @click=${() => this.setPage('org-members')}>Manage members</button><button class="plain-button primary" @click=${() => this.setPage('org-apps')}>Manage apps</button></div>
|
|
2465
2005
|
</header>
|
|
2466
2006
|
<div class="body">
|
|
2467
|
-
<div class="kpis">${kpis.map((kpiArg) =>
|
|
2007
|
+
<div class="kpis">${kpis.map((kpiArg) => renderAdminKpi(kpiArg))}</div>
|
|
2468
2008
|
${this.dataLoading || this.dataError ? this.renderDataState('No organisation data', 'The console has not received live organisation data yet.', 'building') : html``}
|
|
2469
2009
|
<div class="primary-grid">
|
|
2470
2010
|
<div class="card">
|
|
@@ -2695,10 +2235,64 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2695
2235
|
}
|
|
2696
2236
|
|
|
2697
2237
|
private renderGAUsers(): TemplateResult {
|
|
2698
|
-
|
|
2238
|
+
if (!this.gaUsers.length) {
|
|
2239
|
+
return html`${this.renderPageHeader('All Users', 'Platform-wide user administration.')}<div class="body wide-body">${this.renderDataState('User directory not connected', 'The shell is ready for global user data, but no platform user-list endpoint is wired into this app yet. No demo users are shown in production mode.', 'users')}</div>`;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
const query = this.gaUserSearch.trim().toLowerCase();
|
|
2243
|
+
const filtered = query
|
|
2244
|
+
? this.gaUsers.filter((userArg) => userArg.name.toLowerCase().includes(query) || userArg.email.toLowerCase().includes(query))
|
|
2245
|
+
: this.gaUsers;
|
|
2246
|
+
const statusVariant = (statusArg: string): 'ok' | 'error' | 'warn' | 'default' =>
|
|
2247
|
+
statusArg === 'active' ? 'ok' : statusArg === 'suspended' ? 'error' : statusArg === 'new' ? 'warn' : 'default';
|
|
2248
|
+
const userRows = filtered.map((userArg) => ({
|
|
2249
|
+
cells: [
|
|
2250
|
+
html`
|
|
2251
|
+
<div class="identity-cell">
|
|
2252
|
+
<idp-avatar name=${userArg.name || userArg.email} size="28"></idp-avatar>
|
|
2253
|
+
<div>
|
|
2254
|
+
<div class="identity-primary">${userArg.name}</div>
|
|
2255
|
+
<div class="identity-secondary">${userArg.email}</div>
|
|
2256
|
+
</div>
|
|
2257
|
+
</div>
|
|
2258
|
+
`,
|
|
2259
|
+
String(userArg.orgCount),
|
|
2260
|
+
html`<idp-badge variant=${statusVariant(userArg.status)}>${userArg.status}</idp-badge>`,
|
|
2261
|
+
userArg.isGlobalAdmin ? html`<idp-badge variant="accent">Admin</idp-badge>` : '-',
|
|
2262
|
+
this.formatTimeAgo(userArg.createdAt),
|
|
2263
|
+
html`<button class="plain-button ghost" style="font-size:11px" @click=${() => this.dispatchShellEvent<IIdpAdminGaUserActionEventDetail>('idp-admin-ga-user-action', { userId: userArg.id, action: userArg.status === 'suspended' ? 'unsuspend' : 'suspend' })}>${userArg.status === 'suspended' ? 'Unsuspend' : 'Suspend'}</button>`,
|
|
2264
|
+
],
|
|
2265
|
+
}));
|
|
2266
|
+
|
|
2267
|
+
return html`
|
|
2268
|
+
${this.renderPageHeader('All Users', `${this.gaUsers.length} users on the platform`)}
|
|
2269
|
+
<div class="body wide-body">
|
|
2270
|
+
<div style="margin-bottom:12px;max-width:360px">
|
|
2271
|
+
<input class="input" placeholder="Search users…" .value=${this.gaUserSearch} @input=${(eventArg: Event) => { this.gaUserSearch = (eventArg.target as HTMLInputElement).value; }} />
|
|
2272
|
+
</div>
|
|
2273
|
+
<idp-data-table
|
|
2274
|
+
title="Users"
|
|
2275
|
+
badge=${`${filtered.length} shown`}
|
|
2276
|
+
empty-title="No matching users"
|
|
2277
|
+
empty-description="No users match the current search."
|
|
2278
|
+
.columns=${[
|
|
2279
|
+
{ label: 'User' },
|
|
2280
|
+
{ label: 'Orgs', mono: true, hideBelow: 'mobile' },
|
|
2281
|
+
{ label: 'Status' },
|
|
2282
|
+
{ label: 'Admin', hideBelow: 'mobile' },
|
|
2283
|
+
{ label: 'Joined', hideBelow: 'tablet' },
|
|
2284
|
+
{ label: '' },
|
|
2285
|
+
]}
|
|
2286
|
+
.rows=${userRows}
|
|
2287
|
+
></idp-data-table>
|
|
2288
|
+
</div>
|
|
2289
|
+
`;
|
|
2699
2290
|
}
|
|
2700
2291
|
|
|
2701
2292
|
private renderGAOrgs(): TemplateResult {
|
|
2293
|
+
const planVariant = (planArg: string): 'accent' | 'ok' | 'warn' | 'default' =>
|
|
2294
|
+
planArg === 'Pro' ? 'accent' : planArg === 'Enterprise' ? 'ok' : planArg === 'Internal' ? 'warn' : 'default';
|
|
2295
|
+
const hasAdminFields = this.orgs.some((orgArg) => orgArg.status || orgArg.plan);
|
|
2702
2296
|
const orgRows = this.orgs.map((orgArg) => ({
|
|
2703
2297
|
cells: [
|
|
2704
2298
|
html`
|
|
@@ -2711,8 +2305,17 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2711
2305
|
</div>
|
|
2712
2306
|
`,
|
|
2713
2307
|
orgArg.slug || '-',
|
|
2714
|
-
|
|
2715
|
-
|
|
2308
|
+
...(hasAdminFields
|
|
2309
|
+
? [
|
|
2310
|
+
orgArg.memberCount !== undefined ? String(orgArg.memberCount) : '-',
|
|
2311
|
+
orgArg.plan ? html`<idp-badge variant=${planVariant(orgArg.plan)}>${orgArg.plan}</idp-badge>` : '-',
|
|
2312
|
+
html`<idp-badge variant=${orgArg.status === 'suspended' ? 'error' : 'ok'}>${orgArg.status || 'active'}</idp-badge>`,
|
|
2313
|
+
html`<button class="plain-button ghost" style="font-size:11px" @click=${() => this.dispatchShellEvent<IIdpAdminGaOrgActionEventDetail>('idp-admin-ga-org-action', { orgId: orgArg.id, action: orgArg.status === 'suspended' ? 'unsuspend' : 'suspend' })}>${orgArg.status === 'suspended' ? 'Unsuspend' : 'Suspend'}</button>`,
|
|
2314
|
+
]
|
|
2315
|
+
: [
|
|
2316
|
+
html`<idp-badge variant=${this.roleVariant(orgArg.myRole || 'member')}>${orgArg.myRole || 'member'}</idp-badge>`,
|
|
2317
|
+
orgArg.id,
|
|
2318
|
+
]),
|
|
2716
2319
|
],
|
|
2717
2320
|
}));
|
|
2718
2321
|
|
|
@@ -2724,12 +2327,21 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2724
2327
|
badge=${`${this.orgs.length} total`}
|
|
2725
2328
|
empty-title="No organisations"
|
|
2726
2329
|
empty-description="No organisations are visible to this admin session."
|
|
2727
|
-
.columns=${
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2330
|
+
.columns=${hasAdminFields
|
|
2331
|
+
? [
|
|
2332
|
+
{ label: 'Organisation' },
|
|
2333
|
+
{ label: 'Slug', mono: true, hideBelow: 'mobile' },
|
|
2334
|
+
{ label: 'Members', mono: true, hideBelow: 'mobile' },
|
|
2335
|
+
{ label: 'Plan' },
|
|
2336
|
+
{ label: 'Status' },
|
|
2337
|
+
{ label: '' },
|
|
2338
|
+
]
|
|
2339
|
+
: [
|
|
2340
|
+
{ label: 'Organisation' },
|
|
2341
|
+
{ label: 'Slug', mono: true, hideBelow: 'mobile' },
|
|
2342
|
+
{ label: 'Role' },
|
|
2343
|
+
{ label: 'Identifier', mono: true, hideBelow: 'tablet' },
|
|
2344
|
+
]}
|
|
2733
2345
|
.rows=${orgRows}
|
|
2734
2346
|
></idp-data-table>
|
|
2735
2347
|
</div>
|
|
@@ -2752,7 +2364,15 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2752
2364
|
html`<idp-badge variant=${appArg.type === 'global' ? 'accent' : 'outline'}>${appArg.type || 'global'}</idp-badge>`,
|
|
2753
2365
|
appArg.category || '-',
|
|
2754
2366
|
appArg.connectionCount ?? '-',
|
|
2755
|
-
html`<idp-badge variant=${appArg.status === 'suspended' ? 'error' : appArg.status === 'pending_review' ? 'warn' : 'ok'}>${appArg.status || 'active'}</idp-badge>`,
|
|
2367
|
+
html`<idp-badge variant=${appArg.status === 'suspended' || appArg.status === 'rejected' ? 'error' : appArg.status === 'pending_review' ? 'warn' : 'ok'}>${(appArg.status || 'active').replace(/_/g, ' ')}</idp-badge>`,
|
|
2368
|
+
appArg.status === 'pending_review'
|
|
2369
|
+
? html`
|
|
2370
|
+
<div style="display:flex;gap:4px">
|
|
2371
|
+
<button class="plain-button outline" style="font-size:11px;color:var(--idp-ok);border-color:var(--idp-ok-border)" @click=${() => this.dispatchShellEvent<IIdpAdminGaAppActionEventDetail>('idp-admin-ga-app-action', { appId: appArg.id, action: 'approve' })}>Approve</button>
|
|
2372
|
+
<button class="plain-button ghost" style="font-size:11px;color:var(--idp-destructive)" @click=${() => this.dispatchShellEvent<IIdpAdminGaAppActionEventDetail>('idp-admin-ga-app-action', { appId: appArg.id, action: 'reject' })}>Reject</button>
|
|
2373
|
+
</div>
|
|
2374
|
+
`
|
|
2375
|
+
: html`<button class="plain-button ghost" style="font-size:11px" @click=${() => this.dispatchShellEvent<IIdpAdminGaAppActionEventDetail>('idp-admin-ga-app-action', { appId: appArg.id, action: appArg.status === 'suspended' ? 'reinstate' : 'suspend' })}>${appArg.status === 'suspended' ? 'Reinstate' : 'Suspend'}</button>`,
|
|
2756
2376
|
],
|
|
2757
2377
|
}));
|
|
2758
2378
|
|
|
@@ -2770,6 +2390,7 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2770
2390
|
{ label: 'Category', hideBelow: 'mobile' },
|
|
2771
2391
|
{ label: 'Connections', align: 'right', mono: true, hideBelow: 'mobile' },
|
|
2772
2392
|
{ label: 'Status' },
|
|
2393
|
+
{ label: '' },
|
|
2773
2394
|
]}
|
|
2774
2395
|
.rows=${appRows}
|
|
2775
2396
|
></idp-data-table>
|
|
@@ -2798,11 +2419,33 @@ export class IdpAdminShell extends DeesElement {
|
|
|
2798
2419
|
|
|
2799
2420
|
public render(): TemplateResult {
|
|
2800
2421
|
return html`
|
|
2801
|
-
<div
|
|
2802
|
-
|
|
2422
|
+
<div
|
|
2423
|
+
class="shell"
|
|
2424
|
+
@idp-admin-navigate=${this.handleSidebarNavigate}
|
|
2425
|
+
@idp-admin-org-select=${this.handleSidebarOrgSelect}
|
|
2426
|
+
@idp-admin-org-create-request=${this.requestOrgCreate}
|
|
2427
|
+
@idp-admin-logout-request=${this.requestLogout}
|
|
2428
|
+
>
|
|
2429
|
+
<idp-admin-sidebar
|
|
2430
|
+
.page=${this.page}
|
|
2431
|
+
.user=${this.user}
|
|
2432
|
+
.accounts=${this.accounts}
|
|
2433
|
+
.orgs=${this.orgs}
|
|
2434
|
+
.selectedOrgId=${this.selectedOrgId}
|
|
2435
|
+
.globalAdmin=${this.globalAdmin}
|
|
2436
|
+
.sessionCount=${this.sessions.length}
|
|
2437
|
+
></idp-admin-sidebar>
|
|
2803
2438
|
<main>${this.renderMainContent()}</main>
|
|
2804
2439
|
</div>
|
|
2805
2440
|
${this.renderDialog()}
|
|
2441
|
+
${this.toast
|
|
2442
|
+
? html`
|
|
2443
|
+
<div class="toast ${this.toast.tone}">
|
|
2444
|
+
<idp-icon name=${this.toast.tone === 'ok' ? 'check' : 'alert'} size="14"></idp-icon>
|
|
2445
|
+
<span style="flex:1">${this.toast.message}</span>
|
|
2446
|
+
</div>
|
|
2447
|
+
`
|
|
2448
|
+
: html``}
|
|
2806
2449
|
`;
|
|
2807
2450
|
}
|
|
2808
2451
|
}
|