@gen3/workspaces 0.12.45 → 0.12.46

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.
@@ -1,9 +1,8 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import React, { useCallback, useMemo, useState, useRef, useEffect, createContext, useImperativeHandle } from 'react';
3
- import { mergeDefaultTailwindClassnames, convertSecondsToMilliseconds, InfoRolloverButton, OverflowTooltippedLabel, PanelErrorBoundary, ProtectedContent, NavPageLayout, ContentSource, getNavPageLayoutPropsFromConfig } from '@gen3/frontend';
3
+ import { mergeDefaultTailwindClassnames, ACTIVITY_CHANNEL, convertSecondsToMilliseconds, InfoRolloverButton, OverflowTooltippedLabel, PanelErrorBoundary, ProtectedContent, NavPageLayout, ContentSource, getNavPageLayoutPropsFromConfig } from '@gen3/frontend';
4
4
  import { Card, Stack, Badge, Text, List, Group, Tooltip, Button, LoadingOverlay, Alert, Loader, Divider, Select, ActionIcon, TextInput, Textarea, Table, Scroller, Tabs, Center } from '@mantine/core';
5
5
  import { Icon } from '@iconify-icon/react';
6
- import { getCookie } from 'cookies-next';
7
6
  import { GEN3_API, GEN3_WORKSPACE_API, gen3Api, useCoreSelector, selectJEGActiveWorkspaceStatus, useCoreDispatch, setJEGActiveWorkspaceStatus, WorkspaceStatus, setJEGRequestedWorkspaceStatus, RequestedWorkspaceStatus, setJEGActiveWorkspaceId, isFetchBaseQueryError, selectJEGRequestedWorkspaceStatus, selectJEGRequestedWorkspaceStatusTimestamp, isTimeGreaterThan, formatUptimeInMinutes, selectJEGKernelIds, removeManyJEGActiveKernels, upsertManyJEGActiveKernels, normalizeRtkError, selectAllJEGKernels, addJEGActiveKernel, selectWorkspaceTier, setWorkspaceTier, selectWorkspaceFullscreen, setWorkspaceFullscreen, useGetDictionaryFromUrlQuery, useUserAuth, GEN3_COMMONS_NAME } from '@gen3/core';
8
7
  import { useDeepCompareEffect } from 'use-deep-compare';
9
8
  import { notifications } from '@mantine/notifications';
@@ -175,13 +174,74 @@ const FreeWorkspace = ({ assetBaseUrl = '/api/workspace-assets/free', onReady, o
175
174
  const [loading, setLoading] = useState(true);
176
175
  const [loadError, setLoadError] = useState(false);
177
176
  const [retryCount, setRetryCount] = useState(0);
177
+ const [jupyterReady, setJupyterReady] = useState(false);
178
178
  const iframeRef = useRef(null);
179
+ const broadcastChannelRef = useRef(null);
179
180
  const url = `${assetBaseUrl}/lab/index.html`;
180
181
  const handleRetry = useCallback(()=>{
181
182
  setLoadError(false);
182
183
  setLoading(true);
183
184
  setRetryCount((n)=>n + 1);
184
185
  }, []);
186
+ // set up BroadcastChannel
187
+ useEffect(()=>{
188
+ if (!broadcastChannelRef?.current) {
189
+ broadcastChannelRef.current = new BroadcastChannel(ACTIVITY_CHANNEL);
190
+ }
191
+ return ()=>{
192
+ if (broadcastChannelRef.current) {
193
+ broadcastChannelRef.current.close();
194
+ broadcastChannelRef.current = null;
195
+ }
196
+ };
197
+ }, []);
198
+ // Handle setting up iframe event listeners
199
+ useEffect(()=>{
200
+ if (!jupyterReady) return;
201
+ const updateUserActivity = ()=>{
202
+ const timestamp = Date.now();
203
+ if (broadcastChannelRef.current) {
204
+ broadcastChannelRef.current.postMessage({
205
+ type: 'activity-update',
206
+ timestamp
207
+ });
208
+ }
209
+ };
210
+ const iframe = iframeRef.current;
211
+ if (!iframe?.contentWindow) {
212
+ return;
213
+ }
214
+ try {
215
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
216
+ if (iframeDoc) {
217
+ iframeDoc.addEventListener('mousedown', updateUserActivity);
218
+ iframeDoc.addEventListener('keypress', updateUserActivity);
219
+ iframeDoc.addEventListener('scroll', updateUserActivity);
220
+ iframeDoc.addEventListener('click', updateUserActivity);
221
+ iframeDoc.addEventListener('touchstart', updateUserActivity);
222
+ }
223
+ } catch (error) {
224
+ console.warn('Cannot access iframe content - cross-origin restriction', error);
225
+ }
226
+ return ()=>{
227
+ if (iframe?.contentWindow) {
228
+ try {
229
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
230
+ if (iframeDoc) {
231
+ iframeDoc.removeEventListener('mousedown', updateUserActivity);
232
+ iframeDoc.removeEventListener('keypress', updateUserActivity);
233
+ iframeDoc.removeEventListener('scroll', updateUserActivity);
234
+ iframeDoc.removeEventListener('click', updateUserActivity);
235
+ iframeDoc.removeEventListener('touchstart', updateUserActivity);
236
+ }
237
+ } catch {
238
+ // Ignore cleanup errors
239
+ }
240
+ }
241
+ };
242
+ }, [
243
+ jupyterReady
244
+ ]);
185
245
  if (loadError) {
186
246
  return /*#__PURE__*/ jsx("div", {
187
247
  children: /*#__PURE__*/ jsxs(Card, {
@@ -233,6 +293,7 @@ const FreeWorkspace = ({ assetBaseUrl = '/api/workspace-assets/free', onReady, o
233
293
  className: "min-h-0 flex-1 border-0",
234
294
  onLoad: ()=>{
235
295
  setLoading(false);
296
+ setJupyterReady(true);
236
297
  onReady?.();
237
298
  },
238
299
  onError: ()=>{
@@ -632,7 +693,9 @@ const workspaceShutdownAlertLimit = 30000; // 5 minutes: 5 * 60 * 1000 TODO Figu
632
693
  ]);
633
694
  useDeepCompareEffect(()=>{
634
695
  // TODO: add better error logging
635
- console.log('useJEGWorkspaceResourceMonitor', error);
696
+ if (error) {
697
+ console.log('useJEGWorkspaceResourceMonitor', error);
698
+ }
636
699
  }, [
637
700
  error
638
701
  ]);
@@ -994,6 +1057,7 @@ const MicroContainerReduxPanel = ({ compact = false })=>{
994
1057
  const [retryCount, setRetryCount] = useState(0);
995
1058
  const [jupyterReady, setJupyterReady] = useState(false);
996
1059
  const iframeRef = useRef(null);
1060
+ const broadcastChannelRef = useRef(null);
997
1061
  const { status } = useMicroContainerReduxContext();
998
1062
  const normalizedBase = assetBaseUrl.replace(/\/$/, '');
999
1063
  // Scoped notebook path for session isolation
@@ -1013,11 +1077,71 @@ const MicroContainerReduxPanel = ({ compact = false })=>{
1013
1077
  const userHash = btoa(userId || 'anonymous').substring(0, 8);
1014
1078
  scopedNotebookPath = `/workspace/${userHash}/${notebookName}.ipynb`;
1015
1079
  }
1016
- if (process.env.NODE_ENV === 'development') {
1017
- // NOTE: This cookie can only be accessed from the client side
1018
- // in development mode. Otherwise, the cookie is set as httpOnly
1019
- getCookie('credentials_token');
1020
- }
1080
+ // let accessToken = undefined;
1081
+ // if (process.env.NODE_ENV === 'development') {
1082
+ // // NOTE: This cookie can only be accessed from the client side
1083
+ // // in development mode. Otherwise, the cookie is set as httpOnly
1084
+ // accessToken = getCookie('credentials_token');
1085
+ // }
1086
+ // set up BroadcastChannel
1087
+ useEffect(()=>{
1088
+ if (!broadcastChannelRef?.current) {
1089
+ broadcastChannelRef.current = new BroadcastChannel(ACTIVITY_CHANNEL);
1090
+ }
1091
+ return ()=>{
1092
+ if (broadcastChannelRef.current) {
1093
+ broadcastChannelRef.current.close();
1094
+ broadcastChannelRef.current = null;
1095
+ }
1096
+ };
1097
+ }, []);
1098
+ // Handle setting up iframe event listeners
1099
+ useEffect(()=>{
1100
+ if (!jupyterReady) return;
1101
+ const updateUserActivity = ()=>{
1102
+ const timestamp = Date.now();
1103
+ if (broadcastChannelRef.current) {
1104
+ broadcastChannelRef.current.postMessage({
1105
+ type: 'activity-update',
1106
+ timestamp
1107
+ });
1108
+ }
1109
+ };
1110
+ const iframe = iframeRef.current;
1111
+ if (!iframe?.contentWindow) {
1112
+ return;
1113
+ }
1114
+ try {
1115
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
1116
+ if (iframeDoc) {
1117
+ iframeDoc.addEventListener('mousedown', updateUserActivity);
1118
+ iframeDoc.addEventListener('keypress', updateUserActivity);
1119
+ iframeDoc.addEventListener('scroll', updateUserActivity);
1120
+ iframeDoc.addEventListener('click', updateUserActivity);
1121
+ iframeDoc.addEventListener('touchstart', updateUserActivity);
1122
+ }
1123
+ } catch (error) {
1124
+ console.warn('Cannot access iframe content - cross-origin restriction', error);
1125
+ }
1126
+ return ()=>{
1127
+ if (iframe?.contentWindow) {
1128
+ try {
1129
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
1130
+ if (iframeDoc) {
1131
+ iframeDoc.removeEventListener('mousedown', updateUserActivity);
1132
+ iframeDoc.removeEventListener('keypress', updateUserActivity);
1133
+ iframeDoc.removeEventListener('scroll', updateUserActivity);
1134
+ iframeDoc.removeEventListener('click', updateUserActivity);
1135
+ iframeDoc.removeEventListener('touchstart', updateUserActivity);
1136
+ }
1137
+ } catch {
1138
+ // Ignore cleanup errors
1139
+ }
1140
+ }
1141
+ };
1142
+ }, [
1143
+ jupyterReady
1144
+ ]);
1021
1145
  /* ---- Wait for JupyterLite app inside the iframe --------------- */ useEffect(()=>{
1022
1146
  if (!iframeRef.current || loadError) return;
1023
1147
  if (jupyterReady) return;
@@ -1209,6 +1333,7 @@ const MicroContainerReduxPanel = ({ compact = false })=>{
1209
1333
  className: "min-h-0 flex-1 border-0",
1210
1334
  onLoad: ()=>{
1211
1335
  setLoading(false);
1336
+ setJupyterReady(true);
1212
1337
  },
1213
1338
  onError: ()=>{
1214
1339
  setLoadError(true);