@idp.global/catalog 2.1.1 → 3.0.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 +19 -0
- package/dist_bundle/bundle.js +2291 -1744
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/idp-admin-shell.d.ts +13 -1
- package/dist_ts_web/elements/idp-admin-shell.js +55 -2
- package/dist_ts_web/elements/idp-admin-sidebar.js +2 -1
- package/dist_ts_web/elements/idp-button.d.ts +6 -0
- package/dist_ts_web/elements/idp-button.js +48 -10
- package/dist_ts_web/elements/idp-landing-hero.d.ts +2 -0
- package/dist_ts_web/elements/idp-landing-hero.js +24 -4
- package/dist_ts_web/elements/idp-landing-page.d.ts +14 -0
- package/dist_ts_web/elements/idp-landing-page.js +80 -20
- package/dist_ts_web/elements/idp-scim-token-panel.d.ts +43 -0
- package/dist_ts_web/elements/idp-scim-token-panel.js +361 -0
- package/dist_ts_web/elements/idp-sso-connection-form.d.ts +81 -0
- package/dist_ts_web/elements/idp-sso-connection-form.js +637 -0
- package/dist_ts_web/elements/index.d.ts +2 -0
- package/dist_ts_web/elements/index.js +3 -1
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/idp-admin-shell.ts +37 -0
- package/ts_web/elements/idp-admin-sidebar.ts +1 -0
- package/ts_web/elements/idp-button.ts +36 -8
- package/ts_web/elements/idp-landing-hero.ts +9 -3
- package/ts_web/elements/idp-landing-page.ts +83 -19
- package/ts_web/elements/idp-scim-token-panel.ts +311 -0
- package/ts_web/elements/idp-sso-connection-form.ts +574 -0
- package/ts_web/elements/index.ts +2 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { DeesElement, html, property, state, customElement, css, type TemplateResult } from '@design.estate/dees-element';
|
|
2
|
+
import { idpElementStyles } from './tokens.js';
|
|
3
|
+
import './idp-badge.js';
|
|
4
|
+
import './idp-button.js';
|
|
5
|
+
import './idp-icon.js';
|
|
6
|
+
import './idp-input.js';
|
|
7
|
+
|
|
8
|
+
export interface IIdpScimTokenItem {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
tokenPrefix: string;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
expiresAt?: number;
|
|
14
|
+
lastUsedAt?: number;
|
|
15
|
+
revoked: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IIdpScimTokenCreateEventDetail {
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IIdpScimTokenRevokeEventDetail {
|
|
23
|
+
tokenId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'idp-scim-token-panel': IdpScimTokenPanel;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@customElement('idp-scim-token-panel')
|
|
33
|
+
export class IdpScimTokenPanel extends DeesElement {
|
|
34
|
+
public static demo = () => html`
|
|
35
|
+
<div style="max-width: 720px;">
|
|
36
|
+
<idp-scim-token-panel
|
|
37
|
+
scimBaseUrl="https://idp.global/scim/v2"
|
|
38
|
+
.tokens=${[
|
|
39
|
+
{ id: 't1', label: 'Okta production', tokenPrefix: 'idp_scim', createdAt: Date.now() - 86400000 * 30, lastUsedAt: Date.now() - 3600000, revoked: false },
|
|
40
|
+
{ id: 't2', label: 'Entra staging', tokenPrefix: 'idp_scim', createdAt: Date.now() - 86400000 * 90, expiresAt: Date.now() + 86400000 * 30, revoked: true },
|
|
41
|
+
] as IIdpScimTokenItem[]}
|
|
42
|
+
.createdToken=${{ label: 'Okta production', plaintextToken: 'idp_scim_2f8Qb1x0demoTokenValueOnlyShownOnce' }}
|
|
43
|
+
></idp-scim-token-panel>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
public static demoGroups = ['idp.global v5 admin console'];
|
|
47
|
+
|
|
48
|
+
@property({ type: Array })
|
|
49
|
+
public accessor tokens: IIdpScimTokenItem[] = [];
|
|
50
|
+
|
|
51
|
+
/** One-time reveal payload after a createScimToken round trip */
|
|
52
|
+
@property({ type: Object })
|
|
53
|
+
public accessor createdToken: { label: string; plaintextToken: string } | null = null;
|
|
54
|
+
|
|
55
|
+
@property({ type: String })
|
|
56
|
+
public accessor scimBaseUrl = '';
|
|
57
|
+
|
|
58
|
+
@state()
|
|
59
|
+
private accessor newLabel = '';
|
|
60
|
+
|
|
61
|
+
@state()
|
|
62
|
+
private accessor copied = false;
|
|
63
|
+
|
|
64
|
+
public static styles = [
|
|
65
|
+
...idpElementStyles,
|
|
66
|
+
css`
|
|
67
|
+
:host {
|
|
68
|
+
display: block;
|
|
69
|
+
font-family: var(--idp-font);
|
|
70
|
+
color: var(--idp-fg);
|
|
71
|
+
}
|
|
72
|
+
.section {
|
|
73
|
+
background: var(--idp-card);
|
|
74
|
+
border: 1px solid var(--idp-border);
|
|
75
|
+
border-radius: var(--idp-radius);
|
|
76
|
+
padding: 20px;
|
|
77
|
+
}
|
|
78
|
+
.title {
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
}
|
|
82
|
+
.muted {
|
|
83
|
+
font-size: 12.5px;
|
|
84
|
+
color: var(--idp-muted-fg);
|
|
85
|
+
line-height: 1.5;
|
|
86
|
+
margin-top: 3px;
|
|
87
|
+
}
|
|
88
|
+
.endpoint {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
margin-top: 12px;
|
|
93
|
+
padding: 8px 10px;
|
|
94
|
+
background: var(--idp-muted);
|
|
95
|
+
border: 1px solid var(--idp-border);
|
|
96
|
+
border-radius: 8px;
|
|
97
|
+
font-family: var(--idp-mono, ui-monospace, monospace);
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
overflow-wrap: anywhere;
|
|
100
|
+
}
|
|
101
|
+
.reveal {
|
|
102
|
+
margin-top: 14px;
|
|
103
|
+
padding: 12px;
|
|
104
|
+
border: 1px solid var(--idp-info-border, var(--idp-border));
|
|
105
|
+
border-radius: 10px;
|
|
106
|
+
background: var(--idp-accent-soft);
|
|
107
|
+
}
|
|
108
|
+
.reveal-title {
|
|
109
|
+
font-size: 12.5px;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
color: var(--idp-accent);
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: 6px;
|
|
115
|
+
}
|
|
116
|
+
.reveal-token {
|
|
117
|
+
margin-top: 8px;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 8px;
|
|
121
|
+
font-family: var(--idp-mono, ui-monospace, monospace);
|
|
122
|
+
font-size: 12.5px;
|
|
123
|
+
overflow-wrap: anywhere;
|
|
124
|
+
}
|
|
125
|
+
.copy-button {
|
|
126
|
+
flex: 0 0 auto;
|
|
127
|
+
border: 1px solid var(--idp-border);
|
|
128
|
+
background: var(--idp-card);
|
|
129
|
+
border-radius: 7px;
|
|
130
|
+
font-family: inherit;
|
|
131
|
+
font-size: 11px;
|
|
132
|
+
padding: 4px 10px;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
}
|
|
135
|
+
.copy-button:hover {
|
|
136
|
+
background: var(--idp-muted);
|
|
137
|
+
}
|
|
138
|
+
.row {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: 12px;
|
|
142
|
+
padding: 12px 0;
|
|
143
|
+
border-bottom: 1px solid var(--idp-border);
|
|
144
|
+
}
|
|
145
|
+
.row:last-of-type {
|
|
146
|
+
border-bottom: none;
|
|
147
|
+
}
|
|
148
|
+
.row-main {
|
|
149
|
+
flex: 1;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
}
|
|
152
|
+
.row-name {
|
|
153
|
+
font-size: 13px;
|
|
154
|
+
font-weight: 500;
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 8px;
|
|
158
|
+
}
|
|
159
|
+
.row-meta {
|
|
160
|
+
font-size: 11px;
|
|
161
|
+
color: var(--idp-muted-fg);
|
|
162
|
+
margin-top: 2px;
|
|
163
|
+
}
|
|
164
|
+
.revoke {
|
|
165
|
+
background: none;
|
|
166
|
+
border: none;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
font-family: inherit;
|
|
169
|
+
font-size: 11px;
|
|
170
|
+
font-weight: 500;
|
|
171
|
+
color: var(--idp-destructive);
|
|
172
|
+
padding: 4px 8px;
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
}
|
|
175
|
+
.revoke:hover {
|
|
176
|
+
background: var(--idp-error-bg);
|
|
177
|
+
}
|
|
178
|
+
.create-row {
|
|
179
|
+
display: flex;
|
|
180
|
+
gap: 10px;
|
|
181
|
+
align-items: flex-end;
|
|
182
|
+
margin-top: 16px;
|
|
183
|
+
padding-top: 16px;
|
|
184
|
+
border-top: 1px solid var(--idp-border);
|
|
185
|
+
}
|
|
186
|
+
.create-row idp-input {
|
|
187
|
+
flex: 1;
|
|
188
|
+
}
|
|
189
|
+
.empty {
|
|
190
|
+
padding: 18px 0;
|
|
191
|
+
font-size: 12.5px;
|
|
192
|
+
color: var(--idp-muted-fg);
|
|
193
|
+
}
|
|
194
|
+
`,
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
private formatTimeAgo(timestampArg: number): string {
|
|
198
|
+
const deltaMs = Date.now() - timestampArg;
|
|
199
|
+
const days = Math.floor(deltaMs / 86400000);
|
|
200
|
+
if (days > 0) return `${days}d ago`;
|
|
201
|
+
const hours = Math.floor(deltaMs / 3600000);
|
|
202
|
+
if (hours > 0) return `${hours}h ago`;
|
|
203
|
+
return 'just now';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async copyToken() {
|
|
207
|
+
if (!this.createdToken) return;
|
|
208
|
+
try {
|
|
209
|
+
await navigator.clipboard.writeText(this.createdToken.plaintextToken);
|
|
210
|
+
this.copied = true;
|
|
211
|
+
setTimeout(() => (this.copied = false), 2000);
|
|
212
|
+
} catch {
|
|
213
|
+
// clipboard unavailable; the token stays selectable
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private submitCreate() {
|
|
218
|
+
const label = this.newLabel.trim();
|
|
219
|
+
if (!label) return;
|
|
220
|
+
this.dispatchEvent(
|
|
221
|
+
new CustomEvent<IIdpScimTokenCreateEventDetail>('idp-scim-token-create', {
|
|
222
|
+
detail: { label },
|
|
223
|
+
bubbles: true,
|
|
224
|
+
composed: true,
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
this.newLabel = '';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public render(): TemplateResult {
|
|
231
|
+
return html`
|
|
232
|
+
<div class="section">
|
|
233
|
+
<div class="title">SCIM provisioning</div>
|
|
234
|
+
<div class="muted">
|
|
235
|
+
Let your identity provider create, update and deactivate accounts automatically.
|
|
236
|
+
Configure the endpoint below with a bearer token in Okta or Microsoft Entra.
|
|
237
|
+
</div>
|
|
238
|
+
${this.scimBaseUrl
|
|
239
|
+
? html`<div class="endpoint"><idp-icon name="globe" size="14"></idp-icon>${this.scimBaseUrl}</div>`
|
|
240
|
+
: html``}
|
|
241
|
+
${this.createdToken
|
|
242
|
+
? html`
|
|
243
|
+
<div class="reveal">
|
|
244
|
+
<div class="reveal-title">
|
|
245
|
+
<idp-icon name="key" size="14"></idp-icon>
|
|
246
|
+
Token for "${this.createdToken.label}" — copy it now, it won't be shown again
|
|
247
|
+
</div>
|
|
248
|
+
<div class="reveal-token">
|
|
249
|
+
<span>${this.createdToken.plaintextToken}</span>
|
|
250
|
+
<button class="copy-button" @click=${() => this.copyToken()}>
|
|
251
|
+
${this.copied ? 'Copied' : 'Copy'}
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
`
|
|
256
|
+
: html``}
|
|
257
|
+
${this.tokens.length
|
|
258
|
+
? this.tokens.map(
|
|
259
|
+
(tokenArg) => html`
|
|
260
|
+
<div class="row">
|
|
261
|
+
<div class="row-main">
|
|
262
|
+
<div class="row-name">
|
|
263
|
+
${tokenArg.label}
|
|
264
|
+
${tokenArg.revoked
|
|
265
|
+
? html`<idp-badge variant="error">revoked</idp-badge>`
|
|
266
|
+
: tokenArg.expiresAt && tokenArg.expiresAt < Date.now()
|
|
267
|
+
? html`<idp-badge variant="warn">expired</idp-badge>`
|
|
268
|
+
: html`<idp-badge variant="ok">active</idp-badge>`}
|
|
269
|
+
</div>
|
|
270
|
+
<div class="row-meta">
|
|
271
|
+
${tokenArg.tokenPrefix}… · created ${this.formatTimeAgo(tokenArg.createdAt)}
|
|
272
|
+
${tokenArg.lastUsedAt ? ` · last used ${this.formatTimeAgo(tokenArg.lastUsedAt)}` : ' · never used'}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
${!tokenArg.revoked
|
|
276
|
+
? html`
|
|
277
|
+
<button
|
|
278
|
+
class="revoke"
|
|
279
|
+
@click=${() =>
|
|
280
|
+
this.dispatchEvent(
|
|
281
|
+
new CustomEvent<IIdpScimTokenRevokeEventDetail>('idp-scim-token-revoke', {
|
|
282
|
+
detail: { tokenId: tokenArg.id },
|
|
283
|
+
bubbles: true,
|
|
284
|
+
composed: true,
|
|
285
|
+
}),
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
Revoke
|
|
289
|
+
</button>
|
|
290
|
+
`
|
|
291
|
+
: html``}
|
|
292
|
+
</div>
|
|
293
|
+
`,
|
|
294
|
+
)
|
|
295
|
+
: html`<div class="empty">No SCIM tokens yet. Create one to connect your IdP's provisioning.</div>`}
|
|
296
|
+
<div class="create-row">
|
|
297
|
+
<idp-input
|
|
298
|
+
label="New token label"
|
|
299
|
+
placeholder="e.g. Okta production"
|
|
300
|
+
.value=${this.newLabel}
|
|
301
|
+
@input=${(eventArg: Event) => {
|
|
302
|
+
const target = eventArg.composedPath()[0] as HTMLInputElement;
|
|
303
|
+
this.newLabel = target?.value ?? '';
|
|
304
|
+
}}
|
|
305
|
+
></idp-input>
|
|
306
|
+
<idp-button variant="accent" size="sm" @click=${() => this.submitCreate()}>Create token</idp-button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
}
|