|
2 | 2 | // Copyright (c) JupyterLite Contributors |
3 | 3 | // Distributed under the terms of the Modified BSD License. |
4 | 4 |
|
5 | | -import { PageConfig, URLExt } from '@jupyterlab/coreutils'; |
6 | 5 | 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'; |
36 | 23 |
|
37 | | - const data = await response.json(); |
38 | | - return data; |
| 24 | +enum KernelStatus { |
| 25 | + None = 0, |
| 26 | + Info = 1, |
| 27 | + Warning = 2, |
| 28 | + Error = 3 |
39 | 29 | } |
40 | 30 |
|
41 | | -const kernelPlugin: JupyterLiteServerPlugin<void> = { |
42 | | - id: '@jupyterlite/xeus-kernel:register', |
| 31 | +const kernelStatusPlugin: JupyterFrontEndPlugin<void> = { |
| 32 | + id: '@jupyterlite/xeus-extension:xeus-kernel-status', |
43 | 33 | autoStart: true, |
44 | | - requires: [IKernelSpecs], |
45 | | - optional: [ |
46 | | - IServiceWorkerManager, |
47 | | - IBroadcastChannelWrapper, |
48 | | - IEmpackEnvMetaFile |
49 | | - ], |
| 34 | + requires: [IToolbarWidgetRegistry, IRenderMimeRegistry], |
50 | 35 | 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 |
56 | 39 | ) => { |
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; |
66 | 42 |
|
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' |
95 | 70 | ); |
| 71 | + return; |
96 | 72 | } |
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' |
109 | 91 | }); |
110 | | - } |
111 | | - await app.serviceManager.kernelspecs.refreshSpecs(); |
112 | | - } |
113 | | -}; |
114 | 92 |
|
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 | + }) |
126 | 110 | ); |
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; |
129 | 181 | }; |
| 182 | + |
| 183 | + if (toolbarRegistry) { |
| 184 | + toolbarRegistry.addFactory<NotebookPanel>( |
| 185 | + 'Notebook', |
| 186 | + 'xeusKernelLogs', |
| 187 | + toolbarFactory |
| 188 | + ); |
| 189 | + } |
130 | 190 | } |
131 | 191 | }; |
132 | 192 |
|
133 | | -export default [empackEnvMetaPlugin, kernelPlugin]; |
134 | | -export { IEmpackEnvMetaFile }; |
| 193 | +export default [kernelStatusPlugin]; |
0 commit comments