@datalayer/core 1.0.2 → 1.0.11
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 +1 -1
- package/lib/api/constants.d.ts +6 -0
- package/lib/api/constants.js +6 -0
- package/lib/api/index.d.ts +1 -0
- package/lib/api/index.js +1 -0
- package/lib/api/otel/index.d.ts +12 -0
- package/lib/api/otel/index.js +16 -0
- package/lib/api/otel/logs.d.ts +19 -0
- package/lib/api/otel/logs.js +43 -0
- package/lib/api/otel/metrics.d.ts +31 -0
- package/lib/api/otel/metrics.js +65 -0
- package/lib/api/otel/query.d.ts +16 -0
- package/lib/api/otel/query.js +37 -0
- package/lib/api/otel/services.d.ts +39 -0
- package/lib/api/otel/services.js +81 -0
- package/lib/api/otel/traces.d.ts +24 -0
- package/lib/api/otel/traces.js +53 -0
- package/lib/api/otel/types.d.ts +112 -0
- package/lib/api/otel/types.js +5 -0
- package/lib/api/runtimes/checkpoints.d.ts +122 -0
- package/lib/api/runtimes/checkpoints.js +118 -0
- package/lib/api/runtimes/index.d.ts +1 -0
- package/lib/api/runtimes/index.js +1 -0
- package/lib/api/runtimes/runtimes.d.ts +84 -0
- package/lib/api/runtimes/runtimes.js +50 -0
- package/lib/components/auth/Login.js +1 -1
- package/lib/components/display/BusyDots.d.ts +9 -0
- package/lib/components/display/BusyDots.js +31 -0
- package/lib/components/display/LiveRelativeTime.d.ts +10 -0
- package/lib/components/display/LiveRelativeTime.js +21 -0
- package/lib/components/display/index.d.ts +2 -0
- package/lib/components/display/index.js +2 -0
- package/lib/components/flashes/FlashSurveys.js +1 -1
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/navbar/SubdomainNavBar.js +1 -1
- package/lib/components/progress/ConsumptionBar.js +6 -7
- package/lib/components/progress/CreditsIndicator.js +2 -2
- package/lib/components/progress/consumption.d.ts +12 -0
- package/lib/components/progress/consumption.js +31 -0
- package/lib/components/progress/index.d.ts +1 -0
- package/lib/components/progress/index.js +1 -0
- package/lib/components/sparklines/Sparklines.d.ts +16 -0
- package/lib/components/sparklines/Sparklines.js +65 -0
- package/lib/components/sparklines/SparklinesLine.d.ts +8 -0
- package/lib/components/sparklines/SparklinesLine.js +37 -0
- package/lib/components/sparklines/dataProcessing.d.ts +25 -0
- package/lib/components/sparklines/dataProcessing.js +35 -0
- package/lib/components/sparklines/index.d.ts +4 -0
- package/lib/components/sparklines/index.js +7 -0
- package/lib/components/sparklines/types.d.ts +36 -0
- package/lib/components/sparklines/types.js +5 -0
- package/lib/components/storage/ContentsBrowser.js +17 -1
- package/lib/components/subnav/SubNav.js +1 -1
- package/lib/config/Configuration.d.ts +4 -0
- package/lib/hooks/useCache.d.ts +6 -63
- package/lib/hooks/useCache.js +35 -205
- package/lib/hooks/useProjects.d.ts +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +4 -0
- package/lib/models/ItemDTO.js +1 -1
- package/lib/models/RolesPlatform.js +2 -2
- package/lib/models/User.d.ts +2 -0
- package/lib/models/User.js +4 -1
- package/lib/otel/client/OtelClient.d.ts +93 -0
- package/lib/otel/client/OtelClient.js +232 -0
- package/lib/otel/client/index.d.ts +2 -0
- package/lib/otel/client/index.js +5 -0
- package/lib/otel/hooks/index.d.ts +186 -0
- package/lib/otel/hooks/index.js +532 -0
- package/lib/otel/index.d.ts +34 -0
- package/lib/otel/index.js +23 -0
- package/lib/otel/types.d.ts +190 -0
- package/lib/otel/types.js +5 -0
- package/lib/otel/utils.d.ts +33 -0
- package/lib/otel/utils.js +181 -0
- package/lib/otel/views/OtelLive.d.ts +12 -0
- package/lib/otel/views/OtelLive.js +372 -0
- package/lib/otel/views/OtelLogsList.d.ts +11 -0
- package/lib/otel/views/OtelLogsList.js +137 -0
- package/lib/otel/views/OtelMetricsChart.d.ts +22 -0
- package/lib/otel/views/OtelMetricsChart.js +300 -0
- package/lib/otel/views/OtelMetricsList.d.ts +15 -0
- package/lib/otel/views/OtelMetricsList.js +213 -0
- package/lib/otel/views/OtelSearchBar.d.ts +11 -0
- package/lib/otel/views/OtelSearchBar.js +22 -0
- package/lib/otel/views/OtelSpanDetail.d.ts +11 -0
- package/lib/otel/views/OtelSpanDetail.js +172 -0
- package/lib/otel/views/OtelSpanTree.d.ts +11 -0
- package/lib/otel/views/OtelSpanTree.js +176 -0
- package/lib/otel/views/OtelSqlView.d.ts +16 -0
- package/lib/otel/views/OtelSqlView.js +239 -0
- package/lib/otel/views/OtelSystemView.d.ts +15 -0
- package/lib/otel/views/OtelSystemView.js +75 -0
- package/lib/otel/views/OtelTimeline.d.ts +11 -0
- package/lib/otel/views/OtelTimeline.js +101 -0
- package/lib/otel/views/OtelTimelineRangeSlider.d.ts +16 -0
- package/lib/otel/views/OtelTimelineRangeSlider.js +338 -0
- package/lib/otel/views/OtelTracesList.d.ts +13 -0
- package/lib/otel/views/OtelTracesList.js +199 -0
- package/lib/otel/views/index.d.ts +20 -0
- package/lib/otel/views/index.js +21 -0
- package/lib/state/storage/IAMStorage.d.ts +2 -1
- package/lib/state/substates/CoreState.js +7 -6
- package/lib/utils/Date.d.ts +6 -0
- package/lib/utils/Date.js +37 -0
- package/lib/utils/Jwt.d.ts +42 -0
- package/lib/utils/Jwt.js +44 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/views/iam/SignInSimple.d.ts +43 -0
- package/lib/views/iam/SignInSimple.js +113 -0
- package/lib/views/iam/index.d.ts +2 -0
- package/lib/views/iam/index.js +5 -0
- package/lib/views/iam-tokens/IAMTokenEdit.d.ts +5 -1
- package/lib/views/iam-tokens/IAMTokenEdit.js +54 -5
- package/lib/views/iam-tokens/IAMTokenNew.js +2 -2
- package/lib/views/iam-tokens/IAMTokens.d.ts +4 -2
- package/lib/views/iam-tokens/IAMTokens.js +68 -36
- package/lib/views/iam-tokens/Tokens.js +63 -31
- package/lib/views/index.d.ts +3 -1
- package/lib/views/index.js +3 -1
- package/lib/views/otel/DashboardView.d.ts +16 -0
- package/lib/views/otel/DashboardView.js +4 -0
- package/lib/views/otel/LogsView.d.ts +12 -0
- package/lib/views/otel/LogsView.js +4 -0
- package/lib/views/otel/MetricsView.d.ts +12 -0
- package/lib/views/otel/MetricsView.js +4 -0
- package/lib/views/otel/OtelHeader.d.ts +33 -0
- package/lib/views/otel/OtelHeader.js +105 -0
- package/lib/views/otel/SqlView.d.ts +9 -0
- package/lib/views/otel/SqlView.js +4 -0
- package/lib/views/otel/SystemView.d.ts +9 -0
- package/lib/views/otel/SystemView.js +4 -0
- package/lib/views/otel/TracesView.d.ts +12 -0
- package/lib/views/otel/TracesView.js +4 -0
- package/lib/views/otel/index.d.ts +16 -0
- package/lib/views/otel/index.js +12 -0
- package/lib/views/otel/simpleAuthStore.d.ts +21 -0
- package/lib/views/otel/simpleAuthStore.js +22 -0
- package/lib/views/profile/UserBadge.d.ts +20 -0
- package/lib/views/profile/UserBadge.js +101 -0
- package/lib/views/profile/index.d.ts +2 -0
- package/lib/views/profile/index.js +5 -0
- package/package.json +3 -4
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single runtime checkpoint record.
|
|
3
|
+
*/
|
|
4
|
+
export interface RuntimeCheckpointData {
|
|
5
|
+
/** Unique identifier (ULID) */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable name */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Checkpoint description */
|
|
10
|
+
description: string;
|
|
11
|
+
/** Runtime that was checkpointed */
|
|
12
|
+
runtime_uid: string;
|
|
13
|
+
/** Agent spec identifier (e.g. "mocks/monitor-sales-kpis") */
|
|
14
|
+
agent_spec_id: string;
|
|
15
|
+
/** Full agent spec payload */
|
|
16
|
+
agentspec: Record<string, any>;
|
|
17
|
+
/** Additional metadata */
|
|
18
|
+
metadata: Record<string, any>;
|
|
19
|
+
/** Checkpoint mode: criu (full) or light (history-only) */
|
|
20
|
+
checkpoint_mode?: 'criu' | 'light';
|
|
21
|
+
/** Lightweight checkpoint message history */
|
|
22
|
+
messages?: string[];
|
|
23
|
+
/** Status: requested | created | failed | deleted */
|
|
24
|
+
status: string;
|
|
25
|
+
/** Human-readable details about the current status (e.g. error message) */
|
|
26
|
+
status_message?: string;
|
|
27
|
+
/** ISO 8601 timestamp */
|
|
28
|
+
updated_at: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Request payload for creating a checkpoint.
|
|
32
|
+
*/
|
|
33
|
+
export interface CreateRuntimeCheckpointRequest {
|
|
34
|
+
/** Runtime UID of the runtime that was checkpointed */
|
|
35
|
+
runtime_uid: string;
|
|
36
|
+
/** Human-readable name */
|
|
37
|
+
name?: string;
|
|
38
|
+
/** Checkpoint description */
|
|
39
|
+
description?: string;
|
|
40
|
+
/** Agent spec identifier */
|
|
41
|
+
agentspec_id?: string;
|
|
42
|
+
/** Full agent spec payload to persist */
|
|
43
|
+
agentspec?: Record<string, any>;
|
|
44
|
+
/** Additional metadata */
|
|
45
|
+
metadata?: Record<string, any>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Response for listing checkpoints.
|
|
49
|
+
*/
|
|
50
|
+
export interface ListRuntimeCheckpointsResponse {
|
|
51
|
+
success: boolean;
|
|
52
|
+
message: string;
|
|
53
|
+
checkpoints: RuntimeCheckpointData[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Response for getting a single checkpoint.
|
|
57
|
+
*/
|
|
58
|
+
export interface GetRuntimeCheckpointResponse {
|
|
59
|
+
success: boolean;
|
|
60
|
+
message: string;
|
|
61
|
+
checkpoint: RuntimeCheckpointData;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Response for creating a checkpoint.
|
|
65
|
+
*/
|
|
66
|
+
export interface CreateRuntimeCheckpointResponse {
|
|
67
|
+
success: boolean;
|
|
68
|
+
message: string;
|
|
69
|
+
checkpoint: RuntimeCheckpointData;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* List runtime checkpoints for a specific runtime.
|
|
73
|
+
* @param token - Authentication token
|
|
74
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
75
|
+
* @param runtimeUid - Runtime UID to list checkpoints for
|
|
76
|
+
* @returns Promise resolving to list of checkpoints
|
|
77
|
+
*/
|
|
78
|
+
export declare const listCheckpoints: (token: string, baseUrl?: string, runtimeUid?: string) => Promise<ListRuntimeCheckpointsResponse>;
|
|
79
|
+
/**
|
|
80
|
+
* Get a single runtime checkpoint by ID.
|
|
81
|
+
* @param token - Authentication token
|
|
82
|
+
* @param runtimeUid - The runtime UID
|
|
83
|
+
* @param checkpointId - The checkpoint ID (ULID)
|
|
84
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
85
|
+
* @returns Promise resolving to checkpoint details
|
|
86
|
+
*/
|
|
87
|
+
export declare const getCheckpoint: (token: string, runtimeUid: string, checkpointId: string, baseUrl?: string) => Promise<GetRuntimeCheckpointResponse>;
|
|
88
|
+
/**
|
|
89
|
+
* Create a checkpoint record (after a CRIU checkpoint has been taken).
|
|
90
|
+
* @param token - Authentication token
|
|
91
|
+
* @param data - Checkpoint creation payload (runtime_uid, agentspec, etc.)
|
|
92
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
93
|
+
* @returns Promise resolving to the created checkpoint
|
|
94
|
+
*/
|
|
95
|
+
export declare const createCheckpoint: (token: string, data: CreateRuntimeCheckpointRequest, baseUrl?: string) => Promise<CreateRuntimeCheckpointResponse>;
|
|
96
|
+
/**
|
|
97
|
+
* Poll a checkpoint until it reaches one of the target statuses.
|
|
98
|
+
*
|
|
99
|
+
* Useful for waiting until an async pause (status "paused")
|
|
100
|
+
* completes before proceeding. Checkpoint status stays "paused"
|
|
101
|
+
* during resume — only the agent runtime status transitions.
|
|
102
|
+
*
|
|
103
|
+
* @param token - Authentication token
|
|
104
|
+
* @param runtimeUid - The runtime UID that owns the checkpoint
|
|
105
|
+
* @param checkpointId - The checkpoint ID (ULID) to poll
|
|
106
|
+
* @param targetStatuses - Set of statuses that signal completion (e.g. ["paused", "failed"])
|
|
107
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
108
|
+
* @param intervalMs - Polling interval in milliseconds (default: 2000)
|
|
109
|
+
* @param timeoutMs - Maximum wait time in milliseconds (default: 600000 = 10 min)
|
|
110
|
+
* @returns Promise resolving to the checkpoint data once a target status is reached
|
|
111
|
+
* @throws {Error} If polling times out or the request fails
|
|
112
|
+
*/
|
|
113
|
+
export declare const waitForCheckpointStatus: (token: string, runtimeUid: string, checkpointId: string, targetStatuses: string[], baseUrl?: string, intervalMs?: number, timeoutMs?: number) => Promise<RuntimeCheckpointData>;
|
|
114
|
+
/**
|
|
115
|
+
* Delete a runtime checkpoint.
|
|
116
|
+
* @param token - Authentication token
|
|
117
|
+
* @param runtimeUid - The runtime UID that owns the checkpoint
|
|
118
|
+
* @param checkpointId - The checkpoint ID (ULID) to delete
|
|
119
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
120
|
+
* @returns Promise resolving when deletion is complete
|
|
121
|
+
*/
|
|
122
|
+
export declare const deleteCheckpoint: (token: string, runtimeUid: string, checkpointId: string, baseUrl?: string) => Promise<void>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Runtime checkpoints API functions for the Datalayer platform.
|
|
7
|
+
*
|
|
8
|
+
* Provides functions for managing CRIU full-pod checkpoints.
|
|
9
|
+
* These are distinct from runtime snapshots (Jupyter sandbox snapshots).
|
|
10
|
+
*
|
|
11
|
+
* @module api/runtimes/checkpoints
|
|
12
|
+
*/
|
|
13
|
+
import { requestDatalayerAPI } from '../DatalayerApi';
|
|
14
|
+
import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants';
|
|
15
|
+
import { validateToken, validateRequiredString } from '../utils/validation';
|
|
16
|
+
// ─── API Functions ─────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* List runtime checkpoints for a specific runtime.
|
|
19
|
+
* @param token - Authentication token
|
|
20
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
21
|
+
* @param runtimeUid - Runtime UID to list checkpoints for
|
|
22
|
+
* @returns Promise resolving to list of checkpoints
|
|
23
|
+
*/
|
|
24
|
+
export const listCheckpoints = async (token, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES, runtimeUid) => {
|
|
25
|
+
validateToken(token);
|
|
26
|
+
if (!runtimeUid) {
|
|
27
|
+
throw new Error('runtimeUid is required to list checkpoints');
|
|
28
|
+
}
|
|
29
|
+
return requestDatalayerAPI({
|
|
30
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtime-checkpoints/${encodeURIComponent(runtimeUid)}`,
|
|
31
|
+
method: 'GET',
|
|
32
|
+
token,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Get a single runtime checkpoint by ID.
|
|
37
|
+
* @param token - Authentication token
|
|
38
|
+
* @param runtimeUid - The runtime UID
|
|
39
|
+
* @param checkpointId - The checkpoint ID (ULID)
|
|
40
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
41
|
+
* @returns Promise resolving to checkpoint details
|
|
42
|
+
*/
|
|
43
|
+
export const getCheckpoint = async (token, runtimeUid, checkpointId, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
44
|
+
validateToken(token);
|
|
45
|
+
validateRequiredString(runtimeUid, 'Runtime UID');
|
|
46
|
+
validateRequiredString(checkpointId, 'Checkpoint ID');
|
|
47
|
+
return requestDatalayerAPI({
|
|
48
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtime-checkpoints/${encodeURIComponent(runtimeUid)}/${checkpointId}`,
|
|
49
|
+
method: 'GET',
|
|
50
|
+
token,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Create a checkpoint record (after a CRIU checkpoint has been taken).
|
|
55
|
+
* @param token - Authentication token
|
|
56
|
+
* @param data - Checkpoint creation payload (runtime_uid, agentspec, etc.)
|
|
57
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
58
|
+
* @returns Promise resolving to the created checkpoint
|
|
59
|
+
*/
|
|
60
|
+
export const createCheckpoint = async (token, data, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
61
|
+
validateToken(token);
|
|
62
|
+
return requestDatalayerAPI({
|
|
63
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtime-checkpoints`,
|
|
64
|
+
method: 'POST',
|
|
65
|
+
token,
|
|
66
|
+
body: data,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Poll a checkpoint until it reaches one of the target statuses.
|
|
71
|
+
*
|
|
72
|
+
* Useful for waiting until an async pause (status "paused")
|
|
73
|
+
* completes before proceeding. Checkpoint status stays "paused"
|
|
74
|
+
* during resume — only the agent runtime status transitions.
|
|
75
|
+
*
|
|
76
|
+
* @param token - Authentication token
|
|
77
|
+
* @param runtimeUid - The runtime UID that owns the checkpoint
|
|
78
|
+
* @param checkpointId - The checkpoint ID (ULID) to poll
|
|
79
|
+
* @param targetStatuses - Set of statuses that signal completion (e.g. ["paused", "failed"])
|
|
80
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
81
|
+
* @param intervalMs - Polling interval in milliseconds (default: 2000)
|
|
82
|
+
* @param timeoutMs - Maximum wait time in milliseconds (default: 600000 = 10 min)
|
|
83
|
+
* @returns Promise resolving to the checkpoint data once a target status is reached
|
|
84
|
+
* @throws {Error} If polling times out or the request fails
|
|
85
|
+
*/
|
|
86
|
+
export const waitForCheckpointStatus = async (token, runtimeUid, checkpointId, targetStatuses, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES, intervalMs = 2000, timeoutMs = 600_000) => {
|
|
87
|
+
validateToken(token);
|
|
88
|
+
validateRequiredString(runtimeUid, 'Runtime UID');
|
|
89
|
+
validateRequiredString(checkpointId, 'Checkpoint ID');
|
|
90
|
+
const deadline = Date.now() + timeoutMs;
|
|
91
|
+
while (Date.now() < deadline) {
|
|
92
|
+
const resp = await getCheckpoint(token, runtimeUid, checkpointId, baseUrl);
|
|
93
|
+
if (targetStatuses.includes(resp.checkpoint.status)) {
|
|
94
|
+
return resp.checkpoint;
|
|
95
|
+
}
|
|
96
|
+
// Wait before next poll
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Checkpoint ${checkpointId} did not reach status [${targetStatuses.join(', ')}] within ${timeoutMs / 1000}s`);
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Delete a runtime checkpoint.
|
|
103
|
+
* @param token - Authentication token
|
|
104
|
+
* @param runtimeUid - The runtime UID that owns the checkpoint
|
|
105
|
+
* @param checkpointId - The checkpoint ID (ULID) to delete
|
|
106
|
+
* @param baseUrl - Base URL for the runtimes API
|
|
107
|
+
* @returns Promise resolving when deletion is complete
|
|
108
|
+
*/
|
|
109
|
+
export const deleteCheckpoint = async (token, runtimeUid, checkpointId, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
|
|
110
|
+
validateToken(token);
|
|
111
|
+
validateRequiredString(runtimeUid, 'Runtime UID');
|
|
112
|
+
validateRequiredString(checkpointId, 'Checkpoint ID');
|
|
113
|
+
return requestDatalayerAPI({
|
|
114
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtime-checkpoints/${encodeURIComponent(runtimeUid)}/${checkpointId}`,
|
|
115
|
+
method: 'DELETE',
|
|
116
|
+
token,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
@@ -52,3 +52,87 @@ export declare const deleteRuntime: (token: string, podName: string, baseUrl?: s
|
|
|
52
52
|
* @throws {Error} With status 404 if the runtime is not found
|
|
53
53
|
*/
|
|
54
54
|
export declare const updateRuntime: (token: string, podName: string, from: string, baseUrl?: string) => Promise<RuntimeData>;
|
|
55
|
+
/**
|
|
56
|
+
* Response from the pause/resume runtime endpoints.
|
|
57
|
+
* The server returns 202 Accepted and provides a checkpoint UID
|
|
58
|
+
* that can be polled for status updates.
|
|
59
|
+
*/
|
|
60
|
+
export interface PauseResumeResponse {
|
|
61
|
+
success: boolean;
|
|
62
|
+
message: string;
|
|
63
|
+
checkpoint_id: string | null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Optional body for the pause endpoint.
|
|
67
|
+
* Metadata is stored in the checkpoint Solr record created by the backend.
|
|
68
|
+
*/
|
|
69
|
+
export interface PauseRuntimeBody {
|
|
70
|
+
/** Checkpoint mode: 'criu' (full) or 'light' (history-only) */
|
|
71
|
+
checkpoint_mode?: 'criu' | 'light';
|
|
72
|
+
/** Human-readable checkpoint name */
|
|
73
|
+
name?: string;
|
|
74
|
+
/** Checkpoint description */
|
|
75
|
+
description?: string;
|
|
76
|
+
/** Agent spec identifier */
|
|
77
|
+
agent_spec_id?: string;
|
|
78
|
+
/** Full agent spec payload to persist with the checkpoint */
|
|
79
|
+
agentspec?: Record<string, any>;
|
|
80
|
+
/** Additional metadata */
|
|
81
|
+
metadata?: Record<string, any>;
|
|
82
|
+
/** Lightweight checkpoint message history */
|
|
83
|
+
messages?: string[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Pause a runtime by creating a checkpoint (async, light mode by default).
|
|
87
|
+
*
|
|
88
|
+
* Returns immediately with a 202 Accepted response. The actual checkpoint
|
|
89
|
+
* process runs in the background. Use the returned `checkpoint_id` to
|
|
90
|
+
* poll for status via the runtime-checkpoints API.
|
|
91
|
+
*
|
|
92
|
+
* @param token - Authentication token
|
|
93
|
+
* @param podName - The unique pod name of the runtime to pause
|
|
94
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
95
|
+
* @param body - Optional metadata to store in the checkpoint record
|
|
96
|
+
* @returns Promise resolving with the checkpoint ID for status tracking
|
|
97
|
+
* @throws {Error} If authentication token is missing or invalid
|
|
98
|
+
* @throws {Error} If pod name is missing or invalid
|
|
99
|
+
*/
|
|
100
|
+
export declare const pauseRuntime: (token: string, podName: string, baseUrl?: string, body?: PauseRuntimeBody) => Promise<PauseResumeResponse>;
|
|
101
|
+
/**
|
|
102
|
+
* Optional body for the resume endpoint.
|
|
103
|
+
* Contains information needed by the operator to restore from a checkpoint.
|
|
104
|
+
*/
|
|
105
|
+
export interface ResumeRuntimeBody {
|
|
106
|
+
/** Checkpoint mode to resume from: 'criu' or 'light' */
|
|
107
|
+
checkpoint_mode?: 'criu' | 'light';
|
|
108
|
+
/** Explicit checkpoint identifier */
|
|
109
|
+
checkpoint_id?: string;
|
|
110
|
+
/** Agent spec identifier (required by the operator for restore) */
|
|
111
|
+
agent_spec_id?: string;
|
|
112
|
+
/** Specific checkpoint timestamp to restore from */
|
|
113
|
+
checkpoint_timestamp?: string;
|
|
114
|
+
/** Environment name override */
|
|
115
|
+
environment_name?: string;
|
|
116
|
+
/** Container image override */
|
|
117
|
+
container_image?: string;
|
|
118
|
+
/** Target node name */
|
|
119
|
+
node_name?: string;
|
|
120
|
+
/** Additional metadata */
|
|
121
|
+
metadata?: Record<string, any>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resume a paused runtime by restoring from a checkpoint (async).
|
|
125
|
+
*
|
|
126
|
+
* Returns immediately with a 202 Accepted response. The actual restore
|
|
127
|
+
* process runs in the background. Use the returned `checkpoint_id` to
|
|
128
|
+
* poll for status.
|
|
129
|
+
*
|
|
130
|
+
* @param token - Authentication token
|
|
131
|
+
* @param podName - The unique pod name of the runtime to resume
|
|
132
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
133
|
+
* @param body - Optional body with agent_spec_id and restore options
|
|
134
|
+
* @returns Promise resolving with the checkpoint ID for status tracking
|
|
135
|
+
* @throws {Error} If authentication token is missing or invalid
|
|
136
|
+
* @throws {Error} If pod name is missing or invalid
|
|
137
|
+
*/
|
|
138
|
+
export declare const resumeRuntime: (token: string, podName: string, baseUrl?: string, body?: ResumeRuntimeBody) => Promise<PauseResumeResponse>;
|
|
@@ -178,3 +178,53 @@ export const updateRuntime = async (token, podName, from, baseUrl = DEFAULT_SERV
|
|
|
178
178
|
throw error;
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
+
/**
|
|
182
|
+
* Pause a runtime by creating a checkpoint (async, light mode by default).
|
|
183
|
+
*
|
|
184
|
+
* Returns immediately with a 202 Accepted response. The actual checkpoint
|
|
185
|
+
* process runs in the background. Use the returned `checkpoint_id` to
|
|
186
|
+
* poll for status via the runtime-checkpoints API.
|
|
187
|
+
*
|
|
188
|
+
* @param token - Authentication token
|
|
189
|
+
* @param podName - The unique pod name of the runtime to pause
|
|
190
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
191
|
+
* @param body - Optional metadata to store in the checkpoint record
|
|
192
|
+
* @returns Promise resolving with the checkpoint ID for status tracking
|
|
193
|
+
* @throws {Error} If authentication token is missing or invalid
|
|
194
|
+
* @throws {Error} If pod name is missing or invalid
|
|
195
|
+
*/
|
|
196
|
+
export const pauseRuntime = async (token, podName, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES, body) => {
|
|
197
|
+
validateToken(token);
|
|
198
|
+
validateRequiredString(podName, 'Pod name');
|
|
199
|
+
return await requestDatalayerAPI({
|
|
200
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtimes/${encodeURIComponent(podName)}/pause`,
|
|
201
|
+
method: 'POST',
|
|
202
|
+
token,
|
|
203
|
+
...(body ? { body } : {}),
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Resume a paused runtime by restoring from a checkpoint (async).
|
|
208
|
+
*
|
|
209
|
+
* Returns immediately with a 202 Accepted response. The actual restore
|
|
210
|
+
* process runs in the background. Use the returned `checkpoint_id` to
|
|
211
|
+
* poll for status.
|
|
212
|
+
*
|
|
213
|
+
* @param token - Authentication token
|
|
214
|
+
* @param podName - The unique pod name of the runtime to resume
|
|
215
|
+
* @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
|
|
216
|
+
* @param body - Optional body with agent_spec_id and restore options
|
|
217
|
+
* @returns Promise resolving with the checkpoint ID for status tracking
|
|
218
|
+
* @throws {Error} If authentication token is missing or invalid
|
|
219
|
+
* @throws {Error} If pod name is missing or invalid
|
|
220
|
+
*/
|
|
221
|
+
export const resumeRuntime = async (token, podName, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES, body) => {
|
|
222
|
+
validateToken(token);
|
|
223
|
+
validateRequiredString(podName, 'Pod name');
|
|
224
|
+
return await requestDatalayerAPI({
|
|
225
|
+
url: `${baseUrl}${API_BASE_PATHS.RUNTIMES}/runtimes/${encodeURIComponent(podName)}/resume`,
|
|
226
|
+
method: 'POST',
|
|
227
|
+
token,
|
|
228
|
+
...(body ? { body } : {}),
|
|
229
|
+
});
|
|
230
|
+
};
|
|
@@ -167,7 +167,7 @@ export const Login = (props) => {
|
|
|
167
167
|
? 'Login…'
|
|
168
168
|
: heading
|
|
169
169
|
? 'Login with Datalayer'
|
|
170
|
-
: 'Login' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.
|
|
170
|
+
: 'Login' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.ai/password", target: "_blank", children: "Forgot password?" })] })] })), _jsx(Box, { children: _jsxs(Box, { display: "flex", flexDirection: "column", sx: { margin: 'auto' }, children: [showGitHubLogin &&
|
|
171
171
|
iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: MarkGithubIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name], as: "a", style: { margin: '10px 0' }, children: "Login with GitHub" })), showGoogleLogin &&
|
|
172
172
|
iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name] && (_jsx(Button, { leadingVisual: GoogleIcon, href: iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name], as: "a", style: { margin: '10px 0' }, children: "Login with Google" })), showTokenLogin && (_jsx(LoginToken, { homeRoute: homeRoute, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] }));
|
|
173
173
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type BusyDotsProps = {
|
|
2
|
+
color?: string;
|
|
3
|
+
fontSize?: number;
|
|
4
|
+
ml?: number;
|
|
5
|
+
ariaLabel?: string;
|
|
6
|
+
intervalMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function BusyDots({ color, fontSize, ml, ariaLabel, intervalMs, }: BusyDotsProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default BusyDots;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } 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 { Text } from '@primer/react';
|
|
8
|
+
const DOTS = ['.', '..', '...'];
|
|
9
|
+
export function BusyDots({ color = 'fg.muted', fontSize = 0, ml = 1, ariaLabel = 'In progress', intervalMs = 450, }) {
|
|
10
|
+
const [index, setIndex] = useState(0);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const timer = window.setInterval(() => {
|
|
13
|
+
setIndex(prev => (prev + 1) % DOTS.length);
|
|
14
|
+
}, intervalMs);
|
|
15
|
+
return () => window.clearInterval(timer);
|
|
16
|
+
}, [intervalMs]);
|
|
17
|
+
return (_jsxs(Text, { as: "span", sx: {
|
|
18
|
+
ml,
|
|
19
|
+
color,
|
|
20
|
+
fontSize,
|
|
21
|
+
display: 'inline-block',
|
|
22
|
+
position: 'relative',
|
|
23
|
+
fontWeight: 'semibold',
|
|
24
|
+
}, "aria-label": ariaLabel, children: [_jsx(Text, { as: "span", sx: { visibility: 'hidden' }, children: "..." }), _jsx(Text, { as: "span", sx: {
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
left: 0,
|
|
27
|
+
top: 0,
|
|
28
|
+
whiteSpace: 'nowrap',
|
|
29
|
+
}, children: DOTS[index] })] }));
|
|
30
|
+
}
|
|
31
|
+
export default BusyDots;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type ILiveRelativeTimeProps = {
|
|
2
|
+
value?: Date | string | number;
|
|
3
|
+
refreshIntervalMs?: number;
|
|
4
|
+
fallback?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Display a live-updating relative time label (e.g. "5m ago").
|
|
8
|
+
*/
|
|
9
|
+
export declare function LiveRelativeTime({ value, refreshIntervalMs, fallback, }: ILiveRelativeTimeProps): JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } 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 { formatRelativeTime } from '../../utils';
|
|
8
|
+
/**
|
|
9
|
+
* Display a live-updating relative time label (e.g. "5m ago").
|
|
10
|
+
*/
|
|
11
|
+
export function LiveRelativeTime({ value, refreshIntervalMs = 1000, fallback = '—', }) {
|
|
12
|
+
const [now, setNow] = useState(() => new Date());
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const timer = window.setInterval(() => {
|
|
15
|
+
setNow(new Date());
|
|
16
|
+
}, refreshIntervalMs);
|
|
17
|
+
return () => window.clearInterval(timer);
|
|
18
|
+
}, [refreshIntervalMs]);
|
|
19
|
+
const label = useMemo(() => formatRelativeTime(value, now), [value, now]);
|
|
20
|
+
return _jsx(_Fragment, { children: label ?? fallback });
|
|
21
|
+
}
|
|
@@ -4,9 +4,11 @@ export * from './CodePreview';
|
|
|
4
4
|
export * from './DatalayerBox';
|
|
5
5
|
export * from './HorizontalCenter';
|
|
6
6
|
export * from './JupyterDialog';
|
|
7
|
+
export * from './LiveRelativeTime';
|
|
7
8
|
export * from './Markdown';
|
|
8
9
|
export * from './NavLink';
|
|
9
10
|
export * from './NotebookSkeleton';
|
|
11
|
+
export * from './BusyDots';
|
|
10
12
|
export * from './Placeholder';
|
|
11
13
|
export * from './ToTopBranded';
|
|
12
14
|
export * from './VisuallyHidden';
|
|
@@ -8,9 +8,11 @@ export * from './CodePreview';
|
|
|
8
8
|
export * from './DatalayerBox';
|
|
9
9
|
export * from './HorizontalCenter';
|
|
10
10
|
export * from './JupyterDialog';
|
|
11
|
+
export * from './LiveRelativeTime';
|
|
11
12
|
export * from './Markdown';
|
|
12
13
|
export * from './NavLink';
|
|
13
14
|
export * from './NotebookSkeleton';
|
|
15
|
+
export * from './BusyDots';
|
|
14
16
|
export * from './Placeholder';
|
|
15
17
|
export * from './ToTopBranded';
|
|
16
18
|
export * from './VisuallyHidden';
|
|
@@ -30,6 +30,6 @@ export const FlashSurveys = (props) => {
|
|
|
30
30
|
setShow(false);
|
|
31
31
|
enqueueToast('Thank you for your answers.', { variant: 'success' });
|
|
32
32
|
};
|
|
33
|
-
return (_jsx(_Fragment, { children: surveys && (show || surveyName) && !configuration.whiteLabel && (_jsxs(FlashClosable, { variant: "default", children: [_jsx(Box, { children: _jsx(Text, { as: "h2", children: "We'd love to know a bit more about you and your needs..." }) }), _jsx(Box, { children: _jsx(Survey2025_1, { formData: surveyName ? surveys.get(SURVEY_2025_1_NAME)?.form : undefined, onSubmit: onSubmit }) }), _jsx(Box, { mt: 3, children: _jsxs(Text, { children: ["The information you give will remain fully private, read our", ' ', _jsx(Link, { href: "https://datalayer.
|
|
33
|
+
return (_jsx(_Fragment, { children: surveys && (show || surveyName) && !configuration.whiteLabel && (_jsxs(FlashClosable, { variant: "default", children: [_jsx(Box, { children: _jsx(Text, { as: "h2", children: "We'd love to know a bit more about you and your needs..." }) }), _jsx(Box, { children: _jsx(Survey2025_1, { formData: surveyName ? surveys.get(SURVEY_2025_1_NAME)?.form : undefined, onSubmit: onSubmit }) }), _jsx(Box, { mt: 3, children: _jsxs(Text, { children: ["The information you give will remain fully private, read our", ' ', _jsx(Link, { href: "https://datalayer.ai/privacy", target: "_blank", children: "privacy policy" }), "."] }) })] })) }));
|
|
34
34
|
};
|
|
35
35
|
export default FlashSurveys;
|
package/lib/components/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const testIds = {
|
|
|
34
34
|
return `${this.root}-search-live-region`;
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
|
-
function Root({ children, className, fixed = true, fullWidth = false, logoHref = 'https://datalayer.
|
|
37
|
+
function Root({ children, className, fixed = true, fullWidth = false, logoHref = 'https://datalayer.ai', title, titleHref = '/', ...rest }) {
|
|
38
38
|
const navigate = useNavigate();
|
|
39
39
|
const runStore = useRunStore();
|
|
40
40
|
const { configuration } = useCoreStore();
|
|
@@ -4,9 +4,10 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { useInterval } from 'usehooks-ts';
|
|
7
8
|
import { ProgressBar, Tooltip, Button } from '@primer/react';
|
|
8
9
|
import { Box } from '@datalayer/primer-addons';
|
|
9
|
-
import {
|
|
10
|
+
import { getConsumptionDuration, getConsumptionProgress } from './consumption';
|
|
10
11
|
const CRITICAL_LEVEL = 90;
|
|
11
12
|
const WARNING_LEVEL = 75;
|
|
12
13
|
/**
|
|
@@ -31,10 +32,8 @@ function formatTimeRemaining(seconds) {
|
|
|
31
32
|
*/
|
|
32
33
|
export function ConsumptionBar(props) {
|
|
33
34
|
const { expiredAt, startedAt, burningRate, onClick, onUpdate, refreshInterval = 2000, style, } = props;
|
|
34
|
-
const duration = useMemo(() => (
|
|
35
|
-
const [progress, setProgress] = useState(expiredAt
|
|
36
|
-
? Math.min(Math.max(0, ((Date.now() / 1000 - startedAt) / duration) * 100), 100)
|
|
37
|
-
: 100);
|
|
35
|
+
const duration = useMemo(() => getConsumptionDuration(startedAt, expiredAt), [expiredAt, startedAt]);
|
|
36
|
+
const [progress, setProgress] = useState(getConsumptionProgress(startedAt, expiredAt));
|
|
38
37
|
useEffect(() => {
|
|
39
38
|
if (onUpdate) {
|
|
40
39
|
onUpdate(progress, duration);
|
|
@@ -42,7 +41,7 @@ export function ConsumptionBar(props) {
|
|
|
42
41
|
}, [onUpdate, progress, duration]);
|
|
43
42
|
useInterval(() => {
|
|
44
43
|
if (expiredAt) {
|
|
45
|
-
setProgress(
|
|
44
|
+
setProgress(getConsumptionProgress(startedAt, expiredAt));
|
|
46
45
|
}
|
|
47
46
|
}, refreshInterval);
|
|
48
47
|
const bg = expiredAt
|
|
@@ -57,7 +56,7 @@ export function ConsumptionBar(props) {
|
|
|
57
56
|
const title = duration
|
|
58
57
|
? `${formatTimeRemaining(secondsRemaining)} left - ${((progress / 100) * burntCredits).toFixed(2)} / ${burntCredits.toFixed(2)} credits`
|
|
59
58
|
: `Started at ${new Date(startedAt * 1000).toISOString()} - ${burntCredits.toFixed(2)} credits consumed`;
|
|
60
|
-
return (_jsx(_Fragment, { children: _jsx(Tooltip, { text: title,
|
|
59
|
+
return (_jsx(_Fragment, { children: _jsx(Tooltip, { text: title, children: _jsx(Button, { variant: "invisible", onClick: onClick, onKeyDown: onClick
|
|
61
60
|
? event => {
|
|
62
61
|
if (event.key === 'Enter' || event.key === 'Space') {
|
|
63
62
|
onClick();
|
|
@@ -5,8 +5,8 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
5
5
|
*/
|
|
6
6
|
import { useState, useEffect } from 'react';
|
|
7
7
|
import { Box } from '@datalayer/primer-addons';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { useNavigate } from '../../hooks/useNavigate';
|
|
9
|
+
import { ConsumptionBar } from './ConsumptionBar';
|
|
10
10
|
/**
|
|
11
11
|
* Credits indicator component.
|
|
12
12
|
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the total consumption duration in seconds.
|
|
3
|
+
* When `expiredAt` is undefined, duration is elapsed time since `startedAt`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getConsumptionDuration(startedAt: number, expiredAt?: number, nowSeconds?: number): number;
|
|
6
|
+
/**
|
|
7
|
+
* Compute credit consumption progress (0-100).
|
|
8
|
+
* Mirrors ConsumptionBar logic:
|
|
9
|
+
* - if no `expiredAt`, progress is 100
|
|
10
|
+
* - otherwise progress is elapsed / duration clamped to [0, 100]
|
|
11
|
+
*/
|
|
12
|
+
export declare function getConsumptionProgress(startedAt: number, expiredAt?: number, nowSeconds?: number): number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compute the total consumption duration in seconds.
|
|
7
|
+
* When `expiredAt` is undefined, duration is elapsed time since `startedAt`.
|
|
8
|
+
*/
|
|
9
|
+
export function getConsumptionDuration(startedAt, expiredAt, nowSeconds = Date.now() / 1000) {
|
|
10
|
+
if (expiredAt !== undefined) {
|
|
11
|
+
return Math.max(0, expiredAt - startedAt);
|
|
12
|
+
}
|
|
13
|
+
return Math.max(0, nowSeconds - startedAt);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute credit consumption progress (0-100).
|
|
17
|
+
* Mirrors ConsumptionBar logic:
|
|
18
|
+
* - if no `expiredAt`, progress is 100
|
|
19
|
+
* - otherwise progress is elapsed / duration clamped to [0, 100]
|
|
20
|
+
*/
|
|
21
|
+
export function getConsumptionProgress(startedAt, expiredAt, nowSeconds = Date.now() / 1000) {
|
|
22
|
+
if (expiredAt === undefined) {
|
|
23
|
+
return 100;
|
|
24
|
+
}
|
|
25
|
+
const duration = getConsumptionDuration(startedAt, expiredAt, nowSeconds);
|
|
26
|
+
if (duration <= 0) {
|
|
27
|
+
return 100;
|
|
28
|
+
}
|
|
29
|
+
const progress = ((nowSeconds - startedAt) / duration) * 100;
|
|
30
|
+
return Math.min(Math.max(0, progress), 100);
|
|
31
|
+
}
|