@datalayer/core
Version:
[](https://datalayer.io)
170 lines (169 loc) • 8.78 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/*
* Copyright (c) 2023-2025 Datalayer, Inc.
* Distributed under the terms of the Modified BSD License.
*/
import { useCallback, useEffect, useState, } from 'react';
import { CameraIcon } from '@datalayer/icons-react';
import { ActionList, ActionMenu, Flash, FormControl, Select, Spinner, } from '@primer/react';
import { Dialog } from '@primer/react/experimental';
import { Box } from '@datalayer/primer-addons';
import { useToast } from '../../hooks';
import { createRuntimeSnapshot, getRuntimeSnapshots, loadBrowserRuntimeSnapshot, loadRuntimeSnapshot, } from '../../stateful/runtimes';
import { useRuntimesStore } from '../../state';
import { createRuntimeSnapshotName } from '../../utils';
/**
* Runtime Snapshot menu component.
*/
export function RuntimeSnapshotMenu({ children, connection, podName, multiServiceManager, disabled = false, }) {
const { addRuntimeSnapshot, runtimesRunUrl, runtimeSnapshots, setRuntimeSnapshots, } = useRuntimesStore();
const { enqueueToast, trackAsyncTask } = useToast();
const [openLoadDialog, setOpenLoadDialog] = useState(false);
const [loadingRuntimeSnapshot, setLoadingRuntimeSnapshot] = useState(false);
const [takingRuntimeSnapshot, setTakingRuntimeSnapshot] = useState(false);
const [selection, setSelection] = useState(runtimeSnapshots[0]?.id ?? '');
const [error, setError] = useState();
useEffect(() => {
getRuntimeSnapshots()
.then(snapshots => {
setRuntimeSnapshots(snapshots);
if (!selection && snapshots.length > 0) {
setSelection(snapshots[0].id);
}
})
.catch(reason => {
console.error(`Failed to fetch remote kernel snapshots; ${reason}`);
});
}, [runtimesRunUrl]);
const onLoadRuntimeSnapshot = useCallback(() => {
setError(undefined);
setOpenLoadDialog(true);
}, []);
const onRuntimeSnapshotChanged = useCallback(event => {
setSelection(event.target.value);
}, []);
const onLoadRuntimeSnapshotSubmit = useCallback(async ({ id, connection, podName, }) => {
if (podName) {
await loadRuntimeSnapshot({ id: podName, from: id });
enqueueToast(`Runtime snapshot ${podName} is loaded.`, {
variant: 'success',
});
}
else if (connection) {
await loadBrowserRuntimeSnapshot({ connection, id });
enqueueToast(`Runtime snapshot ${id} is loaded.`, {
variant: 'success',
});
}
}, []);
const onTakeRuntimeSnapshot = useCallback(async () => {
try {
setTakingRuntimeSnapshot(true);
let snapshot;
let task;
let ref = '';
let snapshotName = '';
if (podName && multiServiceManager?.remote) {
snapshotName = createRuntimeSnapshotName('cloud');
task = multiServiceManager.remote.runtimesManager.snapshotRuntime({
podName,
name: snapshotName,
description: snapshotName,
stop: false,
});
ref = podName.split('-', 2).reverse()[0];
task.then(s => {
snapshot = s;
});
}
else if (connection && multiServiceManager?.browser) {
const model = connection.model;
ref = model.id;
snapshotName = createRuntimeSnapshotName('browser');
let isPending = true;
task = createRuntimeSnapshot({
connection: multiServiceManager.browser.kernels.connectTo({
model,
}),
metadata: { filename: `${snapshotName}.data` },
onUploadProgress: () => {
if (isPending) {
isPending = false;
// Get the kernel snapshot uid.
getRuntimeSnapshots().then(snapshots => {
snapshot = snapshots.find(s => s.name === snapshotName);
});
}
},
});
}
if (task) {
trackAsyncTask(task, {
error: {
message: (reason, data) => {
const msg = reason === 'Empty snapshot'
? `Runtime ${ref} will not be snapshotted as it does not contain any serializable state.`
: `Failed to snapshot runtime ${ref} - ${reason}`;
return msg;
},
},
pending: { message: `Taking a snapshot of runtime ${ref}…` },
success: {
message: () => `Runtime ${ref} successfully snapshotted as ${snapshotName}.`,
},
});
await task;
if (snapshot) {
addRuntimeSnapshot(snapshot);
}
}
}
finally {
setTakingRuntimeSnapshot(false);
}
}, [connection, podName, multiServiceManager]);
return (_jsxs(_Fragment, { children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { leadingVisual: CameraIcon, variant: "invisible", size: "small", disabled: loadingRuntimeSnapshot || takingRuntimeSnapshot || disabled, children: children }), _jsx(ActionMenu.Overlay, { children: _jsxs(ActionList, { children: [_jsx(ActionList.Item, { onSelect: onLoadRuntimeSnapshot, disabled: loadingRuntimeSnapshot || runtimeSnapshots.length === 0, children: "Load a runtime snapshot\u2026" }), _jsx(ActionList.Item, { onSelect: onTakeRuntimeSnapshot, disabled: takingRuntimeSnapshot, children: "Take a runtime snapshot" })] }) })] }), openLoadDialog && (_jsx(Dialog, { title: _jsx("span", { style: { color: 'var(--fgColor-default)' }, children: "Choose a runtime snapshot to load" }), onClose: () => {
setOpenLoadDialog(false);
}, footerButtons: [
{
buttonType: 'default',
content: 'Cancel',
onClick: event => {
if (!event.defaultPrevented) {
event.preventDefault();
setOpenLoadDialog(false);
}
},
},
{
buttonType: 'primary',
content: loadingRuntimeSnapshot ? (_jsx(Spinner, { size: "small" })) : ('Load'),
disabled: loadingRuntimeSnapshot,
onClick: async (event) => {
if (!event.defaultPrevented) {
event.preventDefault();
setLoadingRuntimeSnapshot(true);
try {
setError(undefined);
const snapshot = runtimeSnapshots.find(s => s.id === selection);
if (snapshot && (connection || podName)) {
await onLoadRuntimeSnapshotSubmit({
connection,
id: snapshot.id,
podName,
});
}
else {
setError('No runtime snapshot found.');
}
}
finally {
setLoadingRuntimeSnapshot(false);
setOpenLoadDialog(false);
}
}
},
autoFocus: true,
},
], children: _jsxs(Box, { as: "form", children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Snapshot" }), _jsx(Select, { name: "snapshot", value: selection, onChange: onRuntimeSnapshotChanged, block: true, children: runtimeSnapshots.map(s => (_jsx(Select.Option, { value: s.id, children: s.name ? `${s.name} (${s.id})` : s.id }, s.id))) })] }), error && _jsx(Flash, { variant: "danger", children: error })] }) }))] }));
}