@datalayer/core 1.1.49 → 1.1.56
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/README.md +19 -42
- package/lib/client/auth/AuthenticationManager.js +6 -4
- package/lib/client/auth/strategies.js +2 -0
- package/lib/components/animation/AnimatedText.d.ts +98 -0
- package/lib/components/animation/AnimatedText.js +220 -0
- package/lib/components/animation/index.d.ts +1 -0
- package/lib/components/{tokens → animation}/index.js +1 -1
- package/lib/components/auth/{LoginToken.d.ts → SigInAPIKey.d.ts} +3 -3
- package/lib/components/auth/{LoginToken.js → SigInAPIKey.js} +5 -5
- package/lib/components/auth/{Login.d.ts → SignIn.d.ts} +3 -3
- package/lib/components/auth/{Login.js → SignIn.js} +13 -13
- package/lib/components/auth/{Login.stories.d.ts → SignIn.stories.d.ts} +2 -2
- package/lib/components/auth/{Login.stories.js → SignIn.stories.js} +9 -9
- package/lib/components/auth/index.d.ts +2 -2
- package/lib/components/auth/index.js +2 -2
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +19 -6
- package/lib/components/avatars/UserProfileAvatar.js +2 -6
- package/lib/components/billing/BillingEntitySelect.js +10 -2
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +107 -0
- package/lib/components/collaboration/index.d.ts +1 -0
- package/lib/components/collaboration/index.js +9 -0
- package/lib/components/display/DatalayerBox.d.ts +3 -1
- package/lib/components/display/DatalayerBox.js +7 -7
- package/lib/components/display/JupyterDialog.d.ts +2 -0
- package/lib/components/display/JupyterDialog.js +10 -4
- package/lib/components/iam/ExternalTokenSilentLogin.js +8 -4
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/labels/StatusLabels.d.ts +42 -0
- package/lib/components/labels/StatusLabels.js +61 -0
- package/lib/components/labels/index.d.ts +1 -0
- package/lib/components/labels/index.js +1 -0
- package/lib/components/principal/Principal.d.ts +2 -0
- package/lib/components/principal/Principal.js +11 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +136 -0
- package/lib/components/principal/PrincipalAppearance.js +203 -0
- package/lib/components/principal/PrincipalAvatar.d.ts +5 -2
- package/lib/components/principal/PrincipalAvatar.js +16 -3
- package/lib/components/principal/PrincipalBadge.js +7 -0
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +2 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/principal/index.d.ts +1 -0
- package/lib/components/principal/index.js +1 -0
- package/lib/components/progress/ConsumptionBar.d.ts +9 -0
- package/lib/components/progress/ConsumptionBar.js +10 -2
- package/lib/components/sharing/ShareAccessComponent.js +6 -1
- package/lib/config/Configuration.d.ts +17 -8
- package/lib/config/Configuration.js +12 -5
- package/lib/hooks/useCache.d.ts +2 -0
- package/lib/hooks/useCache.js +35 -11
- package/lib/hooks/usePrincipalCacheStore.d.ts +2 -0
- package/lib/hooks/usePrincipalCacheStore.js +2 -0
- package/lib/models/IAM.d.ts +10 -0
- package/lib/models/Organization.d.ts +2 -0
- package/lib/models/Organization.js +2 -0
- package/lib/models/Profile.d.ts +2 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +4 -0
- package/lib/models/UserDTO.d.ts +6 -0
- package/lib/models/UserDTO.js +8 -0
- package/lib/state/substates/CoreState.js +5 -4
- package/lib/state/substates/IAMState.js +6 -0
- package/lib/state/substates/LayoutState.js +62 -7
- package/lib/state/substates/OrganizationState.d.ts +6 -0
- package/lib/state/substates/OrganizationState.js +5 -0
- package/lib/state/substates/ProfileState.d.ts +34 -0
- package/lib/state/substates/ProfileState.js +68 -0
- package/lib/state/substates/TeamState.d.ts +6 -0
- package/lib/state/substates/TeamState.js +3 -0
- package/lib/state/substates/index.d.ts +1 -0
- package/lib/state/substates/index.js +1 -0
- package/lib/utils/Jwt.d.ts +0 -6
- package/lib/utils/Jwt.js +8 -1
- package/lib/views/iam/SignInSimple.d.ts +1 -1
- package/lib/views/iam/SignInSimple.js +131 -97
- package/lib/views/profile/UserBadge.js +6 -3
- package/package.json +3 -3
- package/lib/components/tokens/index.d.ts +0 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* ProfileState — the one place the authenticated user's profile lives.
|
|
7
|
+
*
|
|
8
|
+
* The avatar and the banner used to be read from whatever carried them at
|
|
9
|
+
* each call site — the IAM user, a whoami response, a form snapshot — and
|
|
10
|
+
* the surfaces drifted: a banner updated on the profile page did not reach
|
|
11
|
+
* the user menu until the page reloaded. Every surface showing the profile
|
|
12
|
+
* (user menu, sidebars, profile views) reads this store instead, and every
|
|
13
|
+
* flow that learns something about the profile (login, `GET /me`, a profile
|
|
14
|
+
* update) writes it here.
|
|
15
|
+
*
|
|
16
|
+
* The store is fed automatically by {@link IAMState} on login, profile
|
|
17
|
+
* update and logout, and by the `useMe` query with the full profile — the
|
|
18
|
+
* consumers only ever read.
|
|
19
|
+
*
|
|
20
|
+
* @module state/substates/ProfileState
|
|
21
|
+
*/
|
|
22
|
+
import { useStore } from 'zustand';
|
|
23
|
+
import { createStore } from 'zustand/vanilla';
|
|
24
|
+
const userToProfile = (user) => user
|
|
25
|
+
? {
|
|
26
|
+
id: user.id,
|
|
27
|
+
uid: user.uid,
|
|
28
|
+
handle: user.handle,
|
|
29
|
+
email: user.email,
|
|
30
|
+
firstName: user.firstName,
|
|
31
|
+
lastName: user.lastName,
|
|
32
|
+
displayName: user.displayName,
|
|
33
|
+
initials: user.initials,
|
|
34
|
+
avatarUrl: user.avatarUrl,
|
|
35
|
+
avatarIcon: user.avatarIcon,
|
|
36
|
+
banner: user.banner,
|
|
37
|
+
origin: user.origin,
|
|
38
|
+
roles: user.roles,
|
|
39
|
+
}
|
|
40
|
+
: undefined;
|
|
41
|
+
export const profileStore = createStore(set => ({
|
|
42
|
+
profile: undefined,
|
|
43
|
+
setProfile: profile => set({ profile }),
|
|
44
|
+
updateProfile: partial => set(state => ({ profile: { ...state.profile, ...partial } })),
|
|
45
|
+
setProfileFromUser: user => set(state => {
|
|
46
|
+
const profile = userToProfile(user);
|
|
47
|
+
if (!profile) {
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
// A login refresh may know less than a profile update that already
|
|
51
|
+
// landed (the anonymous whoami has no banner): merge, newest wins,
|
|
52
|
+
// but never erase a field with undefined.
|
|
53
|
+
const merged = { ...state.profile };
|
|
54
|
+
for (const [key, value] of Object.entries(profile)) {
|
|
55
|
+
if (value !== undefined) {
|
|
56
|
+
merged[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { profile: merged };
|
|
60
|
+
}),
|
|
61
|
+
clearProfile: () => set({ profile: undefined }),
|
|
62
|
+
}));
|
|
63
|
+
export function useProfileStore(selector) {
|
|
64
|
+
return useStore(profileStore, selector);
|
|
65
|
+
}
|
|
66
|
+
/** The profile, for surfaces that show it. */
|
|
67
|
+
export const useProfile = () => useProfileStore(state => state.profile);
|
|
68
|
+
export default useProfileStore;
|
|
@@ -4,6 +4,12 @@ export type ITeamState = {
|
|
|
4
4
|
};
|
|
5
5
|
export type TeamState = ITeamState & {
|
|
6
6
|
update: (teams: IBaseTeam[]) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Merge what just changed into the team of the list — the way a settings
|
|
9
|
+
* save keeps every surface reading this store current (avatar, banner,
|
|
10
|
+
* name…) without refetching the list.
|
|
11
|
+
*/
|
|
12
|
+
updateTeam: (team: Partial<IBaseTeam> & Pick<IBaseTeam, 'id'>) => void;
|
|
7
13
|
};
|
|
8
14
|
export declare const teamStore: import("zustand").StoreApi<TeamState>;
|
|
9
15
|
export declare function useTeamStore(): TeamState;
|
|
@@ -10,6 +10,9 @@ export const teamStore = createStore((set, get) => ({
|
|
|
10
10
|
update: (teams) => set((state) => ({
|
|
11
11
|
teams,
|
|
12
12
|
})),
|
|
13
|
+
updateTeam: team => set((state) => ({
|
|
14
|
+
teams: state.teams.map(known => known.id === team.id ? { ...known, ...team } : known),
|
|
15
|
+
})),
|
|
13
16
|
}));
|
|
14
17
|
export function useTeamStore(selector) {
|
|
15
18
|
return useStore(teamStore, selector);
|
|
@@ -6,6 +6,7 @@ export * from './CoreState.js';
|
|
|
6
6
|
export * from './DatasourceState.js';
|
|
7
7
|
export * from './IAMState.js';
|
|
8
8
|
export * from './LayoutState.js';
|
|
9
|
+
export * from './ProfileState.js';
|
|
9
10
|
export * from './OrganizationState.js';
|
|
10
11
|
export * from './SurveysState.js';
|
|
11
12
|
export * from './TeamState.js';
|
package/lib/utils/Jwt.d.ts
CHANGED
package/lib/utils/Jwt.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight JWT payload utilities.
|
|
7
|
+
*
|
|
8
|
+
* Decodes a JWT without verification (client-side display only).
|
|
9
|
+
* Never use for security-critical checks.
|
|
10
|
+
*/
|
|
11
|
+
import { asDisplayName } from './Name.js';
|
|
5
12
|
// ── Utilities ─────────────────────────────────────────────────────
|
|
6
13
|
/**
|
|
7
14
|
* Decode the payload of a JWT without verifying the signature.
|
|
@@ -39,6 +46,6 @@ export function getDatalayerJwtUser(token) {
|
|
|
39
46
|
export function getDatalayerDisplayName(user, fallback = '') {
|
|
40
47
|
if (!user)
|
|
41
48
|
return fallback;
|
|
42
|
-
const full =
|
|
49
|
+
const full = asDisplayName(user.firstName ?? '', user.lastName ?? '').trim();
|
|
43
50
|
return full || user.handle || fallback;
|
|
44
51
|
}
|
|
@@ -16,7 +16,7 @@ export interface SignInSimpleProps {
|
|
|
16
16
|
onSignIn?: (token: string, handle: string) => void;
|
|
17
17
|
/**
|
|
18
18
|
* Called when the user authenticates with an API key.
|
|
19
|
-
* If not provided the "Sign
|
|
19
|
+
* If not provided the "Sign in with an API Key" button is hidden.
|
|
20
20
|
*/
|
|
21
21
|
onApiKeySignIn?: (apiKey: string) => void;
|
|
22
22
|
/**
|
|
@@ -14,8 +14,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
14
14
|
*/
|
|
15
15
|
import { useState, useCallback, useRef, useMemo, useEffect, } from 'react';
|
|
16
16
|
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
17
|
+
import { createGlobalStyle } from 'styled-components';
|
|
17
18
|
import { Box, Button, FormControl, Heading, Link, Spinner, Text, Textarea, TextInput, } from '@primer/react';
|
|
18
|
-
import { EyeIcon, EyeClosedIcon, KeyIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
|
+
import { EyeIcon, EyeClosedIcon, KeyIcon, MailIcon, SignInIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
20
|
import { GithubMarkIcon, GoogleIcon, LinkedInGreyIcon, } from '@datalayer/icons-react';
|
|
20
21
|
import { useToast } from '../../hooks/index.js';
|
|
21
22
|
import { isInsideJupyterLab } from '../../utils/Jupyter.js';
|
|
@@ -67,10 +68,43 @@ const getErrorMessage = (error, fallback = 'Unable to start social sign-in.') =>
|
|
|
67
68
|
}
|
|
68
69
|
return fallback;
|
|
69
70
|
};
|
|
71
|
+
// Repaint the browser autofill highlight (Chrome's blue/yellow) with theme
|
|
72
|
+
// tokens so autofilled/disabled inputs keep the Primer surface colors. A
|
|
73
|
+
// scoped global style is used because Primer's TextInput inner <input> is not
|
|
74
|
+
// reliably reachable via the wrapper `sx` for vendor-prefixed autofill pseudos.
|
|
75
|
+
const SignInInputGlobalStyle = createGlobalStyle `
|
|
76
|
+
.signin-input-theme-scope input:-webkit-autofill,
|
|
77
|
+
.signin-input-theme-scope input:-webkit-autofill:hover,
|
|
78
|
+
.signin-input-theme-scope input:-webkit-autofill:focus,
|
|
79
|
+
.signin-input-theme-scope input:-webkit-autofill:active,
|
|
80
|
+
.signin-input-theme-scope input:-webkit-autofill:disabled,
|
|
81
|
+
.signin-input-theme-scope textarea:-webkit-autofill,
|
|
82
|
+
.signin-input-theme-scope textarea:-webkit-autofill:hover,
|
|
83
|
+
.signin-input-theme-scope textarea:-webkit-autofill:focus,
|
|
84
|
+
.signin-input-theme-scope textarea:-webkit-autofill:active,
|
|
85
|
+
.signin-input-theme-scope textarea:-webkit-autofill:disabled {
|
|
86
|
+
-webkit-text-fill-color: var(--fgColor-default) !important;
|
|
87
|
+
caret-color: var(--fgColor-default) !important;
|
|
88
|
+
-webkit-box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
89
|
+
box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
90
|
+
background-color: var(--bgColor-default) !important;
|
|
91
|
+
transition: background-color 9999s ease-in-out 0s !important;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.signin-input-theme-scope input:disabled,
|
|
95
|
+
.signin-input-theme-scope textarea:disabled {
|
|
96
|
+
-webkit-text-fill-color: var(--fgColor-muted) !important;
|
|
97
|
+
-webkit-box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
98
|
+
box-shadow: 0 0 0 1000px var(--bgColor-default) inset !important;
|
|
99
|
+
background-color: var(--bgColor-default) !important;
|
|
100
|
+
opacity: 1 !important;
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
70
103
|
// ── Component ────────────────────────────────────────────────────────
|
|
71
104
|
export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp, name, title = 'Datalayer OTEL', description = 'Sign in to access the observability dashboard.', icon, leadingIcon = _jsx(TelescopeIcon, { size: 24 }), github = false, google = false, linkedin = false, apiKey = false, signUp = true, socialSignInNavigationTarget, asDoc = false, hideHero = false, calloutTitle, calloutDescription, passwordToggleLabel = 'Sign in with a password', signUpTitle = "Don't have an account?", signUpDescription = 'Create a free Datalayer account with your email address.', signUpLabel = 'Sign up with email', signUpHref = 'https://datalayer.ai/signup', forgotPasswordHref = '/password', forgotPasswordLabel = 'Forgot password?', showForgotPassword = true, socialButtonMarginTop = 3, actionButtonMaxWidth = 320, }) => {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
105
|
+
const compactDocMode = asDoc && !hideHero;
|
|
106
|
+
const headingText = name ?? (asDoc && title === 'Datalayer OTEL' ? 'Datalayer Sign In' : title);
|
|
107
|
+
const headingIcon = icon ?? (asDoc ? _jsx(SignInIcon, { size: 24 }) : leadingIcon);
|
|
74
108
|
const loginUrl = useMemo(() => {
|
|
75
109
|
if (loginUrlProp)
|
|
76
110
|
return loginUrlProp;
|
|
@@ -280,104 +314,104 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
|
|
|
280
314
|
}
|
|
281
315
|
}, [mfaUserUid, submit, submitMfa]);
|
|
282
316
|
const socialDisabled = socialLoading || loading || asDoc;
|
|
283
|
-
return (
|
|
317
|
+
return (_jsxs(Box, { className: "signin-input-theme-scope", sx: {
|
|
284
318
|
display: 'flex',
|
|
285
|
-
alignItems: hideHero ? 'flex-start' : 'center',
|
|
319
|
+
alignItems: hideHero || compactDocMode ? 'flex-start' : 'center',
|
|
286
320
|
justifyContent: 'center',
|
|
287
|
-
minHeight: hideHero ? 'auto' : '100vh',
|
|
321
|
+
minHeight: hideHero || compactDocMode ? 'auto' : '100vh',
|
|
288
322
|
bg: hideHero ? 'transparent' : 'canvas.default',
|
|
289
323
|
color: 'fg.default',
|
|
290
|
-
py: hideHero ? 0 : 4,
|
|
291
|
-
}, children: _jsxs(Box, { sx: {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
p: 3,
|
|
360
|
-
bg: 'canvas.backdrop',
|
|
361
|
-
display: 'flex',
|
|
362
|
-
alignItems: 'center',
|
|
363
|
-
justifyContent: 'center',
|
|
364
|
-
}, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
365
|
-
event.preventDefault();
|
|
366
|
-
handleApiKeyAuthenticate();
|
|
367
|
-
}, sx: {
|
|
368
|
-
width: 'min(560px, 100%)',
|
|
324
|
+
py: hideHero ? 0 : compactDocMode ? 2 : 4,
|
|
325
|
+
}, children: [_jsx(SignInInputGlobalStyle, {}), _jsxs(Box, { sx: {
|
|
326
|
+
width: '100%',
|
|
327
|
+
maxWidth: 440,
|
|
328
|
+
p: 0,
|
|
329
|
+
}, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
330
|
+
display: 'flex',
|
|
331
|
+
alignItems: 'center',
|
|
332
|
+
gap: 2,
|
|
333
|
+
mb: compactDocMode ? 2 : 3,
|
|
334
|
+
justifyContent: 'center',
|
|
335
|
+
}, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
|
|
336
|
+
fontSize: 1,
|
|
337
|
+
color: 'fg.muted',
|
|
338
|
+
mb: compactDocMode ? 2 : 3,
|
|
339
|
+
textAlign: 'center',
|
|
340
|
+
}, children: description })] })), (calloutTitle || calloutDescription) && (_jsxs(Box, { sx: { mb: 3, textAlign: 'center' }, children: [calloutTitle && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 1 }, children: calloutTitle })), calloutDescription && (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: calloutDescription }))] })), !mfaUserUid && (github || google || linkedin) && (_jsxs(_Fragment, { children: [github && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'github'
|
|
341
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
342
|
+
: GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
|
|
343
|
+
mt: socialButtonMarginTop,
|
|
344
|
+
maxWidth: actionButtonMaxWidth,
|
|
345
|
+
mx: 'auto',
|
|
346
|
+
}, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'google'
|
|
347
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
348
|
+
: GoogleIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: {
|
|
349
|
+
mt: socialButtonMarginTop,
|
|
350
|
+
maxWidth: actionButtonMaxWidth,
|
|
351
|
+
mx: 'auto',
|
|
352
|
+
}, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'linkedin'
|
|
353
|
+
? () => _jsx(Spinner, { size: "small" })
|
|
354
|
+
: LinkedInGreyIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), sx: {
|
|
355
|
+
mt: socialButtonMarginTop,
|
|
356
|
+
maxWidth: actionButtonMaxWidth,
|
|
357
|
+
mx: 'auto',
|
|
358
|
+
}, children: "Sign in with LinkedIn" }))] })), !mfaUserUid && !showPasswordForm && (_jsx(Box, { sx: { mt: 3, display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { leadingVisual: KeyIcon, size: "large", block: true, disabled: asDoc || loading || socialLoading, onClick: () => setShowPasswordForm(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: passwordToggleLabel }) })), !mfaUserUid && showPasswordForm && (_jsxs(Box, { sx: { mt: 4 }, children: [_jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, disabled: loading || asDoc, onChange: e => setHandle(e.target.value), onKeyDown: handleKeyDown })] }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Password" }), _jsx(TextInput, { block: true, placeholder: "Your password", type: showPassword ? 'text' : 'password', value: password, disabled: loading || asDoc, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), disabled: loading || asDoc, icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), _jsxs(Box, { sx: {
|
|
359
|
+
display: 'flex',
|
|
360
|
+
alignItems: 'center',
|
|
361
|
+
justifyContent: 'space-between',
|
|
362
|
+
mt: 3,
|
|
363
|
+
}, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign In' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
|
|
364
|
+
fontSize: 1,
|
|
365
|
+
cursor: asDoc ? 'not-allowed' : 'pointer',
|
|
366
|
+
background: 'none',
|
|
367
|
+
border: 'none',
|
|
368
|
+
opacity: asDoc ? 0.6 : 1,
|
|
369
|
+
}, onClick: () => {
|
|
370
|
+
if (asDoc || loading) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
handleForgotPassword();
|
|
374
|
+
}, children: forgotPasswordLabel }))] })] })), mfaUserUid && (_jsxs(_Fragment, { children: [_jsxs(FormControl, { required: true, sx: { mb: 3, mt: 2 }, children: [_jsx(FormControl.Label, { children: "MFA Code" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your MFA code", value: mfaCode, disabled: loading || asDoc, onChange: e => setMfaCode(e.target.value), onKeyDown: handleKeyDown })] }), _jsx(Button, { variant: "primary", block: true, disabled: loading || asDoc || !mfaCode.trim(), onClick: submitMfa, sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: loading ? 'Validating…' : 'Validate MFA Code' })] })), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mt: 3, display: 'block' }, children: error })), signUp && (_jsxs(Box, { sx: {
|
|
375
|
+
mt: 4,
|
|
376
|
+
pt: 4,
|
|
377
|
+
textAlign: 'center',
|
|
378
|
+
borderTop: '1px solid',
|
|
379
|
+
borderColor: 'border.muted',
|
|
380
|
+
}, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: signUpTitle }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mb: 3 }, children: signUpDescription }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { variant: "primary", size: "large", leadingVisual: MailIcon, disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
|
|
381
|
+
display: 'flex',
|
|
382
|
+
alignItems: 'center',
|
|
383
|
+
gap: 2,
|
|
384
|
+
my: 3,
|
|
385
|
+
}, children: [_jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "or" }), _jsx(Box, { sx: { flex: 1, height: '1px', bg: 'border.default' } })] }), _jsx(Button, { block: true, leadingVisual: KeyIcon, disabled: asDoc || loading || socialLoading, onClick: () => setShowApiKeyDialog(true), sx: { maxWidth: actionButtonMaxWidth, mx: 'auto' }, children: "Sign in with an API Key" }), showApiKeyDialog && (_jsx(Box, { role: "dialog", "aria-modal": "true", "aria-labelledby": "signin-api-key-title", onKeyDown: event => {
|
|
386
|
+
if (event.key === 'Escape') {
|
|
387
|
+
closeApiKeyDialog();
|
|
388
|
+
}
|
|
389
|
+
}, onClick: closeApiKeyDialog, sx: {
|
|
390
|
+
position: 'fixed',
|
|
391
|
+
inset: 0,
|
|
392
|
+
zIndex: 200,
|
|
369
393
|
p: 3,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
394
|
+
bg: 'canvas.backdrop',
|
|
395
|
+
display: 'flex',
|
|
396
|
+
alignItems: 'center',
|
|
397
|
+
justifyContent: 'center',
|
|
398
|
+
}, children: _jsxs(Box, { as: "form", onClick: event => event.stopPropagation(), onSubmit: event => {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
handleApiKeyAuthenticate();
|
|
401
|
+
}, sx: {
|
|
402
|
+
width: 'min(560px, 100%)',
|
|
403
|
+
p: 3,
|
|
404
|
+
borderRadius: 2,
|
|
405
|
+
border: '1px solid',
|
|
406
|
+
borderColor: 'border.default',
|
|
407
|
+
bg: 'canvas.overlay',
|
|
408
|
+
color: 'fg.default',
|
|
409
|
+
boxShadow: 'shadow.large',
|
|
410
|
+
}, children: [_jsx(Heading, { id: "signin-api-key-title", sx: { fontSize: 2, mb: 2 }, children: "Enter your API Key" }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "API Key" }), _jsx(Textarea, { block: true, required: true, autoFocus: true, placeholder: "Paste your API key here", value: apiKeyValue, onInput: (e) => setApiKeyValue(e.target.value), ref: apiKeyRef })] }), _jsxs(Box, { sx: {
|
|
411
|
+
mt: 3,
|
|
412
|
+
display: 'flex',
|
|
413
|
+
justifyContent: 'flex-end',
|
|
414
|
+
gap: 2,
|
|
415
|
+
}, children: [_jsx(Button, { type: "button", onClick: closeApiKeyDialog, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", disabled: !apiKeyValue.trim(), children: "Authenticate" })] })] }) }))] }))] })] }));
|
|
382
416
|
};
|
|
383
417
|
export default SignInSimple;
|
|
@@ -36,6 +36,9 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
36
36
|
const user = getDatalayerJwtUser(token);
|
|
37
37
|
const displayName = getDatalayerDisplayName(user, user?.handle ?? '');
|
|
38
38
|
const claims = parseJwtPayload(token);
|
|
39
|
+
const popoverDisplayName = claims?.user
|
|
40
|
+
? getDatalayerDisplayName(claims.user, claims.user.handle ?? '')
|
|
41
|
+
: displayName;
|
|
39
42
|
// Colour the trigger label based on token expiry:
|
|
40
43
|
// - red when already expired,
|
|
41
44
|
// - yellow when expiring within ``expirationWarningMs``,
|
|
@@ -84,14 +87,14 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
84
87
|
}, children: [_jsxs(Box, { sx: {
|
|
85
88
|
px: 3,
|
|
86
89
|
py: 2,
|
|
87
|
-
bg: 'canvas.
|
|
90
|
+
bg: 'canvas.inset',
|
|
88
91
|
borderBottom: '1px solid',
|
|
89
92
|
borderColor: 'border.default',
|
|
90
93
|
display: 'flex',
|
|
91
94
|
alignItems: 'center',
|
|
92
95
|
justifyContent: 'space-between',
|
|
93
96
|
gap: 2,
|
|
94
|
-
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children:
|
|
97
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 1 }, children: popoverDisplayName || 'User' }), variant === 'small' && showExpandToggle && (_jsx(Box, { as: "button", type: "button", onClick: () => setExpanded(v => !v), "aria-label": expanded ? 'Hide full JWT claims' : 'Show full JWT claims', "aria-expanded": expanded, title: expanded ? 'Hide full JWT claims' : 'Show full JWT claims', sx: {
|
|
95
98
|
appearance: 'none',
|
|
96
99
|
border: '1px solid',
|
|
97
100
|
borderColor: 'border.default',
|
|
@@ -120,7 +123,7 @@ export const UserBadge = ({ token, variant = 'full', showExpandToggle = true, ex
|
|
|
120
123
|
gridTemplateColumns: '90px 1fr',
|
|
121
124
|
gap: 1,
|
|
122
125
|
fontSize: 0,
|
|
123
|
-
}, children: [claims.user.email && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Email" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.email })] })), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Handle" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.handle }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "UID" }), _jsx(Text, { sx: { fontFamily: 'mono', wordBreak: 'break-all' }, children: claims.user.uid }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Issued" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: new Date(claims.iat * 1000).toISOString() }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expires" }), _jsxs(Box, { sx: {
|
|
126
|
+
}, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "First name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.firstName }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Last name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.lastName }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Display name" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: getDatalayerDisplayName(claims.user, claims.user.handle) }), claims.user.email && (_jsxs(_Fragment, { children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Email" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.email })] })), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Handle" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: claims.user.handle }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "UID" }), _jsx(Text, { sx: { fontFamily: 'mono', wordBreak: 'break-all' }, children: claims.user.uid }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Issued" }), _jsx(Text, { sx: { fontFamily: 'mono' }, children: new Date(claims.iat * 1000).toISOString() }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expires" }), _jsxs(Box, { sx: {
|
|
124
127
|
display: 'flex',
|
|
125
128
|
alignItems: 'center',
|
|
126
129
|
gap: 2,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"watch:lib:src": "tsc -b -w",
|
|
91
91
|
"type-check:watch": "tsc -b -w --noEmit",
|
|
92
92
|
"examples": "run-p jupyter:start examples:vite",
|
|
93
|
-
"examples:vite": "
|
|
93
|
+
"examples:vite": "VITE_DATALAYER_IAM_URL=http://localhost:8888 vite --config vite.examples.config.ts",
|
|
94
94
|
"examples:nextjs": "npm run dev --workspace=nextjj-example",
|
|
95
95
|
"jupyter:start": "./dev/sh/start-jupyter-server.sh",
|
|
96
96
|
"prepare": "husky || true",
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"playwright": "^1.53.2",
|
|
225
225
|
"prettier": "^3.6.2",
|
|
226
226
|
"react-router-dom": "^6.22.3",
|
|
227
|
-
"rimraf": "^6.
|
|
227
|
+
"rimraf": "^6.1.3",
|
|
228
228
|
"storybook": "^9.1.1",
|
|
229
229
|
"typedoc-plugin-markdown": "^4.0.0",
|
|
230
230
|
"typescript": "^5.8.3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|