@gen3/workspaces 0.12.55 → 0.12.57
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/dist/dts/api/hatcheryApiHandler.d.ts.map +1 -1
- package/dist/dts/api/hatcheryApiHandler.js +3 -0
- package/dist/dts/api/hatcheryApiHandler.js.map +1 -1
- package/dist/dts/api/kernelApiHandler.d.ts.map +1 -1
- package/dist/dts/api/kernelApiHandler.js +0 -2
- package/dist/dts/api/kernelApiHandler.js.map +1 -1
- package/dist/dts/api/workspaceAssetsApi.d.ts +17 -0
- package/dist/dts/api/workspaceAssetsApi.d.ts.map +1 -1
- package/dist/dts/api/workspaceAssetsApi.js +115 -123
- package/dist/dts/api/workspaceAssetsApi.js.map +1 -1
- package/dist/dts/auth/auth.js +1 -1
- package/dist/dts/auth/auth.js.map +1 -1
- package/dist/dts/components/AutoFitIframe.d.ts +1 -1
- package/dist/dts/components/AutoFitIframe.d.ts.map +1 -1
- package/dist/dts/components/CodingAssistantPanel/CodingAssistantPanel.d.ts +2 -1
- package/dist/dts/components/CodingAssistantPanel/CodingAssistantPanel.d.ts.map +1 -1
- package/dist/dts/components/CodingAssistantPanel/CodingAssistantPanel.js +0 -1
- package/dist/dts/components/CodingAssistantPanel/CodingAssistantPanel.js.map +1 -1
- package/dist/dts/components/CodingAssistantPanel/CodingAssistantPanel.stories.d.ts +1 -1
- package/dist/dts/components/CodingAssistantPanel/MessageBubble.d.ts +3 -2
- package/dist/dts/components/CodingAssistantPanel/MessageBubble.d.ts.map +1 -1
- package/dist/dts/components/CodingAssistantPanel/MessageBubble.js +3 -3
- package/dist/dts/components/CodingAssistantPanel/MessageBubble.js.map +1 -1
- package/dist/dts/components/CodingAssistantPanel/MessageBubble.stories.d.ts +1 -1
- package/dist/dts/components/CompactDictionaryPanel/CompactDictionaryPanel.d.ts +2 -1
- package/dist/dts/components/CompactDictionaryPanel/CompactDictionaryPanel.d.ts.map +1 -1
- package/dist/dts/components/CompactDictionaryPanel/CompactDictionaryPanel.js.map +1 -1
- package/dist/dts/components/CompactDictionaryPanel/CompactDictionaryPanel.stories.d.ts +1 -1
- package/dist/dts/components/CompactDictionaryPanel/FieldLinkCountBadge.d.ts +2 -1
- package/dist/dts/components/CompactDictionaryPanel/FieldLinkCountBadge.d.ts.map +1 -1
- package/dist/dts/components/DataToolsPanel.d.ts +2 -1
- package/dist/dts/components/DataToolsPanel.d.ts.map +1 -1
- package/dist/dts/components/DataToolsPanel.stories.d.ts +1 -1
- package/dist/dts/components/DataToolsRail.d.ts +2 -1
- package/dist/dts/components/DataToolsRail.d.ts.map +1 -1
- package/dist/dts/components/DataToolsRail.stories.d.ts +1 -1
- package/dist/dts/components/HorizontalAccordian.d.ts +2 -1
- package/dist/dts/components/HorizontalAccordian.d.ts.map +1 -1
- package/dist/dts/components/InfrastructureRightPanel.d.ts +2 -1
- package/dist/dts/components/InfrastructureRightPanel.d.ts.map +1 -1
- package/dist/dts/components/InfrastructureRightPanel.stories.d.ts +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelInfoPanel.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelInfoPanel.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelInfoPanel.js +2 -2
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelInfoPanel.js.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelsPanel.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelsPanel.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelsPanel.js +12 -4
- package/dist/dts/components/KernelLifecyclePanel/ActiveKernelsPanel.js.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ConnectionStatusBadge.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/ConnectionStatusBadge.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ConnectionStatusBadge.js +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ConnectionStatusBadge.js.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/ConnectionStatusBadge.stories.d.ts +1 -1
- package/dist/dts/components/KernelLifecyclePanel/GatewayConnectionPanel.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/GatewayConnectionPanel.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/GatewayConnectionPanel.js +5 -1
- package/dist/dts/components/KernelLifecyclePanel/GatewayConnectionPanel.js.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelLifecyclePanel.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelLifecyclePanel.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelLifecyclePanel.js +2 -2
- package/dist/dts/components/KernelLifecyclePanel/KernelLifecyclePanel.js.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelLifecyclePanel.stories.d.ts +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelSelector.d.ts +2 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelSelector.d.ts.map +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelSelector.js +1 -1
- package/dist/dts/components/KernelLifecyclePanel/KernelSelector.js.map +1 -1
- package/dist/dts/components/MicroContainerPanel.stories.d.ts +3 -4
- package/dist/dts/components/MicroContainerPanel.stories.d.ts.map +1 -1
- package/dist/dts/components/MicroContainerReduxPanel.d.ts +2 -1
- package/dist/dts/components/MicroContainerReduxPanel.d.ts.map +1 -1
- package/dist/dts/components/MicroContainerReduxPanel.js +1 -1
- package/dist/dts/components/MicroContainerReduxPanel.js.map +1 -1
- package/dist/dts/components/TierSelectorLanding.d.ts +2 -1
- package/dist/dts/components/TierSelectorLanding.d.ts.map +1 -1
- package/dist/dts/components/TierSelectorLanding.stories.d.ts +1 -1
- package/dist/dts/components/UpgradeActionsPanel.d.ts +2 -1
- package/dist/dts/components/UpgradeActionsPanel.d.ts.map +1 -1
- package/dist/dts/components/UpgradeActionsPanel.stories.d.ts +1 -1
- package/dist/dts/components/WorkspaceTierCard.d.ts +2 -1
- package/dist/dts/components/WorkspaceTierCard.d.ts.map +1 -1
- package/dist/dts/components/WorkspaceTierCard.stories.d.ts +1 -1
- package/dist/dts/core/hatcheryApi.js +1 -1
- package/dist/dts/core/hatcheryApi.js.map +1 -1
- package/dist/dts/providers/JEGWorkspaceProvider.d.ts +2 -2
- package/dist/dts/providers/JEGWorkspaceProvider.d.ts.map +1 -1
- package/dist/dts/providers/MicroContainerReduxProvider.d.ts +2 -2
- package/dist/dts/providers/MicroContainerReduxProvider.d.ts.map +1 -1
- package/dist/dts/providers/WorkspacesGatewayProvider.d.ts +1 -1
- package/dist/dts/providers/WorkspacesGatewayProvider.d.ts.map +1 -1
- package/dist/dts/providers/useMicroContainerRedux.d.ts.map +1 -1
- package/dist/dts/providers/useMicroContainerRedux.js +4 -1
- package/dist/dts/providers/useMicroContainerRedux.js.map +1 -1
- package/dist/dts/server/kernelLifecycleProxy.js +2 -2
- package/dist/dts/server/kernelLifecycleProxy.js.map +1 -1
- package/dist/dts/server/withJupyterWorkspaces.js +1 -1
- package/dist/dts/server/withJupyterWorkspaces.js.map +1 -1
- package/dist/dts/server/workspaceAssetsHandler.d.ts +20 -1
- package/dist/dts/server/workspaceAssetsHandler.d.ts.map +1 -1
- package/dist/dts/server/workspaceAssetsHandler.js +71 -20
- package/dist/dts/server/workspaceAssetsHandler.js.map +1 -1
- package/dist/dts/workspace/WorkspaceCenter.d.ts +2 -1
- package/dist/dts/workspace/WorkspaceCenter.d.ts.map +1 -1
- package/dist/dts/workspace/WorkspaceLayout/SettingsPanel.d.ts +2 -1
- package/dist/dts/workspace/WorkspaceLayout/SettingsPanel.d.ts.map +1 -1
- package/dist/dts/workspace/WorkspaceLayout/ToolsPanel.d.ts +2 -1
- package/dist/dts/workspace/WorkspaceLayout/ToolsPanel.d.ts.map +1 -1
- package/dist/dts/workspace/WorkspaceLayout/WorkspaceLayout.d.ts +2 -2
- package/dist/dts/workspace/WorkspaceLayout/WorkspaceLayout.d.ts.map +1 -1
- package/dist/dts/workspace/tiers/FreeWorkspace.d.ts +2 -1
- package/dist/dts/workspace/tiers/FreeWorkspace.d.ts.map +1 -1
- package/dist/dts/workspace/tiers/FreeWorkspace.js.map +1 -1
- package/dist/index/cjs/index.js +41 -34
- package/dist/index/cjs/index.js.map +1 -1
- package/dist/index/esm/index.js +41 -34
- package/dist/index/esm/index.js.map +1 -1
- package/dist/jupyterlite-builds/free-private/requirements.txt +1 -1
- package/dist/jupyterlite-builds/remote-private/requirements.txt +1 -1
- package/dist/server/cjs/server.js +140 -131
- package/dist/server/cjs/server.js.map +1 -1
- package/dist/server/esm/server.js +140 -131
- package/dist/server/esm/server.js.map +1 -1
- package/dist/server.d.ts +38 -0
- package/jupyterlite-builds/free-private/requirements.txt +1 -1
- package/jupyterlite-builds/remote-private/requirements.txt +1 -1
- package/package.json +9 -8
- package/dist/dts/hooks/useMicroContainer.d.ts +0 -1
- package/dist/dts/hooks/useMicroContainer.d.ts.map +0 -1
- package/dist/dts/hooks/useMicroContainer.js +0 -319
- package/dist/dts/hooks/useMicroContainer.js.map +0 -1
|
@@ -35,6 +35,7 @@ const MIME_TYPES = {
|
|
|
35
35
|
'.map': 'application/json; charset=utf-8',
|
|
36
36
|
'.ipynb': 'application/json; charset=utf-8'
|
|
37
37
|
};
|
|
38
|
+
const DEFAULT_FULL_THEMES_URL = '/workspace-api/workspace-assets/remote/build/themes';
|
|
38
39
|
const REMOTE_DISABLED_EXTENSIONS = [
|
|
39
40
|
'@jupyterlite/services-extension:config-section-manager',
|
|
40
41
|
'@jupyterlite/services-extension:default-drive',
|
|
@@ -55,10 +56,26 @@ const ASSETS_ROOT_PATH = process.env.JUPYTER_ASSETS_ROOT_PATH || '/gen3/jupyter-
|
|
|
55
56
|
function defaultAssetRoot() {
|
|
56
57
|
return ASSETS_ROOT_PATH.startsWith('/') ? ASSETS_ROOT_PATH : nodePath.join(process.cwd(), ASSETS_ROOT_PATH);
|
|
57
58
|
}
|
|
59
|
+
const DEFAULT_REMOTE_BRANDING = {
|
|
60
|
+
appName: 'JupyterLite',
|
|
61
|
+
faviconUrl: '/favicon2.ico'
|
|
62
|
+
};
|
|
63
|
+
/** Escape characters that are special inside an HTML text node / attribute. */ function escapeHtml(text) {
|
|
64
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Replace the document title with the configured page title (or app name).
|
|
68
|
+
* Values originate from a server-side config file, but they are escaped so a
|
|
69
|
+
* stray `<` in a commons name cannot break out of the title element.
|
|
70
|
+
*/ function replaceTitle(html, branding) {
|
|
71
|
+
const title = branding.pageTitle ?? branding.appName;
|
|
72
|
+
if (!title) return html;
|
|
73
|
+
return html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
|
|
74
|
+
}
|
|
58
75
|
/**
|
|
59
76
|
* Resolve the origin from forwarding headers, with referer fallback.
|
|
60
77
|
* Handles proxies that drop non-standard ports from the Host header.
|
|
61
|
-
*/ function resolveOrigin
|
|
78
|
+
*/ function resolveOrigin(req) {
|
|
62
79
|
const forwardedProto = (Array.isArray(req.headers['x-forwarded-proto']) ? req.headers['x-forwarded-proto'][0] : req.headers['x-forwarded-proto']) || 'http';
|
|
63
80
|
const forwardedHost = Array.isArray(req.headers['x-forwarded-host']) ? req.headers['x-forwarded-host'][0] : req.headers['x-forwarded-host'];
|
|
64
81
|
const hostHeader = Array.isArray(req.headers.host) ? req.headers.host[0] : req.headers.host;
|
|
@@ -86,11 +103,10 @@ function defaultAssetRoot() {
|
|
|
86
103
|
host: resolvedHost
|
|
87
104
|
};
|
|
88
105
|
}
|
|
89
|
-
function injectRemoteConfig(html, req,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
})
|
|
93
|
-
const { proto, host } = resolveOrigin$1(req);
|
|
106
|
+
function injectRemoteConfig(html, req, config) {
|
|
107
|
+
const { gatewayBaseUrl, disabledExtensions, fullThemesUrl, remoteKernelsPath } = config;
|
|
108
|
+
const branding = config.branding ?? DEFAULT_REMOTE_BRANDING;
|
|
109
|
+
const { proto, host } = resolveOrigin(req);
|
|
94
110
|
const absoluteGatewayBase = `${proto}://${host}${gatewayBaseUrl}`;
|
|
95
111
|
// Inject server-side JEG token — overrides any client-supplied auth header.
|
|
96
112
|
// This ensures calls from JupyterLite's own serverconnection.js are also authenticated.
|
|
@@ -104,14 +120,20 @@ function injectRemoteConfig(html, req, gatewayBaseUrl, disabledExtensions, brand
|
|
|
104
120
|
remoteBaseUrl: absoluteGatewayBase,
|
|
105
121
|
remoteToken: accessToken,
|
|
106
122
|
disabledExtensions,
|
|
107
|
-
fullThemesUrl
|
|
123
|
+
fullThemesUrl,
|
|
108
124
|
// Required so JupyterLab sets window.jupyterapp, which the parent
|
|
109
125
|
// frame uses for polling readiness and attaching remote kernels.
|
|
110
126
|
exposeAppInBrowser: 'true'
|
|
111
127
|
};
|
|
128
|
+
if (remoteKernelsPath) {
|
|
129
|
+
// Absolute so JupyterLite does not resolve kernel ops relative to
|
|
130
|
+
// /workspace-api/workspace-assets/remote/. Routing all kernel traffic
|
|
131
|
+
// through jeg-proxy lets JupyterLite see merged container + JEG kernelspecs.
|
|
132
|
+
configData.remoteKernelsBaseUrl = `${proto}://${host}${remoteKernelsPath}`;
|
|
133
|
+
}
|
|
112
134
|
if (branding?.appName) configData.appName = branding.appName;
|
|
113
135
|
if (branding?.faviconUrl) configData.faviconUrl = branding.faviconUrl;
|
|
114
|
-
|
|
136
|
+
const result = html.replace(/(<script\s+id="jupyter-config-data"[^>]*>)\s*(\{[^]*?\})\s*(<\/script>)/, (_match, openTag, existingJson, closeTag)=>{
|
|
115
137
|
let existing = {};
|
|
116
138
|
try {
|
|
117
139
|
existing = JSON.parse(existingJson);
|
|
@@ -133,13 +155,10 @@ function injectRemoteConfig(html, req, gatewayBaseUrl, disabledExtensions, brand
|
|
|
133
155
|
}
|
|
134
156
|
return `${openTag}${JSON.stringify(merged)}${closeTag}`;
|
|
135
157
|
});
|
|
136
|
-
|
|
137
|
-
result = result.replace(/<title>[^<]*<\/title>/, `<title>${branding.appName}</title>`);
|
|
138
|
-
}
|
|
139
|
-
return result;
|
|
158
|
+
return replaceTitle(result, branding);
|
|
140
159
|
}
|
|
141
160
|
function injectBranding(html, branding) {
|
|
142
|
-
|
|
161
|
+
const result = html.replace(/(<script\s+id="jupyter-config-data"[^>]*>)\s*(\{[^]*?\})\s*(<\/script>)/, (_match, openTag, existingJson, closeTag)=>{
|
|
143
162
|
let existing = {};
|
|
144
163
|
try {
|
|
145
164
|
existing = JSON.parse(existingJson);
|
|
@@ -155,10 +174,7 @@ function injectBranding(html, branding) {
|
|
|
155
174
|
if (branding.faviconUrl) existing.faviconUrl = branding.faviconUrl;
|
|
156
175
|
return `${openTag}${JSON.stringify(existing)}${closeTag}`;
|
|
157
176
|
});
|
|
158
|
-
|
|
159
|
-
result = result.replace(/<title>[^<]*<\/title>/, `<title>${branding.appName}</title>`);
|
|
160
|
-
}
|
|
161
|
-
return result;
|
|
177
|
+
return replaceTitle(result, branding);
|
|
162
178
|
}
|
|
163
179
|
// ---------- factory ----------
|
|
164
180
|
function createWorkspaceAssetsHandler(options) {
|
|
@@ -170,10 +186,18 @@ function createWorkspaceAssetsHandler(options) {
|
|
|
170
186
|
...REMOTE_DISABLED_EXTENSIONS,
|
|
171
187
|
...options.additionalDisabledExtensions
|
|
172
188
|
] : REMOTE_DISABLED_EXTENSIONS;
|
|
173
|
-
const branding = options?.appName || options?.faviconUrl ? {
|
|
189
|
+
const branding = options?.appName || options?.faviconUrl || options?.pageTitle ? {
|
|
174
190
|
appName: options.appName,
|
|
175
|
-
faviconUrl: options.faviconUrl
|
|
191
|
+
faviconUrl: options.faviconUrl,
|
|
192
|
+
pageTitle: options.pageTitle
|
|
176
193
|
} : undefined;
|
|
194
|
+
const remoteInjectionConfig = {
|
|
195
|
+
gatewayBaseUrl,
|
|
196
|
+
disabledExtensions,
|
|
197
|
+
fullThemesUrl: options?.fullThemesUrl ?? DEFAULT_FULL_THEMES_URL,
|
|
198
|
+
remoteKernelsPath: options?.remoteKernelsPath,
|
|
199
|
+
branding
|
|
200
|
+
};
|
|
177
201
|
// Build the allowed-file sets once at factory time (not per-request) to
|
|
178
202
|
// avoid unbounded I/O on every incoming request (CWE-770).
|
|
179
203
|
const walkDir = (dir, set)=>{
|
|
@@ -266,7 +290,7 @@ function createWorkspaceAssetsHandler(options) {
|
|
|
266
290
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
267
291
|
try {
|
|
268
292
|
const html = fs.readFileSync(filePath, 'utf-8');
|
|
269
|
-
const injected = injectRemoteConfig(html, req,
|
|
293
|
+
const injected = injectRemoteConfig(html, req, remoteInjectionConfig);
|
|
270
294
|
res.status(200).send(injected);
|
|
271
295
|
} catch {
|
|
272
296
|
res.status(500).end('Internal server error');
|
|
@@ -300,7 +324,6 @@ function createWorkspaceAssetsHandler(options) {
|
|
|
300
324
|
stream.pipe(res);
|
|
301
325
|
};
|
|
302
326
|
}
|
|
303
|
-
/** Default handler — for one-liner re-exports */ createWorkspaceAssetsHandler();
|
|
304
327
|
|
|
305
328
|
const DEFAULT_WORKSPACE_ROUTES = [
|
|
306
329
|
'/Workspaces'
|
|
@@ -320,7 +343,7 @@ function withJupyterWorkspaces(nextConfig, options) {
|
|
|
320
343
|
webpack (config, ctx) {
|
|
321
344
|
config.resolve = config.resolve ?? {};
|
|
322
345
|
config.resolve.alias = {
|
|
323
|
-
...config.resolve.alias
|
|
346
|
+
...config.resolve.alias,
|
|
324
347
|
react: nodePath.dirname(require.resolve('react/package.json')),
|
|
325
348
|
'react-dom': nodePath.dirname(require.resolve('react-dom/package.json'))
|
|
326
349
|
};
|
|
@@ -575,7 +598,7 @@ function filterAndAnnotateSpecs(raw, policy) {
|
|
|
575
598
|
display_name: displayNames[specName] ?? rawSpec.spec.display_name,
|
|
576
599
|
// Inject billing metadata into spec.metadata (available to browser)
|
|
577
600
|
metadata: {
|
|
578
|
-
...rawSpec.spec.metadata
|
|
601
|
+
...rawSpec.spec.metadata,
|
|
579
602
|
costPerHour: costPerHour[specName] ?? 0,
|
|
580
603
|
nodeType: nodeType[specName] ?? 'unknown'
|
|
581
604
|
}
|
|
@@ -807,7 +830,7 @@ function resolveKernelRoute(method, actionSegments) {
|
|
|
807
830
|
const enrichedBody = {
|
|
808
831
|
...launchBody,
|
|
809
832
|
env: {
|
|
810
|
-
...launchBody.env
|
|
833
|
+
...launchBody.env,
|
|
811
834
|
GEN3_API_KEY_ID: fenceCreds.key_id,
|
|
812
835
|
GEN3_API_KEY_SECRET: fenceCreds.api_key,
|
|
813
836
|
NAMESPACE: process.env.NAMESPACE || 'jupyter-pods',
|
|
@@ -1021,125 +1044,112 @@ function createKernelProxyHandler(config) {
|
|
|
1021
1044
|
};
|
|
1022
1045
|
}
|
|
1023
1046
|
|
|
1024
|
-
const
|
|
1047
|
+
const CONFIG_ROOT = process.env.GEN3_FRONTEND_CONFIGURATION_ROOT || './config/';
|
|
1048
|
+
const CONFIG_FILENAME = 'workspaceAssets.json';
|
|
1049
|
+
const DEFAULT_OPTIONS = {
|
|
1025
1050
|
// Route JupyterLite remote-mode kernel WebSocket traffic through revproxy's
|
|
1026
1051
|
// /lw-workspace/proxy/ nginx block → ambassador-service (ExternalName → Emissary)
|
|
1027
1052
|
// → JEG. Next.js API routes cannot handle WebSocket upgrades, so we must
|
|
1028
1053
|
// bypass them and use the nginx path that has allow_upgrade + long timeouts.
|
|
1029
|
-
gatewayBaseUrl: '/lw-workspace/proxy/'
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1054
|
+
gatewayBaseUrl: '/lw-workspace/proxy/',
|
|
1055
|
+
// Route all kernel ops through jeg-proxy so JupyterLite sees merged Python3
|
|
1056
|
+
// (container) + JEG GPU kernelspecs. The proxy routes Python3 kernel
|
|
1057
|
+
// launches/channels to the container; GPU kernel launches are gated (403)
|
|
1058
|
+
// with a message to use the Kernel Panel.
|
|
1059
|
+
remoteKernelsPath: '/lw-workspace/proxy/jeg-proxy',
|
|
1060
|
+
// The remote tier attaches to a real kernel gateway, so JupyterLite's
|
|
1061
|
+
// in-browser Pyodide kernel must not be offered alongside it.
|
|
1062
|
+
additionalDisabledExtensions: [
|
|
1063
|
+
'@jupyterlite/pyodide-kernel-extension:kernel'
|
|
1064
|
+
],
|
|
1065
|
+
appName: GEN3_COMMONS_NAME
|
|
1066
|
+
};
|
|
1067
|
+
const STRING_OPTIONS = [
|
|
1068
|
+
'gatewayBaseUrl',
|
|
1069
|
+
'assetRoot',
|
|
1070
|
+
'appName',
|
|
1071
|
+
'faviconUrl',
|
|
1072
|
+
'pageTitle',
|
|
1073
|
+
'remoteKernelsPath',
|
|
1074
|
+
'fullThemesUrl'
|
|
1075
|
+
];
|
|
1076
|
+
function configFilePath() {
|
|
1077
|
+
return nodePath.resolve(process.cwd(), CONFIG_ROOT, GEN3_COMMONS_NAME, CONFIG_FILENAME);
|
|
1035
1078
|
}
|
|
1036
1079
|
/**
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*/ function
|
|
1041
|
-
if (Array.isArray(
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
try {
|
|
1055
|
-
const refererUrl = new URL(refererHeader);
|
|
1056
|
-
// Prefer referer origin when the host header dropped a non-default port.
|
|
1057
|
-
if (!host || !host.includes(':')) {
|
|
1058
|
-
host = refererUrl.host;
|
|
1059
|
-
}
|
|
1060
|
-
// Only fall back to referer protocol when nothing better is available.
|
|
1061
|
-
if (!proto) {
|
|
1062
|
-
proto = refererUrl.protocol.replace(':', '');
|
|
1063
|
-
}
|
|
1064
|
-
} catch {
|
|
1065
|
-
// Ignore malformed referer; keep header-derived values.
|
|
1080
|
+
* Pick and type-check the known option keys. Unknown keys are ignored so a
|
|
1081
|
+
* config file can carry extra metadata without reaching the factory,
|
|
1082
|
+
* and a wrong-typed value falls back to its default instead of throwing.
|
|
1083
|
+
*/ function parseOptions(raw) {
|
|
1084
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
1085
|
+
console.warn(`[workspace-assets] ${CONFIG_FILENAME} is not a JSON object; using defaults.`);
|
|
1086
|
+
return DEFAULT_OPTIONS;
|
|
1087
|
+
}
|
|
1088
|
+
const source = raw;
|
|
1089
|
+
const parsed = {};
|
|
1090
|
+
for (const key of STRING_OPTIONS){
|
|
1091
|
+
const value = source[key];
|
|
1092
|
+
if (value === undefined) continue;
|
|
1093
|
+
if (typeof value === 'string') {
|
|
1094
|
+
parsed[key] = value;
|
|
1095
|
+
} else {
|
|
1096
|
+
console.warn(`[workspace-assets] ignoring ${key}: expected a string, got ${typeof value}.`);
|
|
1066
1097
|
}
|
|
1067
1098
|
}
|
|
1099
|
+
const extensions = source.additionalDisabledExtensions;
|
|
1100
|
+
// oxlint-disable-next-line no-console
|
|
1101
|
+
console.log('addtional disabled extensions', source.additionalDisabledExtensions);
|
|
1102
|
+
if (extensions !== undefined) {
|
|
1103
|
+
if (Array.isArray(extensions) && extensions.every((entry)=>typeof entry === 'string')) {
|
|
1104
|
+
parsed.additionalDisabledExtensions = extensions;
|
|
1105
|
+
} else {
|
|
1106
|
+
console.warn('[workspace-assets] ignoring additionalDisabledExtensions: expected an array of strings.');
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
// oxlint-disable-next-line no-console
|
|
1110
|
+
console.log('parsed options', parsed);
|
|
1068
1111
|
return {
|
|
1069
|
-
|
|
1070
|
-
|
|
1112
|
+
...DEFAULT_OPTIONS,
|
|
1113
|
+
...parsed
|
|
1071
1114
|
};
|
|
1072
1115
|
}
|
|
1073
|
-
function
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
};
|
|
1083
|
-
})() : null;
|
|
1084
|
-
const withConfig = html.replace(/(<script\s+id="jupyter-config-data"[^>]*>)([\s\S]*?)(<\/script>)/, (_match, openTag, rawContent, closeTag)=>{
|
|
1085
|
-
// Strip leading/trailing whitespace left by the template.
|
|
1086
|
-
const trimmed = rawContent.trim();
|
|
1087
|
-
// Verify the captured content looks like a JSON object before parsing.
|
|
1088
|
-
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
1089
|
-
console.error('[vectis-branding] jupyter-config-data content is not a JSON object; skipping merge.');
|
|
1090
|
-
return _match;
|
|
1091
|
-
}
|
|
1092
|
-
let existing = {};
|
|
1093
|
-
try {
|
|
1094
|
-
existing = JSON.parse(trimmed);
|
|
1095
|
-
} catch (err) {
|
|
1096
|
-
console.error('[vectis-branding] Failed to parse jupyter-config-data JSON; skipping merge.', err);
|
|
1097
|
-
return _match;
|
|
1098
|
-
}
|
|
1099
|
-
const existingDisabled = Array.isArray(existing.disabledExtensions) ? existing.disabledExtensions : [];
|
|
1100
|
-
const remoteDisabledExtensions = isRemoteTierRequest ? [
|
|
1101
|
-
...existingDisabled,
|
|
1102
|
-
'@jupyterlite/pyodide-kernel-extension:kernel'
|
|
1103
|
-
] : existingDisabled;
|
|
1104
|
-
const merged = {
|
|
1105
|
-
...existing,
|
|
1106
|
-
appName: GEN3_COMMONS_NAME,
|
|
1107
|
-
faviconUrl: VECTIS_FAVICON_URL,
|
|
1108
|
-
...remoteDisabledExtensions.length > 0 ? {
|
|
1109
|
-
disabledExtensions: Array.from(new Set(remoteDisabledExtensions))
|
|
1110
|
-
} : {},
|
|
1111
|
-
...isRemoteTierRequest && absoluteRemoteBaseUrl ? {
|
|
1112
|
-
// Use an absolute URL so JupyterLite does not resolve WS endpoints
|
|
1113
|
-
// relative to /workspace-api/workspace-assets/remote/.
|
|
1114
|
-
remoteBaseUrl: absoluteRemoteBaseUrl.base,
|
|
1115
|
-
// Route all kernel ops through jeg-proxy so JupyterLite sees merged
|
|
1116
|
-
// Python3 (container) + JEG GPU kernelspecs. The handler routes
|
|
1117
|
-
// Python3 kernel launches/channels to the container; GPU kernel
|
|
1118
|
-
// launches are gated (403) with a message to use the Kernel Panel.
|
|
1119
|
-
remoteKernelsBaseUrl: `${absoluteRemoteBaseUrl.proto}://${absoluteRemoteBaseUrl.host}/lw-workspace/proxy/jeg-proxy`,
|
|
1120
|
-
fullThemesUrl: '/workspace-api/workspace-assets/remote/build/themes'
|
|
1121
|
-
} : {}
|
|
1122
|
-
};
|
|
1123
|
-
return `${openTag}${JSON.stringify(merged)}${closeTag}`;
|
|
1124
|
-
});
|
|
1125
|
-
// Escape the app name before placing it into the HTML title element.
|
|
1126
|
-
return withConfig.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(VECTIS_WORKSPACE_APP_NAME)}</title>`);
|
|
1116
|
+
function loadOptions() {
|
|
1117
|
+
const file = configFilePath();
|
|
1118
|
+
try {
|
|
1119
|
+
return parseOptions(JSON.parse(fs.readFileSync(file, 'utf-8')));
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1122
|
+
console.warn(`[workspace-assets] could not read ${file} (${reason}); using defaults.`);
|
|
1123
|
+
return DEFAULT_OPTIONS;
|
|
1124
|
+
}
|
|
1127
1125
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1126
|
+
let upstreamHandler$2;
|
|
1127
|
+
let loadedConfigMtimeMs;
|
|
1128
|
+
/** mtime of the config file, or undefined when it does not exist. */ function configMtimeMs() {
|
|
1129
|
+
try {
|
|
1130
|
+
return fs.statSync(configFilePath()).mtimeMs;
|
|
1131
|
+
} catch {
|
|
1132
|
+
return undefined;
|
|
1133
|
+
}
|
|
1133
1134
|
}
|
|
1134
|
-
function
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1135
|
+
function getUpstreamHandler() {
|
|
1136
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1137
|
+
// Rebuild when the config file changes so edits don't need a dev-server
|
|
1138
|
+
// restart. Only the cheap stat runs per request — the expensive asset walk
|
|
1139
|
+
// happens on the first request and then only after a real config change.
|
|
1140
|
+
const mtimeMs = configMtimeMs();
|
|
1141
|
+
if (upstreamHandler$2 && mtimeMs !== loadedConfigMtimeMs) {
|
|
1142
|
+
upstreamHandler$2 = undefined;
|
|
1139
1143
|
}
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1144
|
+
loadedConfigMtimeMs = mtimeMs;
|
|
1145
|
+
}
|
|
1146
|
+
if (!upstreamHandler$2) {
|
|
1147
|
+
upstreamHandler$2 = createWorkspaceAssetsHandler(loadOptions());
|
|
1148
|
+
}
|
|
1149
|
+
return upstreamHandler$2;
|
|
1150
|
+
}
|
|
1151
|
+
function handler$3(req, res) {
|
|
1152
|
+
return getUpstreamHandler()(req, res);
|
|
1143
1153
|
}
|
|
1144
1154
|
|
|
1145
1155
|
// Allowed path prefixes — keeps proxying scoped to the Jupyter Gateway API surface
|
|
@@ -1323,8 +1333,6 @@ const upstreamHandler$1 = createKernelProxyHandler({
|
|
|
1323
1333
|
}
|
|
1324
1334
|
});
|
|
1325
1335
|
async function handler$1(req, res) {
|
|
1326
|
-
const action = Array.isArray(req.query.action) ? req.query.action : [];
|
|
1327
|
-
action[0] === 'api' && action[1] === 'status';
|
|
1328
1336
|
try {
|
|
1329
1337
|
await upstreamHandler$1(req, res);
|
|
1330
1338
|
} catch (err) {
|
|
@@ -1490,6 +1498,7 @@ const upstreamHandler = createHatcheryProxyHandler({
|
|
|
1490
1498
|
getToken: (req)=>getAccessToken(req.headers['cookie'] ?? '') ?? null
|
|
1491
1499
|
});
|
|
1492
1500
|
async function handler(req, res) {
|
|
1501
|
+
// oxlint-disable-next-line no-unused-vars
|
|
1493
1502
|
req.query.action?.[0];
|
|
1494
1503
|
const originalSend = res.send.bind(res);
|
|
1495
1504
|
res.send = (body)=>{
|