@datalayer/core 1.0.24 → 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.
- package/README.md +25 -2
- package/lib/api/constants.d.ts +3 -0
- package/lib/api/constants.js +3 -0
- package/lib/api/index.d.ts +1 -0
- package/lib/api/index.js +1 -0
- package/lib/api/runtimes/checkpoints.d.ts +4 -4
- package/lib/api/runtimes/runtimes.d.ts +3 -3
- package/lib/api/scheduler/index.d.ts +6 -0
- package/lib/api/scheduler/index.js +10 -0
- package/lib/api/scheduler/schedules.d.ts +85 -0
- package/lib/api/scheduler/schedules.js +78 -0
- package/lib/components/checkout/StripeCheckout.js +48 -2
- package/lib/components/display/LiveRelativeTime.d.ts +1 -10
- package/lib/components/display/LiveRelativeTime.js +3 -18
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.js +3 -0
- package/lib/components/scheduler/ScheduleMenu.d.ts +54 -0
- package/lib/components/scheduler/ScheduleMenu.js +154 -0
- package/lib/components/scheduler/index.d.ts +1 -0
- package/lib/{views/iam-tokens → components/scheduler}/index.js +1 -3
- package/lib/components/time/LiveRelativeTime.d.ts +14 -0
- package/lib/components/time/LiveRelativeTime.js +39 -0
- package/lib/components/time/index.d.ts +1 -0
- package/lib/components/time/index.js +5 -0
- package/lib/components/timeline/Timeline.d.ts +20 -0
- package/lib/components/timeline/Timeline.js +90 -0
- package/lib/components/timeline/index.d.ts +1 -0
- package/lib/components/timeline/index.js +5 -0
- package/lib/hooks/useCache.d.ts +1 -1
- package/lib/hooks/useCache.js +17 -13
- package/lib/hooks/useProjectStore.d.ts +1 -1
- package/lib/hooks/useProjects.d.ts +2 -2
- package/lib/models/IAMToken.d.ts +1 -1
- package/lib/models/ProjectDTO.d.ts +1 -1
- package/lib/models/ProjectDTO.js +1 -1
- package/lib/state/substates/CoreState.js +1 -1
- package/lib/utils/Date.d.ts +7 -0
- package/lib/utils/Date.js +43 -10
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/views/api-keys/APIKeyEdit.d.ts +8 -0
- package/lib/views/{iam-tokens/IAMTokenEdit.js → api-keys/APIKeyEdit.js} +31 -31
- package/lib/views/api-keys/APIKeyNew.d.ts +8 -0
- package/lib/views/api-keys/APIKeyNew.js +143 -0
- package/lib/views/api-keys/APIKeys.d.ts +12 -0
- package/lib/views/api-keys/APIKeys.js +119 -0
- package/lib/views/api-keys/APIKeysStandalone.d.ts +2 -0
- package/lib/views/api-keys/APIKeysStandalone.js +117 -0
- package/lib/views/api-keys/index.d.ts +3 -0
- package/lib/views/api-keys/index.js +7 -0
- package/package.json +1 -1
- package/lib/views/iam-tokens/IAMTokenEdit.d.ts +0 -6
- package/lib/views/iam-tokens/IAMTokenNew.d.ts +0 -8
- package/lib/views/iam-tokens/IAMTokenNew.js +0 -118
- package/lib/views/iam-tokens/IAMTokens.d.ts +0 -12
- package/lib/views/iam-tokens/IAMTokens.js +0 -87
- package/lib/views/iam-tokens/Tokens.d.ts +0 -2
- package/lib/views/iam-tokens/Tokens.js +0 -85
- package/lib/views/iam-tokens/index.d.ts +0 -3
- /package/lib/utils/{logger.d.ts → Logger.d.ts} +0 -0
- /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
|
|
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
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const [
|
|
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:
|
|
24
|
+
name: apiKey?.name,
|
|
25
25
|
nameConfirm: '',
|
|
26
|
-
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 (
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
setFormValues({ ...
|
|
34
|
+
if (getAPIKeyQuery.data) {
|
|
35
|
+
const key = getAPIKeyQuery.data;
|
|
36
|
+
setAPIKey(key);
|
|
37
|
+
setFormValues({ ...key, nameConfirm: '' });
|
|
38
38
|
}
|
|
39
|
-
}, [
|
|
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 ===
|
|
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
|
|
74
|
+
const submitAPIKey = async () => {
|
|
75
75
|
runStore.layout().showBackdrop();
|
|
76
|
-
const
|
|
77
|
-
...
|
|
76
|
+
const updatedAPIKey = {
|
|
77
|
+
...apiKey,
|
|
78
78
|
name: formValues.name,
|
|
79
79
|
description: formValues.description,
|
|
80
80
|
};
|
|
81
|
-
|
|
81
|
+
updateAPIKeyMutation.mutate(updatedAPIKey, {
|
|
82
82
|
onSuccess: (resp) => {
|
|
83
83
|
if (resp.success) {
|
|
84
|
-
enqueueToast('The
|
|
84
|
+
enqueueToast('The API key was updated successfully.', {
|
|
85
85
|
variant: 'success',
|
|
86
86
|
});
|
|
87
|
-
|
|
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
|
|
96
|
-
runStore.layout().showBackdrop('Deleting the
|
|
97
|
-
|
|
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
|
|
100
|
+
enqueueToast('The API key was deleted successfully.', {
|
|
101
101
|
variant: 'success',
|
|
102
102
|
});
|
|
103
|
-
navigate(
|
|
103
|
+
navigate(apiKeysListRoute);
|
|
104
104
|
}
|
|
105
105
|
else {
|
|
106
|
-
enqueueToast(resp.message || 'Failed to delete
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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,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;
|
package/package.json
CHANGED
|
@@ -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;
|