@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
@@ -305,6 +305,20 @@ export const queryKeys = {
305
305
  growth: {
306
306
  kpi: () => ['growth', 'kpi'],
307
307
  },
308
+ // Ray (Runtimes)
309
+ ray: {
310
+ all: () => ['ray'],
311
+ clusters: () => [...queryKeys.ray.all(), 'clusters'],
312
+ cluster: (namespace, clusterName) => [...queryKeys.ray.all(), 'cluster', namespace, clusterName],
313
+ jobs: (namespace, clusterName) => [
314
+ ...queryKeys.ray.all(),
315
+ 'jobs',
316
+ namespace,
317
+ clusterName || 'all',
318
+ ],
319
+ job: (namespace, jobName) => [...queryKeys.ray.all(), 'job', namespace, jobName],
320
+ logs: (namespace, jobName) => [...queryKeys.ray.job(namespace, jobName), 'logs'],
321
+ },
308
322
  // OAuth2
309
323
  oauth2: {
310
324
  authorizationUrl: (queryArgs) => ['oauth2', 'authz', 'url', queryArgs],
@@ -2011,7 +2025,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2011
2025
  const principalUid = options?.principalUid;
2012
2026
  const principalKind = options?.principalKind;
2013
2027
  return useQuery({
2014
- queryKey: [...queryKeys.datasources.all(), principalUid || 'self', principalKind || ''],
2028
+ queryKey: [
2029
+ ...queryKeys.datasources.all(),
2030
+ principalUid || 'self',
2031
+ principalKind || '',
2032
+ ],
2015
2033
  queryFn: async () => {
2016
2034
  const resp = await requestDatalayer({
2017
2035
  url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources`, principalUid, principalKind),
@@ -2072,7 +2090,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2072
2090
  const principalUid = options?.principalUid;
2073
2091
  const principalKind = options?.principalKind;
2074
2092
  return useQuery({
2075
- queryKey: [...queryKeys.secrets.all(), principalUid || 'self', principalKind || ''],
2093
+ queryKey: [
2094
+ ...queryKeys.secrets.all(),
2095
+ principalUid || 'self',
2096
+ principalKind || '',
2097
+ ],
2076
2098
  queryFn: async () => {
2077
2099
  const resp = await requestDatalayer({
2078
2100
  url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets`, principalUid, principalKind),
@@ -2144,11 +2166,12 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2144
2166
  queryKey: queryKeys.tokens.all(),
2145
2167
  queryFn: async () => {
2146
2168
  const resp = await requestDatalayer({
2147
- url: `${configuration.iamRunUrl}/api/iam/v1/tokens`,
2169
+ url: `${configuration.iamRunUrl}/api/iam/v1/api-keys`,
2148
2170
  method: 'GET',
2149
2171
  });
2150
- if (resp.success && resp.tokens) {
2151
- const tokens = resp.tokens
2172
+ const tokenItems = asArray(resp?.apiKeys ?? resp?.api_keys ?? resp?.tokens);
2173
+ if (resp.success && tokenItems.length > 0) {
2174
+ const tokens = tokenItems
2152
2175
  .map((t) => {
2153
2176
  const token = toToken(t);
2154
2177
  if (token) {
@@ -2172,7 +2195,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2172
2195
  return useMutation({
2173
2196
  mutationFn: async (token) => {
2174
2197
  const resp = await requestDatalayer({
2175
- url: `${configuration.iamRunUrl}/api/iam/v1/tokens`,
2198
+ url: `${configuration.iamRunUrl}/api/iam/v1/api-keys`,
2176
2199
  method: 'POST',
2177
2200
  body: {
2178
2201
  ...token,
@@ -2180,8 +2203,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2180
2203
  },
2181
2204
  });
2182
2205
  // Transform the token in the response
2183
- if (resp.success && resp.token) {
2184
- resp.token = toToken(resp.token);
2206
+ const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
2207
+ if (resp.success && tokenPayload) {
2208
+ resp.token = toToken(tokenPayload);
2185
2209
  }
2186
2210
  return resp;
2187
2211
  },
@@ -2385,7 +2409,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2385
2409
  const principalUid = options?.principalUid;
2386
2410
  const principalKind = options?.principalKind;
2387
2411
  return useQuery({
2388
- queryKey: [...queryKeys.datasources.detail(datasourceId), principalUid || 'self', principalKind || ''],
2412
+ queryKey: [
2413
+ ...queryKeys.datasources.detail(datasourceId),
2414
+ principalUid || 'self',
2415
+ principalKind || '',
2416
+ ],
2389
2417
  queryFn: async () => {
2390
2418
  const resp = await requestDatalayer({
2391
2419
  url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/datasources/${datasourceId}`, principalUid, principalKind),
@@ -2434,7 +2462,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2434
2462
  const principalUid = options?.principalUid;
2435
2463
  const principalKind = options?.principalKind;
2436
2464
  return useQuery({
2437
- queryKey: [...queryKeys.secrets.detail(secretId), principalUid || 'self', principalKind || ''],
2465
+ queryKey: [
2466
+ ...queryKeys.secrets.detail(secretId),
2467
+ principalUid || 'self',
2468
+ principalKind || '',
2469
+ ],
2438
2470
  queryFn: async () => {
2439
2471
  const resp = await requestDatalayer({
2440
2472
  url: withAccountUidQuery(`${configuration.iamRunUrl}/api/iam/v1/secrets/${secretId}`, principalUid, principalKind),
@@ -2485,11 +2517,12 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2485
2517
  queryKey: queryKeys.tokens.detail(tokenId),
2486
2518
  queryFn: async () => {
2487
2519
  const resp = await requestDatalayer({
2488
- url: `${configuration.iamRunUrl}/api/iam/v1/tokens/${tokenId}`,
2520
+ url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${tokenId}`,
2489
2521
  method: 'GET',
2490
2522
  });
2491
- if (resp.success && resp.token) {
2492
- return toToken(resp.token);
2523
+ const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
2524
+ if (resp.success && tokenPayload) {
2525
+ return toToken(tokenPayload);
2493
2526
  }
2494
2527
  return null;
2495
2528
  },
@@ -2504,14 +2537,15 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2504
2537
  return useMutation({
2505
2538
  mutationFn: async (token) => {
2506
2539
  return requestDatalayer({
2507
- url: `${configuration.iamRunUrl}/api/iam/v1/tokens/${token.id}`,
2540
+ url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${token.id}`,
2508
2541
  method: 'PUT',
2509
2542
  body: { ...token },
2510
2543
  });
2511
2544
  },
2512
2545
  onSuccess: resp => {
2513
- if (resp.success && resp.token) {
2514
- const tok = toToken(resp.token);
2546
+ const tokenPayload = resp?.apiKey ?? resp?.api_key ?? resp?.token;
2547
+ if (resp.success && tokenPayload) {
2548
+ const tok = toToken(tokenPayload);
2515
2549
  if (tok) {
2516
2550
  queryClient.setQueryData(queryKeys.tokens.detail(tok.id), tok);
2517
2551
  }
@@ -2527,7 +2561,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
2527
2561
  return useMutation({
2528
2562
  mutationFn: async (tokenId) => {
2529
2563
  return requestDatalayer({
2530
- url: `${configuration.iamRunUrl}/api/iam/v1/tokens/${tokenId}`,
2564
+ url: `${configuration.iamRunUrl}/api/iam/v1/api-keys/${tokenId}`,
2531
2565
  method: 'DELETE',
2532
2566
  });
2533
2567
  },
@@ -6805,6 +6839,167 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
6805
6839
  });
6806
6840
  };
6807
6841
  // ============================================================================
6842
+ // Ray (Runtimes) Hooks
6843
+ // ============================================================================
6844
+ const useRayClusters = () => {
6845
+ return useQuery({
6846
+ queryKey: queryKeys.ray.clusters(),
6847
+ queryFn: async () => {
6848
+ const resp = await requestDatalayer({
6849
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/clusters`,
6850
+ method: 'GET',
6851
+ });
6852
+ if (!resp.success) {
6853
+ throw new Error(resp.message || 'Failed to fetch Ray clusters');
6854
+ }
6855
+ return {
6856
+ clusters: asArray(resp.clusters),
6857
+ namespaces: asArray(resp.namespaces)
6858
+ .map(value => String(value))
6859
+ .filter(Boolean),
6860
+ };
6861
+ },
6862
+ ...DEFAULT_QUERY_OPTIONS,
6863
+ refetchOnMount: true,
6864
+ enabled: Boolean(configuration.runtimesRunUrl),
6865
+ });
6866
+ };
6867
+ const useRayCluster = (namespace = 'default', clusterName = '') => {
6868
+ return useQuery({
6869
+ queryKey: queryKeys.ray.cluster(namespace, clusterName),
6870
+ queryFn: async () => {
6871
+ const resp = await requestDatalayer({
6872
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/clusters/${encodeURIComponent(clusterName)}?namespace=${encodeURIComponent(namespace)}`,
6873
+ method: 'GET',
6874
+ });
6875
+ if (!resp.success) {
6876
+ throw new Error(resp.message || 'Failed to fetch Ray cluster');
6877
+ }
6878
+ return resp.cluster || null;
6879
+ },
6880
+ ...DEFAULT_QUERY_OPTIONS,
6881
+ enabled: Boolean(configuration.runtimesRunUrl) &&
6882
+ Boolean(namespace) &&
6883
+ Boolean(clusterName),
6884
+ });
6885
+ };
6886
+ const useCreateRayCluster = () => {
6887
+ return useMutation({
6888
+ mutationFn: async (payload) => {
6889
+ const resp = await requestDatalayer({
6890
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/clusters`,
6891
+ method: 'POST',
6892
+ body: payload,
6893
+ });
6894
+ if (resp?.success === false) {
6895
+ throw new Error(resp?.message || resp?.reason || 'Failed to create Ray cluster');
6896
+ }
6897
+ return resp;
6898
+ },
6899
+ onSuccess: () => {
6900
+ queryClient.invalidateQueries({ queryKey: queryKeys.ray.all() });
6901
+ },
6902
+ });
6903
+ };
6904
+ const useDeleteRayCluster = (namespace = 'default') => {
6905
+ return useMutation({
6906
+ mutationFn: async (clusterName) => {
6907
+ return requestDatalayer({
6908
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/clusters/${encodeURIComponent(clusterName)}?namespace=${encodeURIComponent(namespace)}`,
6909
+ method: 'DELETE',
6910
+ });
6911
+ },
6912
+ onSuccess: () => {
6913
+ queryClient.invalidateQueries({ queryKey: queryKeys.ray.all() });
6914
+ },
6915
+ });
6916
+ };
6917
+ const useRayJobs = (namespace = 'default', clusterName) => {
6918
+ return useQuery({
6919
+ queryKey: queryKeys.ray.jobs(namespace, clusterName),
6920
+ queryFn: async () => {
6921
+ const params = new URLSearchParams();
6922
+ params.set('namespace', namespace);
6923
+ if (clusterName) {
6924
+ params.set('cluster_name', clusterName);
6925
+ }
6926
+ const resp = await requestDatalayer({
6927
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/jobs?${params.toString()}`,
6928
+ method: 'GET',
6929
+ });
6930
+ if (!resp.success) {
6931
+ throw new Error(resp.message || 'Failed to fetch Ray jobs');
6932
+ }
6933
+ return asArray(resp.jobs);
6934
+ },
6935
+ ...DEFAULT_QUERY_OPTIONS,
6936
+ enabled: Boolean(configuration.runtimesRunUrl),
6937
+ });
6938
+ };
6939
+ const useSubmitRayJob = (clusterName) => {
6940
+ return useMutation({
6941
+ mutationFn: async (payload) => {
6942
+ return requestDatalayer({
6943
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/clusters/${encodeURIComponent(clusterName)}/jobs`,
6944
+ method: 'POST',
6945
+ body: payload,
6946
+ });
6947
+ },
6948
+ onSuccess: () => {
6949
+ queryClient.invalidateQueries({ queryKey: queryKeys.ray.all() });
6950
+ },
6951
+ });
6952
+ };
6953
+ const useDeleteRayJob = (namespace = 'default') => {
6954
+ return useMutation({
6955
+ mutationFn: async (jobName) => {
6956
+ return requestDatalayer({
6957
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/jobs/${encodeURIComponent(jobName)}?namespace=${encodeURIComponent(namespace)}`,
6958
+ method: 'DELETE',
6959
+ });
6960
+ },
6961
+ onSuccess: () => {
6962
+ queryClient.invalidateQueries({ queryKey: queryKeys.ray.all() });
6963
+ },
6964
+ });
6965
+ };
6966
+ const useRayJobLogs = (jobName, namespace = 'default', tailLines = 200, enabled = true) => {
6967
+ return useQuery({
6968
+ queryKey: queryKeys.ray.logs(namespace, jobName),
6969
+ queryFn: async () => {
6970
+ const params = new URLSearchParams();
6971
+ params.set('namespace', namespace);
6972
+ params.set('tail_lines', String(tailLines));
6973
+ const resp = await requestDatalayer({
6974
+ url: `${configuration.runtimesRunUrl}/api/runtimes/v1/ray/jobs/${encodeURIComponent(jobName)}/logs?${params.toString()}`,
6975
+ method: 'GET',
6976
+ // Logs naturally 404 once a job is deleted; never surface a toast.
6977
+ notifyOnError: false,
6978
+ });
6979
+ if (!resp.success) {
6980
+ throw new Error(resp.message || 'Failed to fetch Ray job logs');
6981
+ }
6982
+ return {
6983
+ logs: String(resp.logs || ''),
6984
+ status: String(resp.status || ''),
6985
+ };
6986
+ },
6987
+ ...DEFAULT_QUERY_OPTIONS,
6988
+ enabled: Boolean(configuration.runtimesRunUrl) && Boolean(jobName) && enabled,
6989
+ refetchOnMount: 'always',
6990
+ refetchInterval: query => {
6991
+ const status = String(query.state.data?.status || '').toLowerCase();
6992
+ const terminal = [
6993
+ 'succeeded',
6994
+ 'failed',
6995
+ 'stopped',
6996
+ 'completed',
6997
+ ].includes(status);
6998
+ return terminal ? false : 3000;
6999
+ },
7000
+ });
7001
+ };
7002
+ // ============================================================================
6808
7003
  // Return all methods grouped by category
6809
7004
  // ============================================================================
6810
7005
  return {
@@ -7093,6 +7288,15 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
7093
7288
  useUserSurveys,
7094
7289
  useGrowthKPI,
7095
7290
  useGrowthContactsKPI,
7291
+ // Ray (Runtimes)
7292
+ useRayClusters,
7293
+ useRayCluster,
7294
+ useCreateRayCluster,
7295
+ useDeleteRayCluster,
7296
+ useRayJobs,
7297
+ useSubmitRayJob,
7298
+ useDeleteRayJob,
7299
+ useRayJobLogs,
7096
7300
  // Query keys for manual operations
7097
7301
  queryKeys,
7098
7302
  // Upload
@@ -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 agent spec ID used to create the runtime. */
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 agent spec ID (e.g. 'data-acquisition'), if any */
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
- /** Agent spec to attach (creates agent on project creation) */
34
+ /** Agentspec to attach (creates agent on project creation) */
35
35
  agentSpecId?: string;
36
36
  };
37
37
  /**
@@ -1,5 +1,5 @@
1
1
  export declare const asToken: (s: any) => IIAMToken;
2
- export type IIAMTokenVariant = 'user_token';
2
+ export type IIAMTokenVariant = 'secret' | 'publishable' | 'restricted' | 'temporary';
3
3
  export type IIAMToken = {
4
4
  id: string;
5
5
  variant: IIAMTokenVariant;
@@ -29,6 +29,9 @@ export type IPage = {
29
29
  screenCapture?: string;
30
30
  creator?: IUser;
31
31
  creatorId?: string;
32
+ selectedPrincipalUid?: string;
33
+ selectedPrincipalKind?: 'user' | 'organization' | 'team';
34
+ selectedOrganizationUid?: string;
32
35
  kernelSnapshot?: ICodeSandboxSnapshot;
33
36
  kernelSnapshotId?: string;
34
37
  createdAt?: string;
@@ -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 agent spec ID (e.g., 'data-acquisition'), if any. */
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;
@@ -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 agent spec ID (e.g., 'data-acquisition'), if any. */
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/logger.js';
7
+ import { configLogger } from '../../utils/Logger.js';
8
8
  let loadConfigurationFromServer = true;
9
9
  let initialConfiguration = {
10
10
  runUrl: 'https://prod1.datalayer.run',
@@ -40,7 +40,7 @@ let initialConfiguration = {
40
40
  logoUrl: 'https://assets.datalayer.tech/datalayer-25.svg',
41
41
  logoSquareUrl: 'https://assets.datalayer.tech/datalayer-square.png',
42
42
  copyright: '© 2025 Datalayer, Inc',
43
- docsUrl: 'https://docs.datalayer.ai',
43
+ docsUrl: 'https://datalayer.ai/docs',
44
44
  supportUrl: 'https://datalayer.ai/support',
45
45
  termsUrl: 'https://datalayer.ai/terms',
46
46
  pricingUrl: 'https://datalayer.ai/pricing',
@@ -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 = Math.max(0, now.getTime() - ts);
80
- const seconds = Math.floor(diffMs / 1000);
81
- if (seconds < 60)
82
- return 'just now';
83
- const minutes = Math.floor(seconds / 60);
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 `${minutes}m ago`;
91
+ return withDirection(minutes, 'm');
86
92
  const hours = Math.floor(minutes / 60);
87
93
  if (hours < 24)
88
- return `${hours}h ago`;
94
+ return withDirection(hours, 'h');
89
95
  const days = Math.floor(hours / 24);
90
96
  if (days < 7)
91
- return `${days}d ago`;
97
+ return withDirection(days, 'd');
92
98
  const weeks = Math.floor(days / 7);
93
99
  if (weeks < 52)
94
- return `${weeks}w ago`;
100
+ return withDirection(weeks, 'w');
95
101
  const years = Math.floor(days / 365);
96
- return `${years}y ago`;
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.
@@ -5,3 +5,4 @@ export declare const namesAsInitials: (firstName: string, lastName: string) => s
5
5
  * Convert first and last names to a friendly name.
6
6
  */
7
7
  export declare const toFriendlyName: (firstName?: string, lastName?: string) => string;
8
+ export declare const createRandomTimestampName: () => string;
package/lib/utils/Name.js CHANGED
@@ -2,6 +2,11 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ import { adjectives, animals, uniqueNamesGenerator, } from 'unique-names-generator';
6
+ const RANDOM_TIMESTAMP_NAME_CONFIGURATION = {
7
+ dictionaries: [adjectives, animals],
8
+ separator: '-',
9
+ };
5
10
  export const asDisplayName = (givenName, familyName) => {
6
11
  return givenName
7
12
  ? familyName
@@ -34,3 +39,6 @@ export const toFriendlyName = (firstName, lastName) => {
34
39
  }
35
40
  return '';
36
41
  };
42
+ export const createRandomTimestampName = () => {
43
+ return `${uniqueNamesGenerator(RANDOM_TIMESTAMP_NAME_CONFIGURATION)}-${Date.now()}`;
44
+ };
@@ -6,7 +6,7 @@ import { adjectives, animals, uniqueNamesGenerator, } from 'unique-names-generat
6
6
  /**
7
7
  * Kernel snapshot description configuration.
8
8
  */
9
- const KERNEL_SNAPSHOT_DESCRIPTION_CONFIGURATION = {
9
+ const CODE_SANDBOX_SNAPSHOT_DESCRIPTION_CONFIGURATION = {
10
10
  dictionaries: [adjectives, animals],
11
11
  separator: '-',
12
12
  };
@@ -17,5 +17,5 @@ const KERNEL_SNAPSHOT_DESCRIPTION_CONFIGURATION = {
17
17
  * @returns The kernel snapshot name
18
18
  */
19
19
  export function createSandboxSnapshotName(suffix) {
20
- return `${uniqueNamesGenerator(KERNEL_SNAPSHOT_DESCRIPTION_CONFIGURATION)}-${suffix}`;
20
+ return `${uniqueNamesGenerator(CODE_SANDBOX_SNAPSHOT_DESCRIPTION_CONFIGURATION)}-${suffix}`;
21
21
  }
@@ -15,6 +15,7 @@ export * from './Ids';
15
15
  export * from './Jwt';
16
16
  export * from './Jupyter';
17
17
  export * from './Lazy';
18
+ export * from './Logger';
18
19
  export * from './Msc';
19
20
  export * from './Name';
20
21
  export * from './Notebook';
@@ -19,6 +19,7 @@ export * from './Ids.js';
19
19
  export * from './Jwt.js';
20
20
  export * from './Jupyter.js';
21
21
  export * from './Lazy.js';
22
+ export * from './Logger.js';
22
23
  export * from './Msc.js';
23
24
  export * from './Name.js';
24
25
  export * from './Notebook.js';
@@ -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;