@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
|
@@ -0,0 +1,154 @@
|
|
|
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, useMemo, useState } from 'react';
|
|
7
|
+
import { ActionList, ActionMenu, Box, Button, Dialog, FormControl, IconButton, Spinner, Text, TextInput, } from '@primer/react';
|
|
8
|
+
import { ClockIcon, TrashIcon } from '@primer/octicons-react';
|
|
9
|
+
import { disableSchedule, updateSchedule, upsertSchedule, } from '../../api/scheduler/schedules.js';
|
|
10
|
+
import { DEFAULT_SERVICE_URLS } from '../../api/constants.js';
|
|
11
|
+
const SCHEDULE_PRESETS = [
|
|
12
|
+
{ id: 'every-minute', label: 'Every minute', cron: '* * * * *' },
|
|
13
|
+
{ id: 'hourly', label: 'Every hour', cron: '0 * * * *' },
|
|
14
|
+
{ id: 'daily', label: 'Every day', cron: '0 0 * * *' },
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_CRON_EXPRESSION = '* * * * *';
|
|
17
|
+
const presetFromCron = (cronExpression) => {
|
|
18
|
+
const normalized = cronExpression.trim();
|
|
19
|
+
const preset = SCHEDULE_PRESETS.find(item => item.cron === normalized);
|
|
20
|
+
return preset ? preset.id : 'custom';
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Self-contained schedule menu that lets a user pick a cron schedule for a
|
|
24
|
+
* notebook and persists it to the scheduler service.
|
|
25
|
+
*
|
|
26
|
+
* The component owns its loading state and renders a spinner in place of the
|
|
27
|
+
* anchor icon while a save request is in flight. The scheduler service URL is
|
|
28
|
+
* configurable through the `baseUrl` prop.
|
|
29
|
+
*/
|
|
30
|
+
export const ScheduleMenu = ({ notebookUid, scheduleUid, token, baseUrl = DEFAULT_SERVICE_URLS.SCHEDULER, enabled, icon = ClockIcon, iconColor, ariaLabel = 'Schedule execution', initialPreset = 'every-minute', initialCronExpression, onSaved, onDeleted, onError, }) => {
|
|
31
|
+
const providedInitialCron = (initialCronExpression || '').trim();
|
|
32
|
+
const hasProvidedInitialCron = providedInitialCron.length > 0;
|
|
33
|
+
const initialCron = hasProvidedInitialCron
|
|
34
|
+
? providedInitialCron
|
|
35
|
+
: DEFAULT_CRON_EXPRESSION;
|
|
36
|
+
const [cronExpression, setCronExpression] = useState(initialCron);
|
|
37
|
+
const [hasExplicitCron, setHasExplicitCron] = useState(hasProvidedInitialCron);
|
|
38
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
39
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
40
|
+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
41
|
+
const [deleteConfirmUid, setDeleteConfirmUid] = useState('');
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const nextProvidedCron = (initialCronExpression || '').trim();
|
|
44
|
+
const nextHasExplicitCron = nextProvidedCron.length > 0;
|
|
45
|
+
setHasExplicitCron(nextHasExplicitCron);
|
|
46
|
+
setCronExpression(nextHasExplicitCron ? nextProvidedCron : DEFAULT_CRON_EXPRESSION);
|
|
47
|
+
}, [initialCronExpression, scheduleUid, notebookUid]);
|
|
48
|
+
const preset = useMemo(() => (hasExplicitCron ? presetFromCron(cronExpression) : 'custom'), [cronExpression, hasExplicitCron]);
|
|
49
|
+
const isEnabled = (enabled ?? true) &&
|
|
50
|
+
Boolean(token) &&
|
|
51
|
+
Boolean(notebookUid || scheduleUid) &&
|
|
52
|
+
!isSaving &&
|
|
53
|
+
!isDeleting;
|
|
54
|
+
const selectedPresetDescription = useMemo(() => {
|
|
55
|
+
if (!hasExplicitCron) {
|
|
56
|
+
return 'Not scheduled';
|
|
57
|
+
}
|
|
58
|
+
const selected = SCHEDULE_PRESETS.find(item => item.cron === cronExpression.trim());
|
|
59
|
+
if (selected) {
|
|
60
|
+
return `${selected.label} (${selected.cron})`;
|
|
61
|
+
}
|
|
62
|
+
return `Custom (${cronExpression || 'Cron expression not set'})`;
|
|
63
|
+
}, [cronExpression, hasExplicitCron]);
|
|
64
|
+
const saveSchedule = async (nextPreset, nextCron) => {
|
|
65
|
+
const cron = nextCron.trim();
|
|
66
|
+
if (!token || (!notebookUid && !scheduleUid) || !cron) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setIsSaving(true);
|
|
70
|
+
try {
|
|
71
|
+
let schedule;
|
|
72
|
+
if (scheduleUid) {
|
|
73
|
+
const response = await updateSchedule(token, scheduleUid, {
|
|
74
|
+
cronExpression: cron,
|
|
75
|
+
preset: nextPreset,
|
|
76
|
+
}, baseUrl);
|
|
77
|
+
schedule = response.schedule;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const response = await upsertSchedule(token, {
|
|
81
|
+
notebookUid: notebookUid,
|
|
82
|
+
cronExpression: cron,
|
|
83
|
+
preset: nextPreset,
|
|
84
|
+
enabled: true,
|
|
85
|
+
}, baseUrl);
|
|
86
|
+
schedule = response.schedule;
|
|
87
|
+
}
|
|
88
|
+
onSaved?.(schedule);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
onError?.(error);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
setIsSaving(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const selectPreset = (nextPreset) => {
|
|
98
|
+
const next = SCHEDULE_PRESETS.find(item => item.id === nextPreset);
|
|
99
|
+
if (!next) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
setHasExplicitCron(true);
|
|
103
|
+
setCronExpression(next.cron);
|
|
104
|
+
void saveSchedule(nextPreset, next.cron);
|
|
105
|
+
};
|
|
106
|
+
const applyCustomCron = () => {
|
|
107
|
+
const value = cronExpression.trim();
|
|
108
|
+
const inferredPreset = presetFromCron(value);
|
|
109
|
+
void saveSchedule(inferredPreset, value);
|
|
110
|
+
};
|
|
111
|
+
const performDeleteSchedule = async () => {
|
|
112
|
+
if (!token || !scheduleUid) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setIsDeleting(true);
|
|
116
|
+
try {
|
|
117
|
+
const response = await disableSchedule(token, scheduleUid, baseUrl);
|
|
118
|
+
setIsDeleteDialogOpen(false);
|
|
119
|
+
setDeleteConfirmUid('');
|
|
120
|
+
onDeleted?.(response.schedule);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
onError?.(error);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
setIsDeleting(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
return (_jsxs(_Fragment, { children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(IconButton, { icon: isSaving ? () => _jsx(Spinner, { size: "small" }) : icon, variant: "invisible", sx: iconColor ? { color: iconColor } : undefined, "aria-label": ariaLabel, title: isSaving
|
|
130
|
+
? `${ariaLabel} (saving…)`
|
|
131
|
+
: isEnabled
|
|
132
|
+
? ariaLabel
|
|
133
|
+
: `${ariaLabel} (disabled)` }) }), _jsxs(ActionMenu.Overlay, { width: "medium", sx: { minWidth: 300 }, children: [_jsx(Box, { sx: {
|
|
134
|
+
px: 3,
|
|
135
|
+
pt: 3,
|
|
136
|
+
pb: 2,
|
|
137
|
+
borderBottom: '1px solid',
|
|
138
|
+
borderColor: 'border.default',
|
|
139
|
+
}, children: _jsx(Text, { as: "p", sx: { fontWeight: 600 }, children: "Schedule" }) }), _jsx(ActionList, { selectionVariant: "single", showDividers: true, children: SCHEDULE_PRESETS.map(option => (_jsxs(ActionList.Item, { selected: preset === option.id, disabled: !isEnabled, onSelect: () => selectPreset(option.id), children: [option.label, _jsx(ActionList.Description, { variant: "block", children: option.cron })] }, option.id))) }), _jsxs(Box, { sx: { p: 3, borderTop: '1px solid', borderColor: 'border.default' }, children: [_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 2 }, children: "Cron Expression" }), _jsx(TextInput, { value: cronExpression, onChange: event => {
|
|
140
|
+
const value = event.currentTarget.value;
|
|
141
|
+
setHasExplicitCron(value.trim().length > 0);
|
|
142
|
+
setCronExpression(value);
|
|
143
|
+
}, placeholder: "* * * * *", "aria-label": "Cron expression", block: true, disabled: !isEnabled }), _jsxs(Box, { sx: { mt: 2, display: 'flex', gap: 2, flexWrap: 'wrap' }, children: [_jsx(Button, { size: "small", onClick: applyCustomCron, disabled: !isEnabled, children: "Apply Cron" }), scheduleUid ? (_jsx(Button, { size: "small", variant: "danger", leadingVisual: TrashIcon, onClick: () => setIsDeleteDialogOpen(true), disabled: !isEnabled, children: "Delete Schedule" })) : null] }), _jsxs(Text, { as: "p", sx: { mt: 2, fontSize: 0, color: 'fg.muted' }, children: ["Current: ", selectedPresetDescription] })] })] })] }), isDeleteDialogOpen && scheduleUid ? (_jsx(Dialog, { title: "Delete schedule", onClose: () => {
|
|
144
|
+
if (isDeleting) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
setIsDeleteDialogOpen(false);
|
|
148
|
+
setDeleteConfirmUid('');
|
|
149
|
+
}, width: "medium", children: _jsxs(Box, { sx: { display: 'grid', gap: 3 }, children: [_jsxs(Text, { children: ["This action will disable the schedule and remove planned runs. Type", ' ', _jsx(Text, { as: "span", sx: { fontWeight: 700 }, children: scheduleUid }), ' ', "to confirm."] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Schedule UID" }), _jsx(TextInput, { value: deleteConfirmUid, onChange: event => setDeleteConfirmUid(event.currentTarget.value), placeholder: scheduleUid, autoFocus: true })] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', gap: 2 }, children: [_jsx(Button, { variant: "default", onClick: () => {
|
|
150
|
+
setIsDeleteDialogOpen(false);
|
|
151
|
+
setDeleteConfirmUid('');
|
|
152
|
+
}, disabled: isDeleting, children: "Cancel" }), _jsx(Button, { variant: "danger", onClick: () => void performDeleteSchedule(), disabled: isDeleting || deleteConfirmUid !== scheduleUid, children: "Delete schedule" })] })] }) })) : null] }));
|
|
153
|
+
};
|
|
154
|
+
export default ScheduleMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ScheduleMenu';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ILiveRelativeTimeProps = {
|
|
2
|
+
value?: Date | string | number;
|
|
3
|
+
refreshIntervalMs?: number;
|
|
4
|
+
fallback?: string;
|
|
5
|
+
showTooltip?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Display a live-updating relative time label (e.g. "5m ago", "in 2m").
|
|
9
|
+
*
|
|
10
|
+
* When `showTooltip` is true, a Primer tooltip shows ISO + localized datetime
|
|
11
|
+
* details with timezone information.
|
|
12
|
+
*/
|
|
13
|
+
export declare function LiveRelativeTime({ value, refreshIntervalMs, fallback, showTooltip, }: ILiveRelativeTimeProps): JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { Tooltip } from '@primer/react';
|
|
8
|
+
import { formatDateTimeDetails, formatRelativeTime } from '../../utils/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Display a live-updating relative time label (e.g. "5m ago", "in 2m").
|
|
11
|
+
*
|
|
12
|
+
* When `showTooltip` is true, a Primer tooltip shows ISO + localized datetime
|
|
13
|
+
* details with timezone information.
|
|
14
|
+
*/
|
|
15
|
+
export function LiveRelativeTime({ value, refreshIntervalMs = 30_000, fallback = '—', showTooltip = true, }) {
|
|
16
|
+
const [now, setNow] = useState(() => new Date());
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const timer = window.setInterval(() => {
|
|
19
|
+
setNow(new Date());
|
|
20
|
+
}, refreshIntervalMs);
|
|
21
|
+
return () => window.clearInterval(timer);
|
|
22
|
+
}, [refreshIntervalMs]);
|
|
23
|
+
const label = useMemo(() => formatRelativeTime(value, now), [value, now]);
|
|
24
|
+
const tooltip = useMemo(() => formatDateTimeDetails(value), [value]);
|
|
25
|
+
const content = (_jsx("button", { type: "button", "aria-label": tooltip || 'Date and time details', style: {
|
|
26
|
+
border: 'none',
|
|
27
|
+
background: 'transparent',
|
|
28
|
+
padding: 0,
|
|
29
|
+
margin: 0,
|
|
30
|
+
font: 'inherit',
|
|
31
|
+
color: 'inherit',
|
|
32
|
+
cursor: 'inherit',
|
|
33
|
+
lineHeight: 'inherit',
|
|
34
|
+
}, children: label ?? fallback }));
|
|
35
|
+
if (!showTooltip || !tooltip) {
|
|
36
|
+
return content;
|
|
37
|
+
}
|
|
38
|
+
return _jsx(Tooltip, { text: tooltip, children: content });
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './LiveRelativeTime';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type TimelineStatus = 'done' | 'current' | 'pending' | 'failed' | 'neutral';
|
|
3
|
+
export type TimelineItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
timestamp?: string;
|
|
7
|
+
status?: TimelineStatus;
|
|
8
|
+
subtitle?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export type TimelineProps = {
|
|
11
|
+
items: TimelineItem[];
|
|
12
|
+
renderTimestamp?: (value?: string) => ReactNode;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Horizontal metro-style timeline with connected markers and labels.
|
|
16
|
+
*
|
|
17
|
+
* The first item is rendered on the left. Pass items in the desired order
|
|
18
|
+
* (for example: newest to oldest).
|
|
19
|
+
*/
|
|
20
|
+
export declare const Timeline: ({ items, renderTimestamp }: TimelineProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from '@primer/react';
|
|
3
|
+
const statusStyles = {
|
|
4
|
+
done: {
|
|
5
|
+
dotBackground: 'success.emphasis',
|
|
6
|
+
dotBorder: 'success.emphasis',
|
|
7
|
+
lineColor: 'success.muted',
|
|
8
|
+
titleColor: 'fg.default',
|
|
9
|
+
},
|
|
10
|
+
current: {
|
|
11
|
+
dotBackground: 'attention.emphasis',
|
|
12
|
+
dotBorder: 'attention.emphasis',
|
|
13
|
+
lineColor: 'attention.muted',
|
|
14
|
+
titleColor: 'fg.default',
|
|
15
|
+
},
|
|
16
|
+
pending: {
|
|
17
|
+
dotBackground: 'canvas.default',
|
|
18
|
+
dotBorder: 'border.default',
|
|
19
|
+
lineColor: 'border.default',
|
|
20
|
+
titleColor: 'fg.muted',
|
|
21
|
+
},
|
|
22
|
+
failed: {
|
|
23
|
+
dotBackground: 'danger.emphasis',
|
|
24
|
+
dotBorder: 'danger.emphasis',
|
|
25
|
+
lineColor: 'danger.muted',
|
|
26
|
+
titleColor: 'danger.fg',
|
|
27
|
+
},
|
|
28
|
+
neutral: {
|
|
29
|
+
dotBackground: 'accent.emphasis',
|
|
30
|
+
dotBorder: 'accent.emphasis',
|
|
31
|
+
lineColor: 'accent.muted',
|
|
32
|
+
titleColor: 'fg.default',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Horizontal metro-style timeline with connected markers and labels.
|
|
37
|
+
*
|
|
38
|
+
* The first item is rendered on the left. Pass items in the desired order
|
|
39
|
+
* (for example: newest to oldest).
|
|
40
|
+
*/
|
|
41
|
+
export const Timeline = ({ items, renderTimestamp }) => {
|
|
42
|
+
if (!items.length) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(Box, { sx: {
|
|
46
|
+
overflowX: 'auto',
|
|
47
|
+
pb: 1,
|
|
48
|
+
}, children: _jsx(Box, { sx: {
|
|
49
|
+
display: 'flex',
|
|
50
|
+
alignItems: 'stretch',
|
|
51
|
+
minWidth: 'max-content',
|
|
52
|
+
}, children: items.map((item, index) => {
|
|
53
|
+
const status = item.status || 'neutral';
|
|
54
|
+
const style = statusStyles[status];
|
|
55
|
+
const hasNext = index < items.length - 1;
|
|
56
|
+
return (_jsxs(Box, { sx: {
|
|
57
|
+
display: 'flex',
|
|
58
|
+
flexDirection: 'column',
|
|
59
|
+
minWidth: 220,
|
|
60
|
+
flex: hasNext ? '0 0 220px' : '0 0 auto',
|
|
61
|
+
pr: hasNext ? 0 : 1,
|
|
62
|
+
}, children: [_jsxs(Box, { sx: {
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
minHeight: 28,
|
|
66
|
+
mb: 2,
|
|
67
|
+
}, children: [_jsx(Box, { sx: {
|
|
68
|
+
width: 16,
|
|
69
|
+
height: 16,
|
|
70
|
+
borderRadius: '50%',
|
|
71
|
+
border: '2px solid',
|
|
72
|
+
borderColor: style.dotBorder,
|
|
73
|
+
bg: style.dotBackground,
|
|
74
|
+
boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.2)',
|
|
75
|
+
} }), hasNext ? (_jsx(Box, { sx: {
|
|
76
|
+
flex: 1,
|
|
77
|
+
height: 4,
|
|
78
|
+
ml: 2,
|
|
79
|
+
mr: 2,
|
|
80
|
+
borderRadius: 999,
|
|
81
|
+
bg: style.lineColor,
|
|
82
|
+
} })) : null] }), _jsx(Text, { as: "p", sx: { fontWeight: 600, color: style.titleColor, mb: 1 }, children: item.title }), _jsx(Text, { as: "p", sx: {
|
|
83
|
+
fontSize: 0,
|
|
84
|
+
color: 'fg.muted',
|
|
85
|
+
mb: item.subtitle ? 1 : 0,
|
|
86
|
+
}, children: renderTimestamp
|
|
87
|
+
? renderTimestamp(item.timestamp)
|
|
88
|
+
: item.timestamp || 'n/a' }), item.subtitle ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.subtle' }, children: item.subtitle })) : null] }, item.id));
|
|
89
|
+
}) }) }));
|
|
90
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Timeline';
|
package/lib/hooks/useCache.d.ts
CHANGED
|
@@ -758,7 +758,7 @@ export declare const useCache: ({ loginRoute }?: CacheProps) => {
|
|
|
758
758
|
principalKind?: "user" | "organization" | "team";
|
|
759
759
|
}) => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
|
|
760
760
|
useToken: (tokenId: string) => import("@tanstack/react-query").UseQueryResult<IIAMToken | null | undefined, Error>;
|
|
761
|
-
useTokens: () => import("@tanstack/react-query").UseQueryResult<
|
|
761
|
+
useTokens: () => import("@tanstack/react-query").UseQueryResult<(IIAMToken | undefined)[], Error>;
|
|
762
762
|
useCreateToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, Omit<IIAMToken, "id" | "value">, unknown>;
|
|
763
763
|
useUpdateToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, IIAMToken, unknown>;
|
|
764
764
|
useDeleteToken: () => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
|
package/lib/hooks/useCache.js
CHANGED
|
@@ -2166,11 +2166,12 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2166
2166
|
queryKey: queryKeys.tokens.all(),
|
|
2167
2167
|
queryFn: async () => {
|
|
2168
2168
|
const resp = await requestDatalayer({
|
|
2169
|
-
url: `${configuration.iamRunUrl}/api/iam/v1/
|
|
2169
|
+
url: `${configuration.iamRunUrl}/api/iam/v1/api-keys`,
|
|
2170
2170
|
method: 'GET',
|
|
2171
2171
|
});
|
|
2172
|
-
|
|
2173
|
-
|
|
2172
|
+
const tokenItems = asArray(resp?.apiKeys ?? resp?.api_keys ?? resp?.tokens);
|
|
2173
|
+
if (resp.success && tokenItems.length > 0) {
|
|
2174
|
+
const tokens = tokenItems
|
|
2174
2175
|
.map((t) => {
|
|
2175
2176
|
const token = toToken(t);
|
|
2176
2177
|
if (token) {
|
|
@@ -2194,7 +2195,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2194
2195
|
return useMutation({
|
|
2195
2196
|
mutationFn: async (token) => {
|
|
2196
2197
|
const resp = await requestDatalayer({
|
|
2197
|
-
url: `${configuration.iamRunUrl}/api/iam/v1/
|
|
2198
|
+
url: `${configuration.iamRunUrl}/api/iam/v1/api-keys`,
|
|
2198
2199
|
method: 'POST',
|
|
2199
2200
|
body: {
|
|
2200
2201
|
...token,
|
|
@@ -2202,8 +2203,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2202
2203
|
},
|
|
2203
2204
|
});
|
|
2204
2205
|
// Transform the token in the response
|
|
2205
|
-
|
|
2206
|
-
|
|
2206
|
+
const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
|
|
2207
|
+
if (resp.success && tokenPayload) {
|
|
2208
|
+
resp.token = toToken(tokenPayload);
|
|
2207
2209
|
}
|
|
2208
2210
|
return resp;
|
|
2209
2211
|
},
|
|
@@ -2515,11 +2517,12 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2515
2517
|
queryKey: queryKeys.tokens.detail(tokenId),
|
|
2516
2518
|
queryFn: async () => {
|
|
2517
2519
|
const resp = await requestDatalayer({
|
|
2518
|
-
url: `${configuration.iamRunUrl}/api/iam/v1/
|
|
2520
|
+
url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${tokenId}`,
|
|
2519
2521
|
method: 'GET',
|
|
2520
2522
|
});
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
+
const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
|
|
2524
|
+
if (resp.success && tokenPayload) {
|
|
2525
|
+
return toToken(tokenPayload);
|
|
2523
2526
|
}
|
|
2524
2527
|
return null;
|
|
2525
2528
|
},
|
|
@@ -2534,14 +2537,15 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2534
2537
|
return useMutation({
|
|
2535
2538
|
mutationFn: async (token) => {
|
|
2536
2539
|
return requestDatalayer({
|
|
2537
|
-
url: `${configuration.iamRunUrl}/api/iam/v1/
|
|
2540
|
+
url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${token.id}`,
|
|
2538
2541
|
method: 'PUT',
|
|
2539
2542
|
body: { ...token },
|
|
2540
2543
|
});
|
|
2541
2544
|
},
|
|
2542
2545
|
onSuccess: resp => {
|
|
2543
|
-
|
|
2544
|
-
|
|
2546
|
+
const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
|
|
2547
|
+
if (resp.success && tokenPayload) {
|
|
2548
|
+
const tok = toToken(tokenPayload);
|
|
2545
2549
|
if (tok) {
|
|
2546
2550
|
queryClient.setQueryData(queryKeys.tokens.detail(tok.id), tok);
|
|
2547
2551
|
}
|
|
@@ -2557,7 +2561,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2557
2561
|
return useMutation({
|
|
2558
2562
|
mutationFn: async (tokenId) => {
|
|
2559
2563
|
return requestDatalayer({
|
|
2560
|
-
url: `${configuration.iamRunUrl}/api/iam/v1/
|
|
2564
|
+
url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${tokenId}`,
|
|
2561
2565
|
method: 'DELETE',
|
|
2562
2566
|
});
|
|
2563
2567
|
},
|
|
@@ -16,7 +16,7 @@ export type ProjectRuntimeEntry = {
|
|
|
16
16
|
agentName?: string;
|
|
17
17
|
/** Agent runtime status (running, starting, terminated, etc.). */
|
|
18
18
|
agentStatus?: string;
|
|
19
|
-
/** The
|
|
19
|
+
/** The agentspec ID used to create the runtime. */
|
|
20
20
|
agentSpecId?: string;
|
|
21
21
|
};
|
|
22
22
|
export type ProjectStoreState = {
|
|
@@ -20,7 +20,7 @@ export type ProjectData = {
|
|
|
20
20
|
isPublic: boolean;
|
|
21
21
|
/** Attached agent (runtime) pod name, if any */
|
|
22
22
|
attachedAgentPodName?: string;
|
|
23
|
-
/** Attached
|
|
23
|
+
/** Attached agentspec ID (e.g. 'data-acquisition'), if any */
|
|
24
24
|
attachedAgentSpecId?: string;
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
@@ -31,7 +31,7 @@ export type CreateProjectRequest = {
|
|
|
31
31
|
name: string;
|
|
32
32
|
/** Project description */
|
|
33
33
|
description?: string;
|
|
34
|
-
/**
|
|
34
|
+
/** Agentspec to attach (creates agent on project creation) */
|
|
35
35
|
agentSpecId?: string;
|
|
36
36
|
};
|
|
37
37
|
/**
|
package/lib/models/IAMToken.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare class ProjectDTO {
|
|
|
66
66
|
get createdAt(): Date;
|
|
67
67
|
/** Attached agent runtime pod name, if any. */
|
|
68
68
|
get attachedAgentPodName(): string | undefined;
|
|
69
|
-
/** Attached
|
|
69
|
+
/** Attached agentspec ID (e.g., 'data-acquisition'), if any. */
|
|
70
70
|
get attachedAgentSpecId(): string | undefined;
|
|
71
71
|
/** Human-readable name of the attached agent runtime, if any. */
|
|
72
72
|
get attachedAgentGivenName(): string | undefined;
|
package/lib/models/ProjectDTO.js
CHANGED
|
@@ -69,7 +69,7 @@ export class ProjectDTO {
|
|
|
69
69
|
this._checkDeleted();
|
|
70
70
|
return this._data.attached_agent_pod_name_s || undefined;
|
|
71
71
|
}
|
|
72
|
-
/** Attached
|
|
72
|
+
/** Attached agentspec ID (e.g., 'data-acquisition'), if any. */
|
|
73
73
|
get attachedAgentSpecId() {
|
|
74
74
|
this._checkDeleted();
|
|
75
75
|
return this._data.attached_agent_spec_id_s || undefined;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { useStore } from 'zustand';
|
|
7
|
-
import { configLogger } from '../../utils/
|
|
7
|
+
import { configLogger } from '../../utils/Logger.js';
|
|
8
8
|
let loadConfigurationFromServer = true;
|
|
9
9
|
let initialConfiguration = {
|
|
10
10
|
runUrl: 'https://prod1.datalayer.run',
|
package/lib/utils/Date.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export declare const timeSince: (date: Date) => string;
|
|
|
6
6
|
* Examples: "just now", "15m ago", "3h ago", "2d ago", "4w ago", "1y ago".
|
|
7
7
|
*/
|
|
8
8
|
export declare const formatRelativeTime: (value?: Date | string | number, now?: Date) => string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Build a detailed datetime string suitable for tooltips.
|
|
11
|
+
*
|
|
12
|
+
* Example:
|
|
13
|
+
* "2026-06-08T16:15:32.531Z • Jun 8, 2026, 6:15:32 PM CEST (Europe/Paris)"
|
|
14
|
+
*/
|
|
15
|
+
export declare const formatDateTimeDetails: (value?: Date | string | number) => string | undefined;
|
|
9
16
|
/**
|
|
10
17
|
* Format a duration in milliseconds into a human-readable string.
|
|
11
18
|
*
|
package/lib/utils/Date.js
CHANGED
|
@@ -76,24 +76,57 @@ export const formatRelativeTime = (value, now = new Date()) => {
|
|
|
76
76
|
if (Number.isNaN(ts)) {
|
|
77
77
|
return typeof value === 'string' ? value : undefined;
|
|
78
78
|
}
|
|
79
|
-
const diffMs =
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
const diffMs = ts - now.getTime();
|
|
80
|
+
const inFuture = diffMs > 0;
|
|
81
|
+
const absSeconds = Math.floor(Math.abs(diffMs) / 1000);
|
|
82
|
+
if (absSeconds < 30)
|
|
83
|
+
return inFuture ? 'in a few seconds' : 'just now';
|
|
84
|
+
const withDirection = (value, unit) => {
|
|
85
|
+
return inFuture ? `in ${value}${unit}` : `${value}${unit} ago`;
|
|
86
|
+
};
|
|
87
|
+
if (absSeconds < 60)
|
|
88
|
+
return withDirection(absSeconds, 's');
|
|
89
|
+
const minutes = Math.floor(absSeconds / 60);
|
|
84
90
|
if (minutes < 60)
|
|
85
|
-
return
|
|
91
|
+
return withDirection(minutes, 'm');
|
|
86
92
|
const hours = Math.floor(minutes / 60);
|
|
87
93
|
if (hours < 24)
|
|
88
|
-
return
|
|
94
|
+
return withDirection(hours, 'h');
|
|
89
95
|
const days = Math.floor(hours / 24);
|
|
90
96
|
if (days < 7)
|
|
91
|
-
return
|
|
97
|
+
return withDirection(days, 'd');
|
|
92
98
|
const weeks = Math.floor(days / 7);
|
|
93
99
|
if (weeks < 52)
|
|
94
|
-
return
|
|
100
|
+
return withDirection(weeks, 'w');
|
|
95
101
|
const years = Math.floor(days / 365);
|
|
96
|
-
return
|
|
102
|
+
return withDirection(years, 'y');
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Build a detailed datetime string suitable for tooltips.
|
|
106
|
+
*
|
|
107
|
+
* Example:
|
|
108
|
+
* "2026-06-08T16:15:32.531Z • Jun 8, 2026, 6:15:32 PM CEST (Europe/Paris)"
|
|
109
|
+
*/
|
|
110
|
+
export const formatDateTimeDetails = (value) => {
|
|
111
|
+
if (value === undefined || value === null) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const date = value instanceof Date
|
|
115
|
+
? value
|
|
116
|
+
: typeof value === 'number'
|
|
117
|
+
? new Date(value)
|
|
118
|
+
: new Date(value);
|
|
119
|
+
const ts = date.getTime();
|
|
120
|
+
if (Number.isNaN(ts)) {
|
|
121
|
+
return typeof value === 'string' ? value : undefined;
|
|
122
|
+
}
|
|
123
|
+
const iso = date.toISOString();
|
|
124
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'local';
|
|
125
|
+
const local = new Intl.DateTimeFormat(undefined, {
|
|
126
|
+
dateStyle: 'medium',
|
|
127
|
+
timeStyle: 'long',
|
|
128
|
+
}).format(date);
|
|
129
|
+
return `${iso} • ${local} (${zone})`;
|
|
97
130
|
};
|
|
98
131
|
/**
|
|
99
132
|
* Format a duration in milliseconds into a human-readable string.
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type APIKeyEditProps = {
|
|
2
|
+
/** Route to navigate after delete. Defaults to '/settings/iam/api-keys'. */
|
|
3
|
+
apiKeysListRoute?: string;
|
|
4
|
+
/** Whether to render the local edit title header. Defaults to true. */
|
|
5
|
+
showTitle?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare const APIKeyEdit: ({ apiKeysListRoute, showTitle, }?: APIKeyEditProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default APIKeyEdit;
|