Skip to content

Commit e49ae55

Browse files
authored
Report kernel status on startup (#197)
* Introducing labextension * Report kernel status to main thread * Logs dialog * Remove log * Using the logger panel * Update mambajs * Add back toolbar button * WAT * Fix closing -> reopening the logs * Scroll to bottom automatically * Linter * Some button styling * Animate button in case of kernel error * Remove debug logs * Linter * Use toolbar factory * Remove debug logs * Review comment * Move toolbar button as per comment review * Recreate widget if disposed
1 parent 3111e25 commit e49ae55

File tree

20 files changed

+2377
-573
lines changed

20 files changed

+2377
-573
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ node_modules/
66
*.egg-info/
77
.ipynb_checkpoints
88
*.tsbuildinfo
9+
jupyterlite_xeus/liteextension
910
jupyterlite_xeus/labextension
1011
# Version file is handled by hatchling
1112
jupyterlite_xeus/_version.py

jupyterlite_xeus/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111

1212

1313
def _jupyter_labextension_paths():
14-
return [{"src": "labextension", "dest": "@jupyterlite/xeus"}]
14+
return [{"src": "labextension", "dest": "@jupyterlite/xeus"}, {"src": "liteextension", "dest": "@jupyterlite/xeus-kernels-extension"}]

packages/xeus-extension/package.json

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
"license": "BSD-3-Clause",
1515
"author": "JupyterLite Contributors",
1616
"files": [
17-
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}"
17+
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
18+
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
19+
"schema/*.json"
1820
],
1921
"main": "lib/index.js",
2022
"types": "lib/index.d.ts",
23+
"style": "style/index.css",
2124
"repository": {
2225
"type": "git",
2326
"url": "https://github.com/jupyterlite/xeus.git"
@@ -38,10 +41,10 @@
3841
"watch:labextension": "jupyter labextension watch ."
3942
},
4043
"dependencies": {
44+
"@jupyterlab/application": "^4.3.4",
4145
"@jupyterlab/coreutils": "^6",
42-
"@jupyterlite/contents": "^0.2.0 || ^0.3.0 || ^0.4.5 || ^0.5.1",
43-
"@jupyterlite/kernel": "^0.2.0 || ^0.3.0 || ^0.4.5 || ^0.5.1",
44-
"@jupyterlite/server": "^0.2.0 || ^0.3.0 || ^0.4.5 || ^0.5.1",
46+
"@jupyterlab/logconsole": "^4.3.5",
47+
"@jupyterlab/notebook": "^4.3.5",
4548
"@jupyterlite/xeus": "^4.0.0-a0",
4649
"@lumino/coreutils": "^2"
4750
},
@@ -63,26 +66,14 @@
6366
"publishConfig": {
6467
"access": "public"
6568
},
69+
"sideEffects": [
70+
"style/*.css",
71+
"style/index.js"
72+
],
73+
"styleModule": "style/index.js",
6674
"jupyterlab": {
6775
"extension": true,
68-
"outputDir": "../../jupyterlite_xeus/labextension",
69-
"webpackConfig": "lab.webpack.config.js",
70-
"sharedPackages": {
71-
"@jupyterlite/kernel": {
72-
"bundled": false,
73-
"singleton": true
74-
},
75-
"@jupyterlite/server": {
76-
"bundled": false,
77-
"singleton": true
78-
},
79-
"@jupyterlite/contents": {
80-
"bundled": false,
81-
"singleton": true
82-
}
83-
}
84-
},
85-
"jupyterlite": {
86-
"liteExtension": true
76+
"schemaDir": "schema",
77+
"outputDir": "../../jupyterlite_xeus/labextension"
8778
}
8879
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"title": "Xeus Kernels Settings",
3+
"description": "Xeus Kernels Settings.",
4+
"jupyter.lab.toolbars": {
5+
"Notebook": [{ "name": "xeusKernelLogs", "rank": 1002.5 }]
6+
},
7+
"properties": {},
8+
"additionalProperties": false,
9+
"type": "object"
10+
}

packages/xeus-extension/src/index.ts

Lines changed: 172 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,133 +2,192 @@
22
// Copyright (c) JupyterLite Contributors
33
// Distributed under the terms of the Modified BSD License.
44

5-
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
65
import {
7-
IServiceWorkerManager,
8-
JupyterLiteServer,
9-
JupyterLiteServerPlugin
10-
} from '@jupyterlite/server';
11-
import { IBroadcastChannelWrapper } from '@jupyterlite/contents';
12-
import { IKernel, IKernelSpecs } from '@jupyterlite/kernel';
13-
14-
import { WebWorkerKernel } from '@jupyterlite/xeus';
15-
import { IEmpackEnvMetaFile } from './tokens';
16-
17-
/**
18-
* Fetches JSON data from the specified URL asynchronously.
19-
*
20-
* This function constructs the full URL using the base URL from the PageConfig and
21-
* the provided relative URL. It then performs a GET request using the Fetch API
22-
* and returns the parsed JSON data.
23-
*
24-
* @param {string} url - The relative URL to fetch the JSON data from.
25-
* @returns {Promise<any>} - A promise that resolves to the parsed JSON data.
26-
* @throws {Error} - Throws an error if the HTTP request fails.
27-
*
28-
*/
29-
async function getJson(url: string) {
30-
const jsonUrl = URLExt.join(PageConfig.getBaseUrl(), url);
31-
const response = await fetch(jsonUrl, { method: 'GET' });
32-
33-
if (!response.ok) {
34-
throw new Error(`HTTP error! status: ${response.status}`);
35-
}
6+
JupyterFrontEndPlugin,
7+
JupyterFrontEnd
8+
} from '@jupyterlab/application';
9+
10+
import {
11+
MainAreaWidget,
12+
IToolbarWidgetRegistry,
13+
showErrorMessage
14+
} from '@jupyterlab/apputils';
15+
16+
import { listIcon, ToolbarButton } from '@jupyterlab/ui-components';
17+
18+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
19+
20+
import { LoggerRegistry, LogConsolePanel } from '@jupyterlab/logconsole';
21+
22+
import { NotebookPanel } from '@jupyterlab/notebook';
3623

37-
const data = await response.json();
38-
return data;
24+
enum KernelStatus {
25+
None = 0,
26+
Info = 1,
27+
Warning = 2,
28+
Error = 3
3929
}
4030

41-
const kernelPlugin: JupyterLiteServerPlugin<void> = {
42-
id: '@jupyterlite/xeus-kernel:register',
31+
const kernelStatusPlugin: JupyterFrontEndPlugin<void> = {
32+
id: '@jupyterlite/xeus-extension:xeus-kernel-status',
4333
autoStart: true,
44-
requires: [IKernelSpecs],
45-
optional: [
46-
IServiceWorkerManager,
47-
IBroadcastChannelWrapper,
48-
IEmpackEnvMetaFile
49-
],
34+
requires: [IToolbarWidgetRegistry, IRenderMimeRegistry],
5035
activate: async (
51-
app: JupyterLiteServer,
52-
kernelspecs: IKernelSpecs,
53-
serviceWorker?: IServiceWorkerManager,
54-
broadcastChannel?: IBroadcastChannelWrapper,
55-
empackEnvMetaFile?: IEmpackEnvMetaFile
36+
app: JupyterFrontEnd,
37+
toolbarRegistry: IToolbarWidgetRegistry,
38+
rendermime: IRenderMimeRegistry
5639
) => {
57-
// Fetch kernel list
58-
let kernelList: string[] = [];
59-
try {
60-
kernelList = await getJson('xeus/kernels.json');
61-
} catch (err) {
62-
console.log(`Could not fetch xeus/kernels.json: ${err}`);
63-
throw err;
64-
}
65-
const contentsManager = app.serviceManager.contents;
40+
const toolbarFactory = (panel: NotebookPanel) => {
41+
const session = panel.sessionContext;
6642

67-
for (const kernel of kernelList) {
68-
// Fetch kernel spec
69-
const kernelspec = await getJson(
70-
'xeus/kernels/' + kernel + '/kernel.json'
71-
);
72-
kernelspec.name = kernel;
73-
kernelspec.dir = kernel;
74-
for (const [key, value] of Object.entries(kernelspec.resources)) {
75-
kernelspec.resources[key] = URLExt.join(
76-
PageConfig.getBaseUrl(),
77-
value as string
78-
);
79-
}
80-
kernelspecs.register({
81-
spec: kernelspec,
82-
create: async (options: IKernel.IOptions): Promise<IKernel> => {
83-
const mountDrive = !!(
84-
(serviceWorker?.enabled && broadcastChannel?.enabled) ||
85-
crossOriginIsolated
86-
);
87-
88-
if (mountDrive) {
89-
console.info(
90-
`${kernelspec.name} contents will be synced with Jupyter Contents`
91-
);
92-
} else {
93-
console.warn(
94-
`${kernelspec.name} contents will NOT be synced with Jupyter Contents`
43+
let currentState = KernelStatus.None;
44+
let kernelId: string | undefined;
45+
let kernelName: string | undefined;
46+
let logConsolePanel: LogConsolePanel | undefined;
47+
let sourceId: string | undefined;
48+
let logConsoleWidget: MainAreaWidget<LogConsolePanel> | undefined;
49+
50+
const createWidget = () => {
51+
if (!logConsolePanel) {
52+
return;
53+
}
54+
55+
logConsoleWidget = new MainAreaWidget<LogConsolePanel>({
56+
content: logConsolePanel
57+
});
58+
logConsoleWidget.title.label = 'Kernel Logs';
59+
logConsoleWidget.title.icon = listIcon;
60+
logConsoleWidget.id = `${sourceId}-widget`;
61+
};
62+
63+
const toolbarButton = new ToolbarButton({
64+
icon: listIcon,
65+
onClick: () => {
66+
if (!logConsolePanel) {
67+
showErrorMessage(
68+
'Cannot show logs',
69+
'Cannot show logs for the current kernel'
9570
);
71+
return;
9672
}
97-
const link = empackEnvMetaFile
98-
? await empackEnvMetaFile.getLink(kernelspec)
99-
: '';
100-
101-
return new WebWorkerKernel({
102-
...options,
103-
contentsManager,
104-
mountDrive,
105-
kernelSpec: kernelspec,
106-
empackEnvMetaLink: link
107-
});
108-
}
73+
74+
if (!logConsoleWidget) {
75+
createWidget();
76+
}
77+
78+
if (logConsoleWidget) {
79+
if (logConsoleWidget.isDisposed) {
80+
createWidget();
81+
}
82+
83+
if (!logConsoleWidget.isAttached) {
84+
app.shell.add(logConsoleWidget, 'main', { mode: 'split-bottom' });
85+
}
86+
87+
app.shell.activateById(logConsoleWidget.id);
88+
}
89+
},
90+
tooltip: 'Show kernel logs'
10991
});
110-
}
111-
await app.serviceManager.kernelspecs.refreshSpecs();
112-
}
113-
};
11492

115-
const empackEnvMetaPlugin: JupyterLiteServerPlugin<IEmpackEnvMetaFile> = {
116-
id: '@jupyterlite/xeus:empack-env-meta',
117-
autoStart: true,
118-
provides: IEmpackEnvMetaFile,
119-
activate: (): IEmpackEnvMetaFile => {
120-
return {
121-
getLink: async (kernelspec: Record<string, any>) => {
122-
const kernelName = kernelspec.name;
123-
const kernel_root_url = URLExt.join(
124-
PageConfig.getBaseUrl(),
125-
`xeus/kernels/${kernelName}`
93+
session.kernelChanged.connect(() => {
94+
kernelId = session.session?.id;
95+
kernelName = session.session?.kernel?.name;
96+
97+
currentState = KernelStatus.None;
98+
99+
if (!kernelId || !kernelName) {
100+
return;
101+
}
102+
103+
sourceId = `${kernelName}-${kernelId}`;
104+
105+
logConsolePanel = new LogConsolePanel(
106+
new LoggerRegistry({
107+
defaultRendermime: rendermime,
108+
maxLength: 1000
109+
})
126110
);
127-
return `${kernel_root_url}`;
128-
}
111+
112+
logConsolePanel.source = sourceId;
113+
114+
const channel = new BroadcastChannel(`/kernel-broadcast/${kernelId}`);
115+
116+
if (logConsolePanel.logger) {
117+
logConsolePanel.logger.level = 'info';
118+
}
119+
120+
// Scroll to bottom when new content shows up
121+
logConsolePanel.logger?.contentChanged.connect(() => {
122+
const element = document.getElementById(`source:${sourceId}`);
123+
124+
if (!element) {
125+
return;
126+
}
127+
128+
const lastChild = element.lastElementChild;
129+
if (lastChild) {
130+
lastChild.scrollIntoView({ behavior: 'smooth' });
131+
}
132+
});
133+
134+
channel.onmessage = event => {
135+
if (!logConsolePanel) {
136+
return;
137+
}
138+
139+
switch (event.data.type) {
140+
case 'log':
141+
logConsolePanel.logger?.log({
142+
type: 'text',
143+
level: 'info',
144+
data: event.data.msg
145+
});
146+
147+
if (currentState < KernelStatus.Info) {
148+
currentState = KernelStatus.Info;
149+
toolbarButton.addClass('xeus-kernel-status-info');
150+
}
151+
break;
152+
case 'warn':
153+
logConsolePanel.logger?.log({
154+
type: 'text',
155+
level: 'warning',
156+
data: event.data.msg
157+
});
158+
159+
if (currentState < KernelStatus.Warning) {
160+
currentState = KernelStatus.Warning;
161+
toolbarButton.addClass('xeus-kernel-status-warning');
162+
}
163+
break;
164+
case 'error':
165+
logConsolePanel.logger?.log({
166+
type: 'text',
167+
level: 'error',
168+
data: event.data.msg
169+
});
170+
171+
if (currentState < KernelStatus.Error) {
172+
currentState = KernelStatus.Error;
173+
toolbarButton.addClass('xeus-kernel-status-error');
174+
}
175+
break;
176+
}
177+
};
178+
});
179+
180+
return toolbarButton;
129181
};
182+
183+
if (toolbarRegistry) {
184+
toolbarRegistry.addFactory<NotebookPanel>(
185+
'Notebook',
186+
'xeusKernelLogs',
187+
toolbarFactory
188+
);
189+
}
130190
}
131191
};
132192

133-
export default [empackEnvMetaPlugin, kernelPlugin];
134-
export { IEmpackEnvMetaFile };
193+
export default [kernelStatusPlugin];

0 commit comments

Comments
 (0)