@datalayer/core 1.1.47 → 1.1.49

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
4
4
 
5
- # ☰ Datalayer Core
5
+ # ☰ 〇 Datalayer Core
6
6
 
7
7
  <p align="center">
8
8
  <strong>Python and Typescript libraries for Datalayer</strong>
@@ -6,7 +6,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
6
6
  import { useEffect, useState } from 'react';
7
7
  import { PageConfig, URLExt } from '@jupyterlab/coreutils';
8
8
  import { EyeIcon, EyeClosedIcon, MarkGithubIcon } from '@primer/octicons-react';
9
- import { Button, FormControl, Heading, Link, PageLayout, TextInput, } from '@primer/react';
9
+ import { GoogleIcon } from '@datalayer/icons-react';
10
+ import { Button, FormControl, Heading, Link, PageLayout, Spinner, TextInput, } from '@primer/react';
10
11
  import { Box } from '@datalayer/primer-addons';
11
12
  import { asUser, IAMProvidersSpecs } from '../../models/index.js';
12
13
  import { useIAMStore } from '../../state/index.js';
@@ -14,8 +15,6 @@ import { CenteredSpinner } from '../display/index.js';
14
15
  import { isInsideJupyterLab, validateLength } from '../../utils/index.js';
15
16
  import { useNavigate, useCache, useToast, useIAM } from '../../hooks/index.js';
16
17
  import { LoginToken } from './LoginToken.js';
17
- /** Inline Google "G" icon for the login button. */
18
- const GoogleIcon = () => (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 48 48", children: [_jsx("path", { fill: "#EA4335", d: "M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" }), _jsx("path", { fill: "#4285F4", d: "M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" }), _jsx("path", { fill: "#FBBC05", d: "M10.53 28.59a14.5 14.5 0 0 1 0-9.18l-7.98-6.19a24.1 24.1 0 0 0 0 21.56l7.98-6.19z" }), _jsx("path", { fill: "#34A853", d: "M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" })] }));
19
18
  export const Login = (props) => {
20
19
  const { heading, homeRoute, loginRoute, showEmailLogin = true, showGitHubLogin = true, showGoogleLogin = true, showTokenLogin = true, } = props;
21
20
  const { useLogin, useOAuth2AuthorizationURL } = useCache({ loginRoute });
@@ -36,19 +35,20 @@ export const Login = (props) => {
36
35
  password: undefined,
37
36
  });
38
37
  const [passwordVisibility, setPasswordVisibility] = useState(false);
38
+ const [pendingOAuthProvider, setPendingOAuthProvider] = useState(null);
39
+ const buildCallbackURI = (iamProvider) => isInsideJupyterLab()
40
+ ? URLExt.join(PageConfig.getBaseUrl(), iamProvider.oauth2CallbackServerRoute)
41
+ : location.protocol +
42
+ '//' +
43
+ location.hostname +
44
+ ':' +
45
+ location.port +
46
+ iamProvider.oauth2CallbackUIRoute;
39
47
  useEffect(() => {
40
48
  const initIAMProvider = (iamProvider) => {
41
- const callbackURI = isInsideJupyterLab()
42
- ? URLExt.join(PageConfig.getBaseUrl(), iamProvider.oauth2CallbackServerRoute)
43
- : location.protocol +
44
- '//' +
45
- location.hostname +
46
- ':' +
47
- location.port +
48
- iamProvider.oauth2CallbackUIRoute;
49
49
  const queryArgs = {
50
50
  provider: iamProvider.name,
51
- callback_uri: callbackURI,
51
+ callback_uri: buildCallbackURI(iamProvider),
52
52
  };
53
53
  /*
54
54
  const xsrfTokenMatch = document.cookie.match('\\b_xsrf=([^;]*)\\b');
@@ -142,6 +142,32 @@ export const Login = (props) => {
142
142
  },
143
143
  });
144
144
  };
145
+ const startOAuthSignIn = (providerSpec) => {
146
+ if (getOAuth2URLMutation.isPending) {
147
+ return;
148
+ }
149
+ setPendingOAuthProvider(providerSpec.name);
150
+ const queryArgs = {
151
+ provider: providerSpec.name,
152
+ callback_uri: buildCallbackURI(providerSpec),
153
+ };
154
+ getOAuth2URLMutation.mutate(queryArgs, {
155
+ onSuccess: authUrl => {
156
+ if (authUrl) {
157
+ window.location.assign(authUrl);
158
+ return;
159
+ }
160
+ setPendingOAuthProvider(null);
161
+ enqueueToast('Unable to start social sign-in.', { variant: 'warning' });
162
+ },
163
+ onError: () => {
164
+ setPendingOAuthProvider(null);
165
+ enqueueToast('Unable to start social sign-in.', { variant: 'error' });
166
+ },
167
+ });
168
+ };
169
+ const socialAuthDisabled = getOAuth2URLMutation.isPending;
170
+ const socialButtonsDisabled = socialAuthDisabled || loading;
145
171
  useEffect(() => {
146
172
  setValidationResult({
147
173
  ...validationResult,
@@ -157,9 +183,9 @@ export const Login = (props) => {
157
183
  : 'Your password may not be empty.',
158
184
  });
159
185
  }, [formValues]);
160
- return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Login to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => {
186
+ return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Login to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, disabled: loading, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, disabled: loading, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => {
161
187
  setPasswordVisibility(!passwordVisibility);
162
- }, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility
188
+ }, disabled: loading, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility
163
189
  ? 'Hide password'
164
190
  : 'Show password', sx: { color: 'var(--fgColor-muted)' } }), sx: { overflow: 'visible' } }), validationResult.password && (_jsx(FormControl.Validation, { variant: validationResult.password ? 'error' : 'success', children: validationResult.password }))] }) }), _jsxs(Box, { mt: 5, children: [_jsx(Button, { variant: "primary", disabled: loading ||
165
191
  validationResult.handle !== '' ||
@@ -168,7 +194,11 @@ export const Login = (props) => {
168
194
  : heading
169
195
  ? 'Login with Datalayer'
170
196
  : 'Login' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.ai/password", target: "_blank", children: "Forgot password?" })] })] })), _jsx(Box, { children: _jsxs(Box, { display: "flex", flexDirection: "column", sx: { margin: 'auto' }, children: [showGitHubLogin &&
171
- iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: MarkGithubIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name], as: "a", style: { margin: '10px 0' }, children: "Login with GitHub" })), showGoogleLogin &&
172
- iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name] && (_jsx(Button, { leadingVisual: GoogleIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name], as: "a", style: { margin: '10px 0' }, children: "Login with Google" })), showTokenLogin && (_jsx(LoginToken, { homeRoute: homeRoute, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] }));
197
+ iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: pendingOAuthProvider === IAMProvidersSpecs.GitHub.name
198
+ ? () => _jsx(Spinner, { size: "small" })
199
+ : MarkGithubIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.GitHub), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "Login with GitHub" })), showGoogleLogin &&
200
+ iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name] && (_jsx(Button, { leadingVisual: pendingOAuthProvider === IAMProvidersSpecs.Google.name
201
+ ? () => _jsx(Spinner, { size: "small" })
202
+ : GoogleIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.Google), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "Login with Google" })), showTokenLogin && (_jsx(LoginToken, { homeRoute: homeRoute, disabled: loading, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] }));
173
203
  };
174
204
  export default Login;
@@ -3,6 +3,10 @@ export interface ILoginTokenProps {
3
3
  * Home page route to navigate to after successful login
4
4
  */
5
5
  homeRoute: string;
6
+ /**
7
+ * Disable token login controls (e.g., while another login flow is submitting)
8
+ */
9
+ disabled?: boolean;
6
10
  /**
7
11
  * CSS style object
8
12
  */
@@ -15,7 +15,7 @@ import { useIAMStore } from '../../state/index.js';
15
15
  * Provides a simple button that expands to show a token input form
16
16
  */
17
17
  export const LoginToken = (props) => {
18
- const { homeRoute, style } = props;
18
+ const { homeRoute, disabled = false, style } = props;
19
19
  const [showForm, setShowForm] = useState(false);
20
20
  const [token, setToken] = useState('');
21
21
  const [loading, setLoading] = useState(false);
@@ -45,9 +45,9 @@ export const LoginToken = (props) => {
45
45
  }
46
46
  };
47
47
  if (!showForm) {
48
- return (_jsx(Button, { onClick: () => setShowForm(true), style: style, children: "Login with Token" }));
48
+ return (_jsx(Button, { onClick: () => setShowForm(true), style: style, disabled: disabled, children: "Login with Token" }));
49
49
  }
50
- return (_jsxs(Box, { style: style, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your Authentication Token" }), _jsx(TextInput, { block: true, placeholder: "Paste your authentication token here", value: token, onChange: e => setToken(e.target.value), onKeyDown: e => {
50
+ return (_jsxs(Box, { style: style, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your Authentication Token" }), _jsx(TextInput, { block: true, placeholder: "Paste your authentication token here", value: token, disabled: disabled || loading, onChange: e => setToken(e.target.value), onKeyDown: e => {
51
51
  if (e.key === 'Enter') {
52
52
  handleLogin();
53
53
  }
@@ -55,7 +55,7 @@ export const LoginToken = (props) => {
55
55
  setShowForm(false);
56
56
  setToken('');
57
57
  }
58
- } }), _jsx(FormControl.Caption, { children: "Enter your Datalayer authentication token to log in" })] }), _jsxs(Box, { mt: 3, display: "flex", justifyContent: "flex-start", children: [_jsx(Button, { variant: "primary", onClick: handleLogin, disabled: loading || !token.trim(), sx: { mr: 2 }, children: loading ? 'Authenticating...' : 'Login' }), _jsx(Button, { variant: "default", onClick: () => {
58
+ } }), _jsx(FormControl.Caption, { children: "Enter your Datalayer authentication token to log in" })] }), _jsxs(Box, { mt: 3, display: "flex", justifyContent: "flex-start", children: [_jsx(Button, { variant: "primary", onClick: handleLogin, disabled: disabled || loading || !token.trim(), sx: { mr: 2 }, children: loading ? 'Authenticating...' : 'Login' }), _jsx(Button, { variant: "default", disabled: disabled || loading, onClick: () => {
59
59
  setShowForm(false);
60
60
  setToken('');
61
61
  }, children: "Cancel" })] })] }));
@@ -2,4 +2,4 @@ import { SxProp } from '@primer/react';
2
2
  export interface IVisuallyHiddenProps {
3
3
  isVisible?: boolean;
4
4
  }
5
- export declare const VisuallyHidden: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "sx" | "isVisible"> & IVisuallyHiddenProps & SxProp, never> & Partial<Pick<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "sx" | "isVisible"> & IVisuallyHiddenProps & SxProp, never>>> & string;
5
+ export declare const VisuallyHidden: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, IVisuallyHiddenProps & SxProp>> & string;
@@ -277,7 +277,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
277
277
  }, children: _jsx(Text, { sx: {
278
278
  color: 'accent.fg',
279
279
  fontWeight: 'semibold',
280
- }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" })) : null] }) })) }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Personal" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
280
+ }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Label, { variant: "secondary", size: "small", sx: adminBadgeSx, children: "admin" })) : null] }) })) }), _jsx(ActionMenu.Overlay, { width: "large", children: _jsxs(ActionList, { selectionVariant: "single", children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Personal" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
281
281
  if (isCurrentUserPrincipal) {
282
282
  return;
283
283
  }
@@ -0,0 +1,2 @@
1
+ export declare const SpinnerBar: () => import("react/jsx-runtime").JSX.Element;
2
+ export default SpinnerBar;
@@ -5,8 +5,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  */
6
6
  import { useState } from 'react';
7
7
  import { useInterval } from 'usehooks-ts';
8
- import { ProgressBar as PrimerProgressBar } from '@primer/react';
9
- export const ProgressBar = () => {
8
+ import { ProgressBar } from '@primer/react';
9
+ export const SpinnerBar = () => {
10
10
  const [progress, setProgress] = useState(0);
11
11
  useInterval(() => {
12
12
  if (progress >= 100) {
@@ -16,6 +16,6 @@ export const ProgressBar = () => {
16
16
  setProgress(progress + 1);
17
17
  }
18
18
  }, 100);
19
- return _jsx(PrimerProgressBar, { progress: progress, "aria-label": "" });
19
+ return _jsx(ProgressBar, { progress: progress, "aria-label": "" });
20
20
  };
21
- export default ProgressBar;
21
+ export default SpinnerBar;
@@ -1,5 +1,5 @@
1
1
  export * from './ConsumptionBar';
2
- export * from './ProgressBar';
2
+ export * from './SpinnerBar';
3
3
  export * from './ProgressRing';
4
4
  export * from './Timer';
5
5
  export * from './consumption';
@@ -3,7 +3,7 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  export * from './ConsumptionBar.js';
6
- export * from './ProgressBar.js';
6
+ export * from './SpinnerBar.js';
7
7
  export * from './ProgressRing.js';
8
8
  export * from './Timer.js';
9
9
  export * from './consumption.js';
@@ -68,7 +68,7 @@ export declare const SubNav: React.NamedExoticComponent<{
68
68
  readonly type: ({ id, children, className, "data-testid": testId, fullWidth, hasShadow, }: SubNavProps) => import("react/jsx-runtime").JSX.Element;
69
69
  } & {
70
70
  Heading: ({ href, children, className, "data-testid": testID, ...props }: SubNavHeadingProps) => import("react/jsx-runtime").JSX.Element;
71
- Link: React.ForwardRefExoticComponent<Omit<SubNavLinkProps, "ref"> & React.RefAttributes<HTMLDivElement | HTMLAnchorElement>>;
71
+ Link: React.ForwardRefExoticComponent<Omit<SubNavLinkProps, "ref"> & React.RefAttributes<HTMLAnchorElement | HTMLDivElement>>;
72
72
  Action: typeof _SubNavAction;
73
73
  SubMenu: typeof SubMenu;
74
74
  testIds: {
@@ -26,7 +26,7 @@ export class DatalayerConfiguration {
26
26
  * Default configuration values for Datalayer
27
27
  */
28
28
  export const DEFAULT_DATALAYER_CONFIG = {
29
- datalayerUrl: 'https://oss.datalayer.run',
29
+ datalayerUrl: 'https://prod1.datalayer.run',
30
30
  credits: 100,
31
31
  cpuEnvironment: 'ai-agents-env',
32
32
  gpuEnvironment: 'ai-env',
@@ -744,7 +744,7 @@ export declare const useCache: ({ loginRoute }?: CacheProps) => {
744
744
  selectedPrincipalKind?: "personal" | "organization" | "team";
745
745
  }) => import("@tanstack/react-query").UseQueryResult<any, Error>;
746
746
  useCreatePage: () => import("@tanstack/react-query").UseMutationResult<any, Error, Omit<any, "id">, unknown>;
747
- useUpdatePage: () => import("@tanstack/react-query").UseMutationResult<any, Error, Pick<any, "name" | "description" | "id" | "tags">, unknown>;
747
+ useUpdatePage: () => import("@tanstack/react-query").UseMutationResult<any, Error, Pick<any, "name" | "id" | "tags" | "description">, unknown>;
748
748
  useDeletePage: () => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
749
749
  useDatasource: (datasourceId: string, options?: {
750
750
  principalUid?: string;
@@ -788,7 +788,7 @@ export declare const useCache: ({ loginRoute }?: CacheProps) => {
788
788
  }) => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
789
789
  useToken: (tokenId: string) => import("@tanstack/react-query").UseQueryResult<NoInfer<IIAMToken | null | undefined>, Error>;
790
790
  useTokens: () => import("@tanstack/react-query").UseQueryResult<NoInfer<(IIAMToken | undefined)[]>, Error>;
791
- useCreateToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, Omit<IIAMToken, "id" | "value">, unknown>;
791
+ useCreateToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, Omit<IIAMToken, "value" | "id">, unknown>;
792
792
  useUpdateToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, IIAMToken, unknown>;
793
793
  useDeleteToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
794
794
  useInvite: (token: string) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("..").IInvite | undefined>, Error>;
@@ -1,4 +1,4 @@
1
- import { IThemeManager } from '@jupyterlab/apputils';
1
+ import type { IThemeManager } from '@jupyterlab/apputils';
2
2
  export declare enum JupyterLabTheme {
3
3
  LIGHT = "JupyterLab Light",
4
4
  DARK = "JupyterLab Dark"
@@ -1,5 +1,5 @@
1
1
  import { type Id } from 'react-toastify';
2
- import { Notification } from '@jupyterlab/apputils';
2
+ import type { Notification } from '@jupyterlab/apputils';
3
3
  export type ToastProps = {
4
4
  /**
5
5
  * List of associated actions
@@ -5,10 +5,25 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
5
5
  */
6
6
  import React from 'react';
7
7
  import { toast } from 'react-toastify';
8
- import { Notification } from '@jupyterlab/apputils';
9
8
  import { Button } from '@primer/react';
10
- import { JupyterReactTheme } from '@datalayer/jupyter-react';
11
9
  import { isInsideJupyterLab } from '../utils/index.js';
10
+ // `@datalayer/jupyter-react` and `@jupyterlab/apputils` are heavy (they pull
11
+ // JupyterLab / CodeMirror / lumino) and are only needed when running INSIDE
12
+ // JupyterLab. They are lazily loaded here so the standalone bundle (landing app)
13
+ // never puts JupyterLab on its critical path.
14
+ const JupyterReactTheme = React.lazy(() => import('@datalayer/jupyter-react').then(m => ({
15
+ default: m.JupyterReactTheme,
16
+ })));
17
+ // Runtime handle to the JupyterLab Notification API. Populated only when running
18
+ // inside JupyterLab (where @jupyterlab/apputils is already a shared singleton),
19
+ // so standalone builds never eagerly import it. Callers fall back to
20
+ // react-toastify until (and unless) this resolves.
21
+ let NotificationApi;
22
+ if (isInsideJupyterLab()) {
23
+ void import('@jupyterlab/apputils').then(m => {
24
+ NotificationApi = m.Notification;
25
+ });
26
+ }
12
27
  const TOAST_POSITION = 'bottom-right';
13
28
  const THEME_STORAGE_KEY = 'datalayer-theme';
14
29
  function resolveToastTheme() {
@@ -62,7 +77,7 @@ function ToastButton({ action, closeToast }) {
62
77
  */
63
78
  function createContent(message, closeHandler, actions) {
64
79
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: "jp-toast-message", children: message.split('\n').map((part, index) => (_jsxs(React.Fragment, { children: [index > 0 ? _jsx("br", {}) : null, part] }, `part-${index}`))) }), (actions?.length ?? 0) > 0 && (_jsxs("div", { className: "jp-toast-buttonBar", children: [_jsx("div", { className: "jp-toast-spacer" }), actions.map((action, idx) => {
65
- return (_jsx(JupyterReactTheme, { children: _jsx(ToastButton, { action: action, closeToast: closeHandler }, 'button-' + idx) }));
80
+ return (_jsx(React.Suspense, { fallback: null, children: _jsx(JupyterReactTheme, { children: _jsx(ToastButton, { action: action, closeToast: closeHandler }) }) }, 'button-' + idx));
66
81
  })] }))] }));
67
82
  }
68
83
  export const useToast = () => {
@@ -73,8 +88,8 @@ export const useToast = () => {
73
88
  const theme = resolveToastTheme();
74
89
  switch (options.variant) {
75
90
  case 'info': {
76
- return insideJupyterLab
77
- ? Notification.info(message, {
91
+ return insideJupyterLab && NotificationApi
92
+ ? NotificationApi.info(message, {
78
93
  autoClose: autoClose ?? 5000,
79
94
  actions,
80
95
  })
@@ -84,8 +99,8 @@ export const useToast = () => {
84
99
  }, actions), { autoClose, position: TOAST_POSITION, theme });
85
100
  }
86
101
  case 'success': {
87
- return insideJupyterLab
88
- ? Notification.success(message, {
102
+ return insideJupyterLab && NotificationApi
103
+ ? NotificationApi.success(message, {
89
104
  autoClose: autoClose ?? 5000,
90
105
  actions,
91
106
  })
@@ -95,8 +110,8 @@ export const useToast = () => {
95
110
  }, actions), { autoClose, position: TOAST_POSITION, theme });
96
111
  }
97
112
  case 'warning': {
98
- return insideJupyterLab
99
- ? Notification.warning(message, {
113
+ return insideJupyterLab && NotificationApi
114
+ ? NotificationApi.warning(message, {
100
115
  autoClose: autoClose ?? false,
101
116
  actions,
102
117
  })
@@ -110,8 +125,8 @@ export const useToast = () => {
110
125
  });
111
126
  }
112
127
  case 'error': {
113
- return insideJupyterLab
114
- ? Notification.error(message, {
128
+ return insideJupyterLab && NotificationApi
129
+ ? NotificationApi.error(message, {
115
130
  autoClose: autoClose ?? false,
116
131
  actions,
117
132
  })
@@ -128,8 +143,8 @@ export const useToast = () => {
128
143
  };
129
144
  const trackAsyncTask = (promise, options) => {
130
145
  const theme = resolveToastTheme();
131
- return insideJupyterLab
132
- ? Notification.promise(promise, options)
146
+ return insideJupyterLab && NotificationApi
147
+ ? NotificationApi.promise(promise, options)
133
148
  : toast.promise(promise, {
134
149
  error: { render: options.error.message, ...options.error.options },
135
150
  pending: options.pending.message,
@@ -158,8 +173,8 @@ export const useToast = () => {
158
173
  * @param id Toast id
159
174
  */
160
175
  dismiss: (id) => {
161
- if (insideJupyterLab) {
162
- Notification.dismiss(id);
176
+ if (insideJupyterLab && NotificationApi) {
177
+ NotificationApi.dismiss(id);
163
178
  }
164
179
  else {
165
180
  toast.dismiss(id);
@@ -190,7 +190,11 @@ export class OtelClient {
190
190
  if (filteredTotal > 0 || !serviceName || !fallbackWithoutService) {
191
191
  return filteredTotal;
192
192
  }
193
- const unfiltered = await this.fetchMetrics({ metricName, limit, accountUid });
193
+ const unfiltered = await this.fetchMetrics({
194
+ metricName,
195
+ limit,
196
+ accountUid,
197
+ });
194
198
  return unfiltered.data.reduce((sum, row) => sum + Number(row.value || 0), 0);
195
199
  }
196
200
  // ── Services ──────────────────────────────────────────────────────────────
package/lib/utils/Date.js CHANGED
@@ -99,7 +99,10 @@ export const formatRelativeTime = (value, now = new Date()) => {
99
99
  if (weeks < 52)
100
100
  return withDirection(weeks, 'w');
101
101
  const years = Math.floor(days / 365);
102
- return withDirection(years, 'y');
102
+ if (years >= 1)
103
+ return withDirection(years, 'y');
104
+ // Around the year boundary (e.g. 364 days), prefer weeks over "0y".
105
+ return withDirection(weeks, 'w');
103
106
  };
104
107
  /**
105
108
  * Build a detailed datetime string suitable for tooltips.
@@ -13,7 +13,7 @@ export interface SignInSimpleProps {
13
13
  * Called after a successful login with the JWT and the user handle.
14
14
  * Typically used to store credentials in a Zustand / context store.
15
15
  */
16
- onSignIn: (token: string, handle: string) => void;
16
+ onSignIn?: (token: string, handle: string) => void;
17
17
  /**
18
18
  * Called when the user authenticates with an API key.
19
19
  * If not provided the "Sign In with an API Key" button is hidden.
@@ -74,6 +74,62 @@ export interface SignInSimpleProps {
74
74
  * - `null`/empty disables forwarding any callback navigation target.
75
75
  */
76
76
  socialSignInNavigationTarget?: string | null;
77
+ /**
78
+ * Disable all interactive controls (useful when embedded in documentation).
79
+ */
80
+ asDoc?: boolean;
81
+ /**
82
+ * Hide header title and description area.
83
+ */
84
+ hideHero?: boolean;
85
+ /**
86
+ * Optional contextual heading shown above auth actions.
87
+ */
88
+ calloutTitle?: string;
89
+ /**
90
+ * Optional contextual description shown below the callout title.
91
+ */
92
+ calloutDescription?: string;
93
+ /**
94
+ * Label for password-auth toggle button.
95
+ */
96
+ passwordToggleLabel?: string;
97
+ /**
98
+ * Sign-up section heading text.
99
+ */
100
+ signUpTitle?: string;
101
+ /**
102
+ * Sign-up section descriptive text.
103
+ */
104
+ signUpDescription?: string;
105
+ /**
106
+ * Sign-up button label.
107
+ */
108
+ signUpLabel?: string;
109
+ /**
110
+ * Sign-up URL opened when clicking the sign-up button.
111
+ */
112
+ signUpHref?: string;
113
+ /**
114
+ * Forgot-password URL for password form.
115
+ */
116
+ forgotPasswordHref?: string;
117
+ /**
118
+ * Forgot-password link label.
119
+ */
120
+ forgotPasswordLabel?: string;
121
+ /**
122
+ * Show forgot-password link in password form.
123
+ */
124
+ showForgotPassword?: boolean;
125
+ /**
126
+ * Top margin for each social button.
127
+ */
128
+ socialButtonMarginTop?: number;
129
+ /**
130
+ * Maximum width (px) for primary action buttons.
131
+ */
132
+ actionButtonMaxWidth?: number;
77
133
  }
78
134
  export declare const SignInSimple: React.FC<SignInSimpleProps>;
79
135
  export default SignInSimple;
@@ -14,11 +14,11 @@ 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 { Box, Button, FormControl, Heading, Text, Textarea, TextInput, } from '@primer/react';
18
- import { EyeIcon, EyeClosedIcon, KeyIcon, LinkExternalIcon, TelescopeIcon, } from '@primer/octicons-react';
19
- import { GithubMarkIcon, LinkedInGreyIcon } from '@datalayer/icons-react';
17
+ 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 { GithubMarkIcon, GoogleIcon, LinkedInGreyIcon, } from '@datalayer/icons-react';
20
+ import { useToast } from '../../hooks/index.js';
20
21
  import { isInsideJupyterLab } from '../../utils/Jupyter.js';
21
- const GoogleIcon = () => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", preserveAspectRatio: "xMidYMid", viewBox: "0 0 256 262", height: "16px", fill: "currentColor", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" }), _jsx("path", { fill: "currentColor", d: "M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" }), _jsx("path", { fill: "currentColor", d: "M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" }), _jsx("path", { fill: "currentColor", d: "M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" })] }));
22
22
  const OAUTH2_PROVIDERS = {
23
23
  github: {
24
24
  name: 'github',
@@ -52,8 +52,23 @@ const getIAMUrlFromDocumentConfig = () => {
52
52
  return '';
53
53
  }
54
54
  };
55
+ const getErrorMessage = (error, fallback = 'Unable to start social sign-in.') => {
56
+ if (error instanceof Error && error.message) {
57
+ return error.message;
58
+ }
59
+ if (typeof error === 'string' && error.trim()) {
60
+ return error;
61
+ }
62
+ if (error && typeof error === 'object' && 'message' in error) {
63
+ const message = error.message;
64
+ if (typeof message === 'string' && message.trim()) {
65
+ return message;
66
+ }
67
+ }
68
+ return fallback;
69
+ };
55
70
  // ── Component ────────────────────────────────────────────────────────
56
- 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, }) => {
71
+ 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, }) => {
57
72
  const headingText = name ?? title;
58
73
  const headingIcon = icon ?? leadingIcon;
59
74
  const loginUrl = useMemo(() => {
@@ -64,10 +79,16 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
64
79
  }, [loginUrlProp]);
65
80
  const [handle, setHandle] = useState('');
66
81
  const [password, setPassword] = useState('');
82
+ const [showPasswordForm, setShowPasswordForm] = useState(false);
67
83
  const [showPassword, setShowPassword] = useState(false);
68
84
  const [loading, setLoading] = useState(false);
69
85
  const [socialLoading, setSocialLoading] = useState(false);
86
+ const [pendingSocialProvider, setPendingSocialProvider] = useState(null);
87
+ const [mfaUserUid, setMfaUserUid] = useState(null);
88
+ const [mfaCode, setMfaCode] = useState('');
89
+ const [mfaToken, setMfaToken] = useState(null);
70
90
  const [error, setError] = useState(null);
91
+ const { enqueueToast } = useToast();
71
92
  // API Key dialog state
72
93
  const [showApiKeyDialog, setShowApiKeyDialog] = useState(false);
73
94
  const [apiKeyValue, setApiKeyValue] = useState('');
@@ -86,7 +107,11 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
86
107
  return route.startsWith('/') ? route : '/';
87
108
  }, []);
88
109
  const startOAuthSignIn = useCallback(async (providerSpec) => {
110
+ if (socialLoading) {
111
+ return;
112
+ }
89
113
  setError(null);
114
+ setPendingSocialProvider(providerSpec.name);
90
115
  setSocialLoading(true);
91
116
  try {
92
117
  const iamUrl = getIAMUrlFromDocumentConfig();
@@ -120,12 +145,19 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
120
145
  window.location.assign(authorizationURL);
121
146
  }
122
147
  catch (err) {
123
- setError(err instanceof Error ? err.message : 'Unable to start social sign-in.');
124
- }
125
- finally {
148
+ const message = getErrorMessage(err);
149
+ setError(message);
150
+ enqueueToast(message, { variant: 'error' });
151
+ setPendingSocialProvider(null);
126
152
  setSocialLoading(false);
127
153
  }
128
- }, [buildCallbackURI, currentRelativeRoute, socialSignInNavigationTarget]);
154
+ }, [
155
+ buildCallbackURI,
156
+ currentRelativeRoute,
157
+ enqueueToast,
158
+ socialLoading,
159
+ socialSignInNavigationTarget,
160
+ ]);
129
161
  useEffect(() => {
130
162
  if (showApiKeyDialog) {
131
163
  apiKeyRef.current?.focus();
@@ -142,10 +174,21 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
142
174
  closeApiKeyDialog();
143
175
  }, [apiKeyValue, onApiKeySignIn, closeApiKeyDialog]);
144
176
  const handleSignUp = useCallback(() => {
145
- window.open('https://datalayer.ai/signup', '_blank', 'noopener,noreferrer');
146
- }, []);
177
+ window.location.assign(signUpHref);
178
+ }, [signUpHref]);
179
+ const handleForgotPassword = useCallback(() => {
180
+ if (!forgotPasswordHref) {
181
+ return;
182
+ }
183
+ if (forgotPasswordHref.startsWith('http://') ||
184
+ forgotPasswordHref.startsWith('https://')) {
185
+ window.open(forgotPasswordHref, '_blank', 'noopener,noreferrer');
186
+ return;
187
+ }
188
+ window.location.assign(forgotPasswordHref);
189
+ }, [forgotPasswordHref]);
147
190
  const submit = useCallback(async () => {
148
- if (!handle || !password || loading)
191
+ if (asDoc || !handle || !password || loading)
149
192
  return;
150
193
  setLoading(true);
151
194
  setError(null);
@@ -160,7 +203,17 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
160
203
  }
161
204
  const data = await resp.json();
162
205
  if (data.success && data.token) {
163
- onSignIn(data.token, handle);
206
+ const maybeUserUid = String(data?.user?.id || data?.user?.uid || '').trim();
207
+ const hasMfa = Boolean(data?.user?.mfaUrl);
208
+ if (hasMfa && maybeUserUid) {
209
+ setMfaUserUid(maybeUserUid);
210
+ setMfaToken(data.token);
211
+ setError(null);
212
+ return;
213
+ }
214
+ if (onSignIn) {
215
+ onSignIn(data.token, handle);
216
+ }
164
217
  }
165
218
  else {
166
219
  setError(data.message || 'Invalid username or password.');
@@ -172,42 +225,130 @@ export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp,
172
225
  finally {
173
226
  setLoading(false);
174
227
  }
175
- }, [handle, password, loading, loginUrl, onSignIn]);
228
+ }, [asDoc, handle, password, loading, loginUrl, onSignIn]);
229
+ const submitMfa = useCallback(async () => {
230
+ if (asDoc || loading || !mfaUserUid || !mfaCode.trim() || !mfaToken) {
231
+ return;
232
+ }
233
+ setLoading(true);
234
+ setError(null);
235
+ try {
236
+ const loginUrlObject = new URL(loginUrl, window.location.origin);
237
+ const iamBaseUrl = loginUrlObject.pathname.endsWith('/api/iam/v1/login')
238
+ ? loginUrlObject.href.replace(/\/api\/iam\/v1\/login$/, '')
239
+ : `${loginUrlObject.origin}`;
240
+ const response = await fetch(`${iamBaseUrl}/api/iam/v1/users/${mfaUserUid}/mfa/validate`, {
241
+ method: 'POST',
242
+ headers: { 'Content-Type': 'application/json' },
243
+ body: JSON.stringify({ userUid: mfaUserUid, code: mfaCode.trim() }),
244
+ });
245
+ const payload = await response.json();
246
+ if (!response.ok || !payload?.success) {
247
+ throw new Error(payload?.message || 'Failed to validate your MFA code.');
248
+ }
249
+ if (onSignIn) {
250
+ onSignIn(mfaToken, handle);
251
+ }
252
+ }
253
+ catch (err) {
254
+ const message = err instanceof Error ? err.message : String(err);
255
+ setError(message);
256
+ enqueueToast(message, { variant: 'error' });
257
+ }
258
+ finally {
259
+ setLoading(false);
260
+ }
261
+ }, [
262
+ asDoc,
263
+ enqueueToast,
264
+ handle,
265
+ loading,
266
+ loginUrl,
267
+ mfaCode,
268
+ mfaToken,
269
+ mfaUserUid,
270
+ onSignIn,
271
+ ]);
176
272
  const handleKeyDown = useCallback((e) => {
177
- if (e.key === 'Enter')
178
- submit();
179
- }, [submit]);
273
+ if (e.key === 'Enter') {
274
+ if (mfaUserUid) {
275
+ submitMfa();
276
+ }
277
+ else {
278
+ submit();
279
+ }
280
+ }
281
+ }, [mfaUserUid, submit, submitMfa]);
282
+ const socialDisabled = socialLoading || loading || asDoc;
180
283
  return (_jsx(Box, { sx: {
181
284
  display: 'flex',
182
- alignItems: 'center',
285
+ alignItems: hideHero ? 'flex-start' : 'center',
183
286
  justifyContent: 'center',
184
- height: '100vh',
185
- bg: 'canvas.default',
287
+ minHeight: hideHero ? 'auto' : '100vh',
288
+ bg: hideHero ? 'transparent' : 'canvas.default',
186
289
  color: 'fg.default',
290
+ py: hideHero ? 0 : 4,
187
291
  }, children: _jsxs(Box, { sx: {
188
- width: 360,
189
- p: 4,
190
- borderRadius: 2,
191
- border: '1px solid',
192
- borderColor: 'border.default',
193
- bg: 'canvas.subtle',
194
- }, children: [_jsxs(Box, { sx: {
195
- display: 'flex',
196
- alignItems: 'center',
197
- gap: 2,
198
- mb: 4,
199
- justifyContent: 'center',
200
- }, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mb: 3, textAlign: 'center' }, children: description }), _jsxs(FormControl, { required: true, sx: { mb: 3 }, children: [_jsx(FormControl.Label, { children: "Username" }), _jsx(TextInput, { autoFocus: true, block: true, placeholder: "Your username", value: handle, 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, onChange: e => setPassword(e.target.value), onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => setShowPassword(!showPassword), icon: showPassword ? EyeClosedIcon : EyeIcon, "aria-label": showPassword ? 'Hide password' : 'Show password', sx: { color: 'var(--fgColor-muted)' } }) })] }), error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 1, mb: 3, display: 'block' }, children: error })), _jsx(Button, { variant: "primary", block: true, disabled: loading || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' }), (github || google || linkedin) && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
292
+ width: '100%',
293
+ maxWidth: 440,
294
+ p: 0,
295
+ }, children: [!hideHero && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
201
296
  display: 'flex',
202
297
  alignItems: 'center',
203
298
  gap: 2,
204
- my: 3,
205
- }, 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' } })] }), github && (_jsx(Button, { block: true, leadingVisual: GithubMarkIcon, disabled: socialLoading, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: { mb: 2 }, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, leadingVisual: GoogleIcon, disabled: socialLoading, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: { mb: linkedin ? 2 : 0 }, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, leadingVisual: LinkedInGreyIcon, disabled: socialLoading, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), children: "Sign in with LinkedIn" }))] })), signUp && (_jsx(Box, { sx: { mt: 2, display: 'flex', justifyContent: 'center' }, children: _jsx(Button, { size: "small", variant: "invisible", leadingVisual: LinkExternalIcon, onClick: handleSignUp, children: "Sign Up" }) })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
299
+ mb: 3,
300
+ justifyContent: 'center',
301
+ }, children: [headingIcon, _jsx(Heading, { sx: { fontSize: 3 }, children: headingText })] }), _jsx(Text, { as: "p", sx: {
302
+ fontSize: 1,
303
+ color: 'fg.muted',
304
+ mb: 3,
305
+ textAlign: 'center',
306
+ }, 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'
307
+ ? () => _jsx(Spinner, { size: "small" })
308
+ : GithubMarkIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.github), sx: {
309
+ mt: socialButtonMarginTop,
310
+ maxWidth: actionButtonMaxWidth,
311
+ mx: 'auto',
312
+ }, children: "Sign in with GitHub" })), google && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'google'
313
+ ? () => _jsx(Spinner, { size: "small" })
314
+ : GoogleIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.google), sx: {
315
+ mt: socialButtonMarginTop,
316
+ maxWidth: actionButtonMaxWidth,
317
+ mx: 'auto',
318
+ }, children: "Sign in with Google" })), linkedin && (_jsx(Button, { block: true, size: "large", leadingVisual: pendingSocialProvider === 'linkedin'
319
+ ? () => _jsx(Spinner, { size: "small" })
320
+ : LinkedInGreyIcon, disabled: socialDisabled, onClick: () => startOAuthSignIn(OAUTH2_PROVIDERS.linkedin), sx: {
321
+ mt: socialButtonMarginTop,
322
+ maxWidth: actionButtonMaxWidth,
323
+ mx: 'auto',
324
+ }, 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: {
325
+ display: 'flex',
326
+ alignItems: 'center',
327
+ justifyContent: 'space-between',
328
+ mt: 3,
329
+ }, children: [_jsx(Button, { variant: "primary", disabled: loading || asDoc || !handle || !password, onClick: submit, children: loading ? 'Signing in…' : 'Sign in' }), showForgotPassword && (_jsx(Link, { as: "button", sx: {
330
+ fontSize: 1,
331
+ cursor: asDoc ? 'not-allowed' : 'pointer',
332
+ background: 'none',
333
+ border: 'none',
334
+ opacity: asDoc ? 0.6 : 1,
335
+ }, onClick: () => {
336
+ if (asDoc || loading) {
337
+ return;
338
+ }
339
+ handleForgotPassword();
340
+ }, 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: {
341
+ mt: 4,
342
+ pt: 4,
343
+ textAlign: 'center',
344
+ borderTop: '1px solid',
345
+ borderColor: 'border.muted',
346
+ }, 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", disabled: asDoc || loading, onClick: handleSignUp, children: signUpLabel }) })] })), apiKey && onApiKeySignIn && (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
206
347
  display: 'flex',
207
348
  alignItems: 'center',
208
349
  gap: 2,
209
350
  my: 3,
210
- }, 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, onClick: () => setShowApiKeyDialog(true), children: "Sign In with an API Key" }), showApiKeyDialog && (_jsx(Box, { role: "dialog", "aria-modal": "true", "aria-labelledby": "signin-api-key-title", onKeyDown: event => {
351
+ }, 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 => {
211
352
  if (event.key === 'Escape') {
212
353
  closeApiKeyDialog();
213
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/core",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -1,2 +0,0 @@
1
- export declare const ProgressBar: () => import("react/jsx-runtime").JSX.Element;
2
- export default ProgressBar;