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