@datalayer/core 1.0.23 → 1.0.25

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.
Files changed (67) hide show
  1. package/README.md +30 -7
  2. package/lib/api/constants.d.ts +3 -0
  3. package/lib/api/constants.js +3 -0
  4. package/lib/api/index.d.ts +1 -0
  5. package/lib/api/index.js +1 -0
  6. package/lib/api/runtimes/checkpoints.d.ts +4 -4
  7. package/lib/api/runtimes/runtimes.d.ts +3 -3
  8. package/lib/api/scheduler/index.d.ts +6 -0
  9. package/lib/api/scheduler/index.js +10 -0
  10. package/lib/api/scheduler/schedules.d.ts +85 -0
  11. package/lib/api/scheduler/schedules.js +78 -0
  12. package/lib/components/checkout/StripeCheckout.js +48 -2
  13. package/lib/components/display/LiveRelativeTime.d.ts +1 -10
  14. package/lib/components/display/LiveRelativeTime.js +3 -18
  15. package/lib/components/index.d.ts +3 -0
  16. package/lib/components/index.js +3 -0
  17. package/lib/components/runtimes/RuntimePickerBase.js +18 -16
  18. package/lib/components/runtimes/RuntimePickerCell.js +1 -1
  19. package/lib/components/scheduler/ScheduleMenu.d.ts +54 -0
  20. package/lib/components/scheduler/ScheduleMenu.js +154 -0
  21. package/lib/components/scheduler/index.d.ts +1 -0
  22. package/lib/{views/iam-tokens → components/scheduler}/index.js +1 -3
  23. package/lib/components/time/LiveRelativeTime.d.ts +14 -0
  24. package/lib/components/time/LiveRelativeTime.js +39 -0
  25. package/lib/components/time/index.d.ts +1 -0
  26. package/lib/components/time/index.js +5 -0
  27. package/lib/components/timeline/Timeline.d.ts +20 -0
  28. package/lib/components/timeline/Timeline.js +90 -0
  29. package/lib/components/timeline/index.d.ts +1 -0
  30. package/lib/components/timeline/index.js +5 -0
  31. package/lib/hooks/useCache.d.ts +31 -1
  32. package/lib/hooks/useCache.js +221 -17
  33. package/lib/hooks/useProjectStore.d.ts +1 -1
  34. package/lib/hooks/useProjects.d.ts +2 -2
  35. package/lib/models/IAMToken.d.ts +1 -1
  36. package/lib/models/Page.d.ts +3 -0
  37. package/lib/models/ProjectDTO.d.ts +1 -1
  38. package/lib/models/ProjectDTO.js +1 -1
  39. package/lib/state/substates/CoreState.js +2 -2
  40. package/lib/utils/Date.d.ts +7 -0
  41. package/lib/utils/Date.js +43 -10
  42. package/lib/utils/Name.d.ts +1 -0
  43. package/lib/utils/Name.js +8 -0
  44. package/lib/utils/Snapshot.js +2 -2
  45. package/lib/utils/index.d.ts +1 -0
  46. package/lib/utils/index.js +1 -0
  47. package/lib/views/api-keys/APIKeyEdit.d.ts +8 -0
  48. package/lib/views/{iam-tokens/IAMTokenEdit.js → api-keys/APIKeyEdit.js} +31 -31
  49. package/lib/views/api-keys/APIKeyNew.d.ts +8 -0
  50. package/lib/views/api-keys/APIKeyNew.js +143 -0
  51. package/lib/views/api-keys/APIKeys.d.ts +12 -0
  52. package/lib/views/api-keys/APIKeys.js +119 -0
  53. package/lib/views/api-keys/APIKeysStandalone.d.ts +2 -0
  54. package/lib/views/api-keys/APIKeysStandalone.js +117 -0
  55. package/lib/views/api-keys/index.d.ts +3 -0
  56. package/lib/views/api-keys/index.js +7 -0
  57. package/package.json +1 -1
  58. package/lib/views/iam-tokens/IAMTokenEdit.d.ts +0 -6
  59. package/lib/views/iam-tokens/IAMTokenNew.d.ts +0 -8
  60. package/lib/views/iam-tokens/IAMTokenNew.js +0 -118
  61. package/lib/views/iam-tokens/IAMTokens.d.ts +0 -12
  62. package/lib/views/iam-tokens/IAMTokens.js +0 -87
  63. package/lib/views/iam-tokens/Tokens.d.ts +0 -2
  64. package/lib/views/iam-tokens/Tokens.js +0 -85
  65. package/lib/views/iam-tokens/index.d.ts +0 -3
  66. /package/lib/utils/{logger.d.ts → Logger.d.ts} +0 -0
  67. /package/lib/utils/{logger.js → Logger.js} +0 -0
@@ -10,20 +10,20 @@ import { Box } from '@datalayer/primer-addons';
10
10
  import { BoringAvatar } from '../../components/avatars/index.js';
11
11
  import { useCache, useNavigate, useToast } from '../../hooks/index.js';
12
12
  import { useRunStore } from '../../state/index.js';
13
- export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {}) => {
14
- const { tokenId } = useParams();
13
+ export const APIKeyEdit = ({ apiKeysListRoute = '/settings/iam/api-keys', showTitle = true, } = {}) => {
14
+ const { tokenId: apiKeyId } = useParams();
15
15
  const runStore = useRunStore();
16
16
  const navigate = useNavigate();
17
17
  const { enqueueToast } = useToast();
18
- const { useUpdateToken, useToken, useDeleteToken } = useCache();
19
- const getTokenQuery = useToken(tokenId);
20
- const updateTokenMutation = useUpdateToken();
21
- const deleteTokenMutation = useDeleteToken();
22
- const [token, setToken] = useState();
18
+ const { useUpdateToken: useUpdateAPIKey, useToken: useAPIKey, useDeleteToken: useDeleteAPIKey, } = useCache();
19
+ const getAPIKeyQuery = useAPIKey(apiKeyId);
20
+ const updateAPIKeyMutation = useUpdateAPIKey();
21
+ const deleteAPIKeyMutation = useDeleteAPIKey();
22
+ const [apiKey, setAPIKey] = useState();
23
23
  const [formValues, setFormValues] = useState({
24
- name: token?.name,
24
+ name: apiKey?.name,
25
25
  nameConfirm: '',
26
- description: token?.description,
26
+ description: apiKey?.description,
27
27
  });
28
28
  const [validationResult, setValidationResult] = useState({
29
29
  name: undefined,
@@ -31,12 +31,12 @@ export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {})
31
31
  description: undefined,
32
32
  });
33
33
  useEffect(() => {
34
- if (getTokenQuery.data) {
35
- const token = getTokenQuery.data;
36
- setToken(token);
37
- setFormValues({ ...token, nameConfirm: '' });
34
+ if (getAPIKeyQuery.data) {
35
+ const key = getAPIKeyQuery.data;
36
+ setAPIKey(key);
37
+ setFormValues({ ...key, nameConfirm: '' });
38
38
  }
39
- }, [getTokenQuery.data]);
39
+ }, [getAPIKeyQuery.data]);
40
40
  const nameNameChange = (event) => {
41
41
  setFormValues(prevFormValues => ({
42
42
  ...prevFormValues,
@@ -63,7 +63,7 @@ export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {})
63
63
  : formValues.name.length > 2
64
64
  ? true
65
65
  : false,
66
- nameConfirm: formValues.nameConfirm === token?.name ? true : false,
66
+ nameConfirm: formValues.nameConfirm === apiKey?.name ? true : false,
67
67
  description: formValues.description === undefined
68
68
  ? undefined
69
69
  : formValues.description.length > 2
@@ -71,20 +71,20 @@ export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {})
71
71
  : false,
72
72
  });
73
73
  }, [formValues]);
74
- const nameSubmit = async () => {
74
+ const submitAPIKey = async () => {
75
75
  runStore.layout().showBackdrop();
76
- const updatedToken = {
77
- ...token,
76
+ const updatedAPIKey = {
77
+ ...apiKey,
78
78
  name: formValues.name,
79
79
  description: formValues.description,
80
80
  };
81
- updateTokenMutation.mutate(updatedToken, {
81
+ updateAPIKeyMutation.mutate(updatedAPIKey, {
82
82
  onSuccess: (resp) => {
83
83
  if (resp.success) {
84
- enqueueToast('The token is successfully updated.', {
84
+ enqueueToast('The API key was updated successfully.', {
85
85
  variant: 'success',
86
86
  });
87
- setToken(updatedToken);
87
+ setAPIKey(updatedAPIKey);
88
88
  }
89
89
  },
90
90
  onSettled: () => {
@@ -92,31 +92,31 @@ export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {})
92
92
  },
93
93
  });
94
94
  };
95
- const handleDelete = async () => {
96
- runStore.layout().showBackdrop('Deleting the token...');
97
- deleteTokenMutation.mutate(token.id, {
95
+ const deleteAPIKey = async () => {
96
+ runStore.layout().showBackdrop('Deleting the API key...');
97
+ deleteAPIKeyMutation.mutate(apiKey.id, {
98
98
  onSuccess: (resp) => {
99
99
  if (resp.success) {
100
- enqueueToast('The token is successfully deleted.', {
100
+ enqueueToast('The API key was deleted successfully.', {
101
101
  variant: 'success',
102
102
  });
103
- navigate(tokensListRoute);
103
+ navigate(apiKeysListRoute);
104
104
  }
105
105
  else {
106
- enqueueToast(resp.message || 'Failed to delete token.', {
106
+ enqueueToast(resp.message || 'Failed to delete API key.', {
107
107
  variant: 'error',
108
108
  });
109
109
  }
110
110
  },
111
111
  onError: () => {
112
- enqueueToast('Failed to delete token.', { variant: 'error' });
112
+ enqueueToast('Failed to delete API key.', { variant: 'error' });
113
113
  },
114
114
  onSettled: () => {
115
115
  runStore.layout().hideBackdrop();
116
116
  },
117
117
  });
118
118
  };
119
- return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { children: _jsx(Heading, { sx: { fontSize: 3 }, children: "API Key" }) }), _jsxs(Box, { display: "flex", children: [_jsxs(Box, { children: [_jsx(BoringAvatar, { displayName: token?.name, size: 100, style: { paddingRight: 10 } }), _jsx(Text, { as: "h2", sx: { paddingTop: 3 }, children: token?.name }), _jsx(Box, { mt: 3, children: _jsx(Label, { size: "large", children: token?.variant }) })] }), _jsxs(Box, { ml: 10, children: [_jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: nameNameChange }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: nameDescriptionChange, rows: 5 }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Expiration date" }), _jsx(TextInput, { block: true, value: token?.expirationDate.toLocaleDateString(), onChange: nameNameChange, disabled: true })] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name || !validationResult.description, sx: { marginTop: 3 }, onClick: nameSubmit, children: "Update token" })] }), _jsxs(Box, { sx: { marginTop: 3 }, children: [_jsx(Heading, { as: "h2", sx: {
119
+ return (_jsxs(_Fragment, { children: [showTitle && (_jsx(PageHeader, { children: _jsx(Heading, { sx: { fontSize: 3 }, children: "API Key" }) })), _jsxs(Box, { display: "flex", children: [_jsxs(Box, { children: [_jsx(BoringAvatar, { displayName: apiKey?.name, size: 100, style: { paddingRight: 10 } }), _jsx(Text, { as: "h2", sx: { paddingTop: 3 }, children: apiKey?.name }), _jsx(Box, { mt: 3, children: _jsx(Label, { size: "large", children: apiKey?.variant }) })] }), _jsxs(Box, { ml: 10, children: [_jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: nameNameChange }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: nameDescriptionChange, rows: 5 }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Expiration date" }), _jsx(TextInput, { block: true, value: apiKey?.expirationDate.toLocaleDateString(), onChange: nameNameChange, disabled: true })] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name || !validationResult.description, sx: { marginTop: 3 }, onClick: submitAPIKey, children: "Update API key" })] }), _jsxs(Box, { sx: { marginTop: 3 }, children: [_jsx(Heading, { as: "h2", sx: {
120
120
  fontSize: 4,
121
121
  fontWeight: 'normal',
122
122
  color: 'danger.fg',
@@ -130,6 +130,6 @@ export const IAMTokenEdit = ({ tokensListRoute = '/settings/iam/tokens', } = {})
130
130
  alignItems: 'center',
131
131
  justifyContent: 'space-between',
132
132
  gap: 3,
133
- }, children: [_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'danger.fg' }, children: "Confirm the token name to delete" }), _jsx(FormControl, { children: _jsx(TextInput, { block: true, value: formValues.nameConfirm, onChange: nameConfirmChange }) }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "This operation is not reversible." })] }), _jsx(Button, { variant: "danger", disabled: !validationResult.nameConfirm, onClick: handleDelete, children: "Delete token" })] })] })] })] })] }));
133
+ }, children: [_jsxs(Box, { sx: { display: 'grid', gap: 1 }, children: [_jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold', color: 'danger.fg' }, children: "Confirm the API key name to delete" }), _jsx(FormControl, { children: _jsx(TextInput, { block: true, value: formValues.nameConfirm, onChange: nameConfirmChange }) }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "This operation is not reversible." })] }), _jsx(Button, { variant: "danger", disabled: !validationResult.nameConfirm, onClick: deleteAPIKey, children: "Delete API key" })] })] })] })] })] }));
134
134
  };
135
- export default IAMTokenEdit;
135
+ export default APIKeyEdit;
@@ -0,0 +1,8 @@
1
+ export type APIKeyNewProps = {
2
+ /** Route to navigate when clicking "List my API keys". Defaults to '/settings/iam/api-keys'. */
3
+ apiKeysListRoute?: string;
4
+ /** Whether to render the "New API Key" title header in create mode. Defaults to true. */
5
+ showTitle?: boolean;
6
+ };
7
+ export declare const APIKeyNew: ({ apiKeysListRoute, showTitle, }?: APIKeyNewProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default APIKeyNew;
@@ -0,0 +1,143 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useEffect, useState } from 'react';
7
+ import { PageHeader, FormControl, Button, TextInput, Textarea, Select, Text, IconButton, } from '@primer/react';
8
+ import { Box } from '@datalayer/primer-addons';
9
+ import { CopyIcon } from '@primer/octicons-react';
10
+ import { Calendar, defaultCalendarStrings } from '@fluentui/react';
11
+ import { useCache, useNavigate, useToast } from '../../hooks/index.js';
12
+ import { useRunStore } from '../../state/index.js';
13
+ export const APIKeyNew = ({ apiKeysListRoute = '/settings/iam/api-keys', showTitle = true, } = {}) => {
14
+ const runStore = useRunStore();
15
+ const { useCreateToken: useCreateAPIKey } = useCache();
16
+ const createAPIKeyMutation = useCreateAPIKey();
17
+ const navigate = useNavigate();
18
+ const { enqueueToast } = useToast();
19
+ const [today, _] = useState(new Date());
20
+ const [showAPIKey, setShowAPIKey] = useState(false);
21
+ const [apiKey, setAPIKey] = useState();
22
+ const [formValues, setFormValues] = useState({
23
+ variant: 'secret',
24
+ name: undefined,
25
+ description: undefined,
26
+ expirationDate: undefined,
27
+ });
28
+ const [validationResult, setValidationResult] = useState({
29
+ variant: undefined,
30
+ name: undefined,
31
+ description: undefined,
32
+ expirationDate: undefined,
33
+ });
34
+ const valueVariantChange = (event) => {
35
+ setFormValues(prevFormValues => ({
36
+ ...prevFormValues,
37
+ variant: event.target.value,
38
+ }));
39
+ };
40
+ const valueNameChange = (event) => {
41
+ setFormValues(prevFormValues => ({
42
+ ...prevFormValues,
43
+ name: event.target.value,
44
+ }));
45
+ };
46
+ const valueDescriptionChange = (event) => {
47
+ setFormValues(prevFormValues => ({
48
+ ...prevFormValues,
49
+ description: event.target.value,
50
+ }));
51
+ };
52
+ const expirationDateChange = (expirationDate) => {
53
+ setFormValues(prevFormValues => ({
54
+ ...prevFormValues,
55
+ expirationDate,
56
+ }));
57
+ };
58
+ useEffect(() => {
59
+ setValidationResult({
60
+ ...validationResult,
61
+ name: formValues.name === undefined
62
+ ? undefined
63
+ : formValues.name.length > 2
64
+ ? true
65
+ : false,
66
+ description: formValues.description === undefined
67
+ ? undefined
68
+ : formValues.description.length > 2
69
+ ? true
70
+ : false,
71
+ expirationDate: formValues.expirationDate === undefined
72
+ ? undefined
73
+ : formValues.expirationDate.getTime() > today.getTime()
74
+ ? true
75
+ : false,
76
+ });
77
+ }, [formValues]);
78
+ const submitAPIKey = () => {
79
+ runStore.layout().showBackdrop('Creating an API key...');
80
+ createAPIKeyMutation.mutate({
81
+ name: formValues.name,
82
+ variant: formValues.variant,
83
+ description: formValues.description,
84
+ expirationDate: formValues.expirationDate,
85
+ }, {
86
+ onSuccess: (resp) => {
87
+ if (resp.success && resp.token) {
88
+ enqueueToast(resp.message, { variant: 'success' });
89
+ setAPIKey(resp.token);
90
+ setShowAPIKey(true);
91
+ }
92
+ },
93
+ onSettled: () => {
94
+ runStore.layout().hideBackdrop();
95
+ },
96
+ });
97
+ };
98
+ return (_jsx(Box, { children: showAPIKey ? (_jsxs(_Fragment, { children: [_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "Your API Key Is Created" }) }) }), _jsxs(Box, { sx: {
99
+ border: '1px solid',
100
+ borderColor: 'accent.muted',
101
+ borderRadius: 2,
102
+ p: 3,
103
+ bg: 'canvas.subtle',
104
+ mb: 3,
105
+ }, children: [_jsx(Text, { sx: { fontWeight: 600, color: 'accent.fg' }, children: "Important" }), _jsx(Text, { as: "p", sx: { mt: 1, color: 'fg.muted' }, children: "Save this API key now. You will not be able to see the full value again after leaving this page." })] }), _jsxs(Box, { sx: {
106
+ border: '1px solid',
107
+ borderColor: 'border.default',
108
+ borderRadius: 2,
109
+ p: 3,
110
+ bg: 'canvas.default',
111
+ }, children: [_jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(120px, 180px) 1fr", sx: { rowGap: 2, columnGap: 3, mb: 3 }, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Name" }), _jsx(Text, { sx: { fontWeight: 600 }, children: apiKey?.name || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Description" }), _jsx(Text, { children: apiKey?.description || '-' }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Expiration date" }), _jsx(Text, { children: apiKey?.expirationDate
112
+ ? `${apiKey.expirationDate.toLocaleString()} (${apiKey.expirationDate.toISOString()})`
113
+ : '-' })] }), _jsx(Text, { sx: { color: 'fg.muted', mb: 2 }, children: "API key value" }), _jsxs(Box, { display: "flex", sx: {
114
+ alignItems: 'flex-start',
115
+ gap: 2,
116
+ }, children: [_jsx(Text, { as: "code", sx: {
117
+ color: 'fg.default',
118
+ bg: 'canvas.inset',
119
+ border: '1px solid',
120
+ borderColor: 'border.default',
121
+ borderRadius: 2,
122
+ p: 2,
123
+ overflowWrap: 'anywhere',
124
+ fontSize: 0,
125
+ lineHeight: '20px',
126
+ flex: 1,
127
+ maxHeight: 140,
128
+ overflowY: 'auto',
129
+ }, children: apiKey?.value }), _jsx(IconButton, { "aria-label": "Copy API key to clipboard", icon: CopyIcon, size: "small", onClick: () => {
130
+ if (apiKey?.value) {
131
+ navigator.clipboard.writeText(apiKey.value);
132
+ enqueueToast('API key copied to clipboard', {
133
+ variant: 'success',
134
+ });
135
+ }
136
+ } })] })] }), _jsx(Box, { mt: 3, children: _jsx(Button, { onClick: e => navigate(apiKeysListRoute, e), children: "List my API Keys" }) })] })) : (_jsxs(_Fragment, { children: [showTitle ? (_jsx(PageHeader, { children: _jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "New API Key" }) }) })) : null, _jsx(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", sx: { gap: 3 }, children: _jsx(Box, { children: _jsxs(Box, { sx: { label: { marginTop: 2 } }, children: [_jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Type" }), _jsxs(Select, { name: "type", value: formValues.variant, onChange: valueVariantChange, children: [_jsx(Select.Option, { value: "secret", children: "Secret" }), _jsx(Select.Option, { value: "publishable", disabled: true, children: "Publishable (coming soon)" }), _jsx(Select.Option, { value: "restricted", disabled: true, children: "Restricted (coming soon)" }), _jsx(Select.Option, { value: "temporary", disabled: true, children: "Temporary (coming soon)" })] }), _jsx(FormControl.Caption, { children: "Secret is currently available. Additional API key types are coming soon." })] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Name" }), _jsx(TextInput, { block: true, value: formValues.name, onChange: valueNameChange, autoFocus: true }), _jsx(FormControl.Caption, { children: "Hint: The token name is a short name that identifies in a unique way your token." }), validationResult.name === false && (_jsx(FormControl.Validation, { variant: "error", children: "Name length must be between 2 and 32 characters." }))] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Expiration day" }), _jsx(Calendar, { showGoToToday: true, onSelectDate: expirationDateChange, value: formValues.expirationDate, strings: defaultCalendarStrings }), validationResult.expirationDate !== true ? (_jsx(FormControl.Validation, { variant: "error", children: "Pick an expiration date in the future." })) : (_jsxs(FormControl.Validation, { variant: "success", children: ["Expiration date:", ' ', formValues.expirationDate?.toLocaleDateString(), "."] }))] }), _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Description" }), _jsx(Textarea, { block: true, value: formValues.description, onChange: valueDescriptionChange }), validationResult.description === false && (_jsx(FormControl.Validation, { variant: "error", children: "Description must have more than 2 characters." }))] }), _jsx(Button, { variant: "primary", disabled: !validationResult.name ||
137
+ !validationResult.description ||
138
+ !validationResult.expirationDate, sx: { marginTop: 2 }, onClick: e => {
139
+ e.preventDefault();
140
+ submitAPIKey();
141
+ }, children: "Create an API key" })] }) }) })] })) }));
142
+ };
143
+ export default APIKeyNew;
@@ -0,0 +1,12 @@
1
+ export type APIKeysProps = {
2
+ /** Route to navigate when clicking "New API Key" button. Defaults to '/new/api-key'. */
3
+ newAPIKeyRoute?: string;
4
+ /** Base route for the API keys list (used for edit navigation). Defaults to current relative path. */
5
+ apiKeysListRoute?: string;
6
+ /** Whether to display view titles/headings. Defaults to true. */
7
+ showTitle?: boolean;
8
+ /** Whether to render the "New API Key" button in this component header. Defaults to true. */
9
+ showNewButton?: boolean;
10
+ };
11
+ export declare const APIKeys: ({ newAPIKeyRoute, apiKeysListRoute, showTitle, showNewButton, }?: APIKeysProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default APIKeys;
@@ -0,0 +1,119 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useState, useEffect, useRef } from 'react';
7
+ import { PageLayout, Button, IconButton, TextInput, Text, Label, RelativeTime, } from '@primer/react';
8
+ import { Blankslate, Dialog, PageHeader, Table, DataTable, } from '@primer/react/experimental';
9
+ import { Box } from '@datalayer/primer-addons';
10
+ import { EditIcon } from '@datalayer/icons-react';
11
+ import { TrashIcon } from '@primer/octicons-react';
12
+ import { useCache, useNavigate, useToast } from '../../hooks/index.js';
13
+ const APIKeysTable = ({ apiKeysListRoute, showTitle = true, }) => {
14
+ const { useTokens: useAPIKeys, useDeleteToken: useDeleteAPIKey } = useCache();
15
+ const { enqueueToast } = useToast();
16
+ const getAPIKeysQuery = useAPIKeys();
17
+ const deleteAPIKeyMutation = useDeleteAPIKey();
18
+ const navigate = useNavigate();
19
+ const [apiKeys, setApiKeys] = useState([]);
20
+ const [deletingAPIKey, setDeletingAPIKey] = useState(null);
21
+ const [deleteNameConfirm, setDeleteNameConfirm] = useState('');
22
+ const returnFocusRef = useRef(null);
23
+ useEffect(() => {
24
+ if (getAPIKeysQuery.data) {
25
+ const normalized = getAPIKeysQuery.data.filter((apiKey) => Boolean(apiKey));
26
+ setApiKeys(normalized);
27
+ }
28
+ }, [getAPIKeysQuery.data]);
29
+ const confirmDeleteAPIKey = () => {
30
+ if (!deletingAPIKey)
31
+ return;
32
+ if (deleteNameConfirm.trim() !== deletingAPIKey.name) {
33
+ enqueueToast('Please type the API key name exactly to confirm deletion.', {
34
+ variant: 'error',
35
+ });
36
+ return;
37
+ }
38
+ deleteAPIKeyMutation.mutate(deletingAPIKey.id, {
39
+ onSuccess: (resp) => {
40
+ if (resp?.success) {
41
+ enqueueToast(`API key "${deletingAPIKey.name}" deleted.`, {
42
+ variant: 'success',
43
+ });
44
+ }
45
+ else {
46
+ enqueueToast(resp?.message || 'Failed to delete API key.', {
47
+ variant: 'error',
48
+ });
49
+ }
50
+ },
51
+ onError: () => {
52
+ enqueueToast('Failed to delete API key.', { variant: 'error' });
53
+ },
54
+ onSettled: () => {
55
+ setDeletingAPIKey(null);
56
+ setDeleteNameConfirm('');
57
+ },
58
+ });
59
+ };
60
+ return apiKeys.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [showTitle && _jsx(Blankslate.Heading, { children: "API Keys" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No API Keys found." }) })] })) : (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [showTitle && (_jsxs(_Fragment, { children: [_jsx(Table.Title, { as: "h2", id: "api-keys", children: "API Keys" }), _jsx(Table.Subtitle, { as: "p", id: "api-keys-subtitle", children: "Your API keys." })] })), _jsx(DataTable, { "aria-labelledby": "api-keys", "aria-describedby": "api-keys-subtitle", data: apiKeys, columns: [
61
+ {
62
+ header: 'Type',
63
+ field: 'variant',
64
+ renderCell: apiKey => _jsx(Label, { children: apiKey.variant }),
65
+ },
66
+ {
67
+ header: 'Name',
68
+ field: 'name',
69
+ rowHeader: true,
70
+ },
71
+ {
72
+ header: 'Description',
73
+ field: 'description',
74
+ },
75
+ {
76
+ header: 'Expiration date',
77
+ field: 'expirationDate',
78
+ renderCell: apiKey => (_jsx(RelativeTime, { date: new Date(apiKey.expirationDate) })),
79
+ },
80
+ {
81
+ header: '',
82
+ field: 'id',
83
+ renderCell: apiKey => (_jsxs(Box, { display: "flex", sx: { gap: 1 }, children: [_jsx(IconButton, { icon: EditIcon, "aria-label": "Edit", size: "small", variant: "invisible", onClick: e => navigate(apiKeysListRoute
84
+ ? `${apiKeysListRoute}/${apiKey.id}`
85
+ : `${apiKey.id}`, e) }), _jsx(IconButton, { ref: returnFocusRef, icon: TrashIcon, "aria-label": "Delete", size: "small", variant: "invisible", sx: { color: 'danger.fg' }, onClick: () => {
86
+ setDeletingAPIKey(apiKey);
87
+ setDeleteNameConfirm('');
88
+ } })] })),
89
+ },
90
+ ] })] }), deletingAPIKey && (_jsxs(Dialog, { title: "Delete API key", onClose: () => {
91
+ setDeletingAPIKey(null);
92
+ setDeleteNameConfirm('');
93
+ }, footerButtons: [
94
+ {
95
+ buttonType: 'default',
96
+ content: 'Cancel',
97
+ onClick: () => {
98
+ setDeletingAPIKey(null);
99
+ setDeleteNameConfirm('');
100
+ },
101
+ },
102
+ {
103
+ buttonType: 'danger',
104
+ content: 'Delete',
105
+ disabled: deleteNameConfirm.trim() !== deletingAPIKey.name,
106
+ onClick: event => {
107
+ if (!event.defaultPrevented) {
108
+ event.preventDefault();
109
+ confirmDeleteAPIKey();
110
+ }
111
+ },
112
+ },
113
+ ], children: ["Are you sure you want to delete the API key", ' ', _jsx("strong", { children: deletingAPIKey.name }), "? This action cannot be undone.", _jsxs(Text, { sx: { mt: 3, display: 'block', color: 'fg.muted' }, children: ["Type ", _jsx("strong", { children: deletingAPIKey.name }), " to confirm deletion."] }), _jsx(TextInput, { block: true, value: deleteNameConfirm, onChange: e => setDeleteNameConfirm(e.target.value), placeholder: "Retype API key name", sx: { mt: 2 }, autoFocus: true })] }))] }));
114
+ };
115
+ export const APIKeys = ({ newAPIKeyRoute = '/new/api-key', apiKeysListRoute, showTitle = true, showNewButton = true, } = {}) => {
116
+ const navigate = useNavigate();
117
+ return (_jsxs(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [showTitle || showNewButton ? (_jsx(PageLayout.Header, { children: _jsxs(PageHeader, { children: [showTitle && (_jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "API Keys" }) })), showNewButton && (_jsx(PageHeader.Actions, { children: _jsx(Button, { size: "small", variant: "primary", onClick: e => navigate(newAPIKeyRoute, e), children: "New API Key" }) }))] }) })) : null, _jsx(PageLayout.Content, { children: _jsx(Box, { children: _jsx(APIKeysTable, { apiKeysListRoute: apiKeysListRoute, showTitle: showTitle }) }) })] }));
118
+ };
119
+ export default APIKeys;
@@ -0,0 +1,2 @@
1
+ export declare const APIKeysStandalone: () => import("react/jsx-runtime").JSX.Element;
2
+ export default APIKeysStandalone;
@@ -0,0 +1,117 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useState, useEffect, useRef } from 'react';
7
+ import { PageLayout, Button, IconButton, TextInput, Text, Label, RelativeTime, } from '@primer/react';
8
+ import { Blankslate, Dialog, PageHeader, Table, DataTable, } from '@primer/react/experimental';
9
+ import { Box } from '@datalayer/primer-addons';
10
+ import { EditIcon } from '@datalayer/icons-react';
11
+ import { TrashIcon } from '@primer/octicons-react';
12
+ import { useCache, useNavigate, useToast } from '../../hooks/index.js';
13
+ const APIKeysTable = () => {
14
+ const { useTokens: useAPIKeys, useDeleteToken: useDeleteAPIKey } = useCache();
15
+ const { enqueueToast } = useToast();
16
+ const getAPIKeysQuery = useAPIKeys();
17
+ const deleteAPIKeyMutation = useDeleteAPIKey();
18
+ const navigate = useNavigate();
19
+ const [apiKeys, setApiKeys] = useState([]);
20
+ const [deletingAPIKey, setDeletingAPIKey] = useState(null);
21
+ const [deleteNameConfirm, setDeleteNameConfirm] = useState('');
22
+ const returnFocusRef = useRef(null);
23
+ useEffect(() => {
24
+ if (getAPIKeysQuery.data) {
25
+ const normalized = getAPIKeysQuery.data.filter((apiKey) => Boolean(apiKey));
26
+ setApiKeys(normalized);
27
+ }
28
+ }, [getAPIKeysQuery.data]);
29
+ const confirmDeleteAPIKey = () => {
30
+ if (!deletingAPIKey)
31
+ return;
32
+ if (deleteNameConfirm.trim() !== deletingAPIKey.name) {
33
+ enqueueToast('Please type the API key name exactly to confirm deletion.', {
34
+ variant: 'error',
35
+ });
36
+ return;
37
+ }
38
+ deleteAPIKeyMutation.mutate(deletingAPIKey.id, {
39
+ onSuccess: (resp) => {
40
+ if (resp?.success) {
41
+ enqueueToast(`API key "${deletingAPIKey.name}" deleted.`, {
42
+ variant: 'success',
43
+ });
44
+ }
45
+ else {
46
+ enqueueToast(resp?.message || 'Failed to delete API key.', {
47
+ variant: 'error',
48
+ });
49
+ }
50
+ },
51
+ onError: () => {
52
+ enqueueToast('Failed to delete API key.', { variant: 'error' });
53
+ },
54
+ onSettled: () => {
55
+ setDeletingAPIKey(null);
56
+ setDeleteNameConfirm('');
57
+ },
58
+ });
59
+ };
60
+ return apiKeys.length === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Heading, { children: "API Keys" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "No API Keys found." }) })] })) : (_jsxs(_Fragment, { children: [_jsxs(Table.Container, { children: [_jsx(Table.Title, { as: "h2", id: "api-keys", children: "API Keys" }), _jsx(Table.Subtitle, { as: "p", id: "api-keys-subtitle", children: "Your API keys." }), _jsx(DataTable, { "aria-labelledby": "api-keys", "aria-describedby": "api-keys-subtitle", data: apiKeys, columns: [
61
+ {
62
+ header: 'Type',
63
+ field: 'variant',
64
+ renderCell: apiKey => _jsx(Label, { children: apiKey.variant }),
65
+ },
66
+ {
67
+ header: 'Name',
68
+ field: 'name',
69
+ rowHeader: true,
70
+ },
71
+ {
72
+ header: 'Description',
73
+ field: 'description',
74
+ },
75
+ {
76
+ header: 'Expiration date',
77
+ field: 'expirationDate',
78
+ renderCell: apiKey => (_jsx(RelativeTime, { date: new Date(apiKey.expirationDate) })),
79
+ },
80
+ {
81
+ header: '',
82
+ field: 'id',
83
+ renderCell: apiKey => (_jsxs(Box, { display: "flex", sx: { gap: 1 }, children: [_jsx(IconButton, { icon: EditIcon, "aria-label": "Edit", size: "small", variant: "invisible", onClick: e => navigate(`${apiKey.id}`, e) }), _jsx(IconButton, { ref: returnFocusRef, icon: TrashIcon, "aria-label": "Delete", size: "small", variant: "invisible", sx: { color: 'danger.fg' }, onClick: () => {
84
+ setDeletingAPIKey(apiKey);
85
+ setDeleteNameConfirm('');
86
+ } })] })),
87
+ },
88
+ ] })] }), deletingAPIKey && (_jsxs(Dialog, { title: "Delete API key", onClose: () => {
89
+ setDeletingAPIKey(null);
90
+ setDeleteNameConfirm('');
91
+ }, footerButtons: [
92
+ {
93
+ buttonType: 'default',
94
+ content: 'Cancel',
95
+ onClick: () => {
96
+ setDeletingAPIKey(null);
97
+ setDeleteNameConfirm('');
98
+ },
99
+ },
100
+ {
101
+ buttonType: 'danger',
102
+ content: 'Delete',
103
+ disabled: deleteNameConfirm.trim() !== deletingAPIKey.name,
104
+ onClick: event => {
105
+ if (!event.defaultPrevented) {
106
+ event.preventDefault();
107
+ confirmDeleteAPIKey();
108
+ }
109
+ },
110
+ },
111
+ ], children: ["Are you sure you want to delete the API key", ' ', _jsx("strong", { children: deletingAPIKey.name }), "? This action cannot be undone.", _jsxs(Text, { sx: { mt: 3, display: 'block', color: 'fg.muted' }, children: ["Type ", _jsx("strong", { children: deletingAPIKey.name }), " to confirm deletion."] }), _jsx(TextInput, { block: true, value: deleteNameConfirm, onChange: e => setDeleteNameConfirm(e.target.value), placeholder: "Retype API key name", sx: { mt: 2 }, autoFocus: true })] }))] }));
112
+ };
113
+ export const APIKeysStandalone = () => {
114
+ const navigate = useNavigate();
115
+ return (_jsxs(PageLayout, { containerWidth: "full", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsxs(PageHeader, { children: [_jsx(PageHeader.TitleArea, { variant: "large", children: _jsx(PageHeader.Title, { children: "API Keys" }) }), _jsx(PageHeader.Actions, { children: _jsx(Button, { size: "small", variant: "primary", onClick: e => navigate('/api-keys/new', e), children: "New API key" }) })] }) }), _jsx(PageLayout.Content, { children: _jsx(Box, { children: _jsx(APIKeysTable, {}) }) })] }));
116
+ };
117
+ export default APIKeysStandalone;
@@ -0,0 +1,3 @@
1
+ export * from './APIKeyEdit';
2
+ export * from './APIKeyNew';
3
+ export * from './APIKeys';
@@ -0,0 +1,7 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from './APIKeyEdit.js';
6
+ export * from './APIKeyNew.js';
7
+ export * from './APIKeys.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/core",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -1,6 +0,0 @@
1
- export type IAMTokenEditProps = {
2
- /** Route to navigate after delete. Defaults to '/settings/iam/tokens'. */
3
- tokensListRoute?: string;
4
- };
5
- export declare const IAMTokenEdit: ({ tokensListRoute, }?: IAMTokenEditProps) => import("react/jsx-runtime").JSX.Element;
6
- export default IAMTokenEdit;
@@ -1,8 +0,0 @@
1
- export type IAMTokenNewProps = {
2
- /** Route to navigate when clicking "List my Tokens". Defaults to '/settings/iam/tokens'. */
3
- tokensListRoute?: string;
4
- /** Whether to render the "New API Key" title header in create mode. Defaults to true. */
5
- showTitle?: boolean;
6
- };
7
- export declare const IAMTokenNew: ({ tokensListRoute, showTitle, }?: IAMTokenNewProps) => import("react/jsx-runtime").JSX.Element;
8
- export default IAMTokenNew;