diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 395c12dd..e4b47ed6 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -16,7 +16,7 @@ ENV CI=true USER root WORKDIR /repo -COPY --from=source-tree /repo/pnpm-lock.yaml /repo/pnpm-lock.yaml +COPY --from=source-tree /repo/pnpm-lock.yaml /repo/.npmrc /repo/ RUN pnpm fetch @@ -40,6 +40,8 @@ WORKDIR /root COPY ./docker/app/root/scripts /root/scripts +RUN mkdir workspace + ENV SELF_PARTY=alice ENV ALL_PARTIES=alice diff --git a/docker/app/root/scripts/start.sh b/docker/app/root/scripts/start.sh index 4ddd2e68..a691ef24 100755 --- a/docker/app/root/scripts/start.sh +++ b/docker/app/root/scripts/start.sh @@ -7,4 +7,4 @@ ray start \ --disable-usage-stats \ --resources="{\"$SELF_PARTY\": 4}" -secretnote --allow-root --no-browser +secretnote --allow-root --no-browser --notebook-dir=/root/workspace diff --git a/packages/secretnote/src/components/dropdown-menu/index.less b/packages/secretnote/src/components/dropdown-menu/index.less index a31ab151..5189b430 100644 --- a/packages/secretnote/src/components/dropdown-menu/index.less +++ b/packages/secretnote/src/components/dropdown-menu/index.less @@ -31,6 +31,8 @@ } .ant-upload { + padding-left: 30px; + margin-left: -30px; color: var(--mana-secretnote-text-color); font-size: 12px; } diff --git a/packages/secretnote/src/modules/editor/cell/view.tsx b/packages/secretnote/src/modules/editor/cell/view.tsx index 95dfe679..9ad1aaf2 100644 --- a/packages/secretnote/src/modules/editor/cell/view.tsx +++ b/packages/secretnote/src/modules/editor/cell/view.tsx @@ -10,7 +10,6 @@ import { KernelError, ILSPDocumentConnectionManager, CodeEditorManager, - CodeEditorSettings, } from '@difizen/libro-jupyter'; import { getOrigin, @@ -25,6 +24,7 @@ import { } from '@difizen/mana-app'; import { l10n } from '@difizen/mana-l10n'; import { message } from 'antd'; +import { isUndefined } from 'lodash-es'; import { forwardRef } from 'react'; import { Ribbon } from '@/components/ribbon'; @@ -36,15 +36,15 @@ import type { SecretNoteModel } from '../model'; const SecretNoteCodeCellComponent = forwardRef<HTMLDivElement>((props, ref) => { const instance = useInject<SecretNoteCodeCellView>(ViewInstance); - const { allExecutionParty, executionParty } = instance; + const { partyList, parties } = instance; return ( <div className={instance.className} ref={ref} tabIndex={10} onBlur={instance.blur}> <Ribbon - items={allExecutionParty} - value={executionParty} + items={partyList.map((name) => ({ label: name, key: name }))} + value={parties} onChange={(val) => { - instance.changeExecutionParty(val); + instance.onPartiesChange(val); }} > <CellEditorMemo /> @@ -54,6 +54,8 @@ const SecretNoteCodeCellComponent = forwardRef<HTMLDivElement>((props, ref) => { }); SecretNoteCodeCellComponent.displayName = 'SecretNoteCodeCellComponent'; +let lastParties: string[] = []; // store last parties for new cell + @transient() @view('secretnote-code-cell-view') export class SecretNoteCodeCellView extends JupyterCodeCellView { @@ -63,13 +65,10 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView { view = SecretNoteCodeCellComponent; @prop() - executionParty: string[] = []; + parties: string[] = []; - get allExecutionParty() { - return this.serverManager.servers.map((server) => ({ - key: server.id, - label: server.name, - })); + get partyList() { + return this.serverManager.servers.map((server) => server.name); } constructor( @@ -80,89 +79,72 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView { @inject(SecretNoteKernelManager) kernelManager: SecretNoteKernelManager, @inject(ILSPDocumentConnectionManager) lsp: ILSPDocumentConnectionManager, @inject(CodeEditorManager) codeEditorManager: CodeEditorManager, - @inject(CodeEditorSettings) codeEditorSettings: CodeEditorSettings, ) { - super( - options, - cellService, - viewManager, - lsp, - codeEditorManager, - codeEditorSettings, - ); + super(options, cellService, viewManager, lsp, codeEditorManager); this.serverManager = serverManager; this.kernelManager = kernelManager; - this.executionParty = - this.getExecutionParty() || this.allExecutionParty.map((item) => item.key); + this.parties = this.getInitializedParties(); } - async run() { + getUsableConnections() { const libroModel = this.parent.model as SecretNoteModel; - const cellModel = this.model; if (!libroModel) { - return false; + return []; } - let kernelConnections = getOrigin(libroModel.kernelConnections); - - // 没有可用的 Kernel 连接 - if (kernelConnections.length === 0) { - message.info(l10n.t('没有可用的 Kernel 连接')); - return false; - } + const kernelConnections = getOrigin(libroModel.kernelConnections); - kernelConnections = kernelConnections.filter((connection) => { + return kernelConnections.filter((connection) => { + if (connection.isDisposed) { + return false; + } const server = this.kernelManager.getServerByKernelConnection(connection); return ( - server && server.status === 'running' && this.executionParty.includes(server.id) + server && server.status === 'running' && this.parties.includes(server.name) ); }); - if (kernelConnections.length === 0) { - message.info(l10n.t('请选择一个执行节点')); - return false; - } - if (kernelConnections.length !== this.executionParty.length) { - message.info(l10n.t('有的 Kernel 连接不可用')); - return false; - } + } - const hasDisposedConnection = kernelConnections.some((item) => { - return item.isDisposed; - }); - if (hasDisposedConnection) { - message.error(l10n.t('有的 Kernel 连接已经被销毁')); + async run() { + const cellModel = this.model; + const kernelConnections = this.getUsableConnections(); + + if (kernelConnections.length === 0) { + message.info(l10n.t('No available node to execute')); return false; } this.clearExecution(); - this.setExecutionStatus({ executing: true }); - this.setExecutionTime({ start: '', end: '', toExecute: new Date().toISOString() }); - this.setExecutionParty(); + this.updateExecutionStatus({ executing: true }); + this.updateExecutionTime({ + toExecute: new Date().toISOString(), + }); + this.savePartiesToMeta(); try { const list: Promise<KernelMessage.IExecuteReplyMsg>[] = []; for (let i = 0, len = kernelConnections.length; i < len; i += 1) { const connection = kernelConnections[i]; - - const future = connection.requestExecute({ - code: cellModel.value, - }); + const future = connection.requestExecute( + { + code: cellModel.value, + }, + // Even after receiving a reply message, you can still receive other messages. + false, + ); future.onIOPub = ( msg: KernelMessage.IIOPubMessage<KernelMessage.IOPubMessageType>, ) => { + if (msg.header.msg_type === 'execute_input') { + this.updateExecutionStatus({ kernelExecuting: true }); + this.updateExecutionTime({ start: msg.header.date }); + } cellModel.msgChangeEmitter.fire({ connection, msg, }); - if ( - cellModel.kernelExecuting === false && - msg.header.msg_type === 'execute_input' - ) { - this.setExecutionStatus({ kernelExecuting: true }); - this.setExecutionTime({ start: msg.header.date }); - } }; future.onReply = (msg: KernelMessage.IExecuteReplyMsg) => { @@ -176,8 +158,8 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView { } const futureDoneList = await Promise.all(list); - this.setExecutionStatus({ executing: false, kernelExecuting: false }); - this.setExecutionTime(this.parseMessageTime(futureDoneList)); + this.updateExecutionStatus({ executing: false, kernelExecuting: false }); + this.updateExecutionTime(this.parseMessageTime(futureDoneList)); const ok = futureDoneList.every((msg) => msg.content.status === 'ok'); if (ok) { @@ -197,27 +179,27 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView { } } - setExecutionStatus(status: { executing?: boolean; kernelExecuting?: boolean }) { + updateExecutionStatus(status: { executing?: boolean; kernelExecuting?: boolean }) { const { executing, kernelExecuting } = status; - if (executing !== undefined) { + if (!isUndefined(executing)) { this.model.executing = executing; } - if (kernelExecuting !== undefined) { + if (!isUndefined(kernelExecuting)) { this.model.kernelExecuting = kernelExecuting; } } - setExecutionTime(times: { start?: string; end?: string; toExecute?: string }) { + updateExecutionTime(times: { start?: string; end?: string; toExecute?: string }) { const meta = this.model.metadata.execution as ExecutionMeta; if (meta) { const { start, end, toExecute } = times; - if (start !== undefined) { + if (!isUndefined(start)) { meta['shell.execute_reply.started'] = start; } - if (end !== undefined) { + if (!isUndefined(end)) { meta['shell.execute_reply.end'] = end; } - if (toExecute !== undefined) { + if (!isUndefined(toExecute)) { meta.to_execute = toExecute; } } @@ -240,29 +222,32 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView { return { start, end }; } - changeExecutionParty(party: string[]) { - this.executionParty = party; - this.setExecutionParty(party); + onPartiesChange(parties: string[]) { + this.parties = parties; + this.savePartiesToMeta(parties); + lastParties = parties; } - getExecutionParty() { + getInitializedParties() { const execution = this.model.metadata.execution as ExecutionMeta; - if (execution && execution.executionParty) { + if (execution && execution.parties) { try { - const party: string[] = JSON.parse(execution.executionParty as string); - return party.filter((p) => - this.allExecutionParty.some((item) => item.key === p), - ); + const parties: string[] = JSON.parse(execution.parties as string); + return parties.filter((p) => this.partyList.includes(p)); } catch (e) { return []; } + } else if (lastParties.length > 0) { + // load parties from previous cell settings + return lastParties; } + return this.partyList; } - setExecutionParty(party: string[] = this.executionParty) { + savePartiesToMeta(parties: string[] = this.parties) { const execution = this.model.metadata.execution as ExecutionMeta; if (execution) { - execution.executionParty = JSON.stringify(party); + execution.parties = JSON.stringify(parties); } } } diff --git a/packages/secretnote/src/modules/editor/model.ts b/packages/secretnote/src/modules/editor/model.ts index 5a0781cf..52a31b21 100644 --- a/packages/secretnote/src/modules/editor/model.ts +++ b/packages/secretnote/src/modules/editor/model.ts @@ -231,5 +231,5 @@ export class SecretNoteModel extends LibroModel { autoSave = debounce(() => { this.commandRegistry.executeCommand(DocumentCommands.Save.id); - }, 1000); + }, 500); } diff --git a/packages/secretnote/src/modules/file/log-preview-contrib.tsx b/packages/secretnote/src/modules/file/log-preview-contrib.tsx index 6969685c..7bc8e596 100644 --- a/packages/secretnote/src/modules/file/log-preview-contrib.tsx +++ b/packages/secretnote/src/modules/file/log-preview-contrib.tsx @@ -9,6 +9,6 @@ const LogViewer = React.lazy(() => import('@/components/log-viewer')); export class LogPreview implements FilePreviewContribution { type = 'log'; render = (data: string) => { - return <LogViewer code={data} />; + return <LogViewer code={[data]} />; }; } diff --git a/packages/secretnote/src/modules/metrics/view.tsx b/packages/secretnote/src/modules/metrics/view.tsx index ebfd8297..410f3bc0 100644 --- a/packages/secretnote/src/modules/metrics/view.tsx +++ b/packages/secretnote/src/modules/metrics/view.tsx @@ -2,7 +2,6 @@ import type { ModalItem, ModalItemProps } from '@difizen/mana-app'; import { useInject } from '@difizen/mana-app'; import { l10n } from '@difizen/mana-l10n'; import { Badge, Drawer } from 'antd'; -import { forwardRef } from 'react'; import { Smoothie } from '@/components/smoothie'; @@ -10,72 +9,6 @@ import { MetricsService } from './service'; import './index.less'; -const MetricsComponent1 = forwardRef<HTMLDivElement, ModalItemProps<void>>( - function MetricsComponent1(props, ref) { - const { visible, close } = props; - const metricsService = useInject<MetricsService>(MetricsService); - const { metrics } = metricsService; - - const afterOpenChange = async (open: boolean) => { - if (open) { - metricsService.enable(); - } - }; - - const onClose = () => { - metricsService.disable(); - close(); - }; - - return ( - <div ref={ref}> - <Drawer - placement="right" - onClose={() => onClose()} - width={360} - open={visible} - mask={false} - afterOpenChange={afterOpenChange} - destroyOnClose={true} - title={l10n.t('资源消耗')} - > - <div className="secretnote-kernel-status"> - {metrics.map((item) => ( - <div key={item.kernel.id} className="kernel-status-item"> - <div className="server-name">{item.server.name}:</div> - <div className="metrics-item"> - <span className="label">{l10n.t('名称')}:</span> - <span>{item.kernel.name}</span> - </div> - <div className="metrics-item"> - <span className="label">{l10n.t('状态')}:</span> - <Badge - color={item.kernel.statusColor} - text={item.kernel.statusText} - /> - </div> - <div className="metrics-item"> - <span className="label">PID:</span> - <span>{item.kernel.pid}</span> - </div> - <div className="metrics-item"> - <span className="label">CPU:</span> - <span>{item.kernel.cpuText}</span> - </div> - <Smoothie data={{ time: Date.now(), data: item.kernel.cpu }} /> - <div className="metrics-item"> - <span className="label">RAM:</span> - <span>{item.kernel.memoryText}</span> - </div> - <Smoothie data={{ time: Date.now(), data: item.kernel.memory }} /> - </div> - ))} - </div> - </Drawer> - </div> - ); - }, -); const MetricsComponent = (props: ModalItemProps<void>) => { const { visible, close } = props; const metricsService = useInject<MetricsService>(MetricsService); diff --git a/packages/secretnote/src/modules/scql-editor/editor/editor.ts b/packages/secretnote/src/modules/scql-editor/editor/editor.ts index 54bb2b62..24bc8d88 100644 --- a/packages/secretnote/src/modules/scql-editor/editor/editor.ts +++ b/packages/secretnote/src/modules/scql-editor/editor/editor.ts @@ -9,7 +9,13 @@ import type { ICoordinate, SearchMatch, } from '@difizen/libro-jupyter'; -import { Disposable, DisposableCollection, watch, Emitter } from '@difizen/mana-app'; +import { + Disposable, + DisposableCollection, + watch, + Emitter, + Deferred, +} from '@difizen/mana-app'; import * as monaco from 'monaco-editor'; import type { IMatching } from 'syntax-parser'; @@ -24,6 +30,8 @@ export type MonacoEditorType = monaco.editor.IStandaloneCodeEditor; export type MonacoMatch = monaco.editor.FindMatch; export class SQLEditor implements IEditor { + protected editorReadyDeferred = new Deferred<void>(); + editorReady = this.editorReadyDeferred.promise; private editorHost: HTMLElement; private _model: IModel; private _uuid = ''; @@ -110,6 +118,7 @@ export class SQLEditor implements IEditor { ); this.updateEditorSize(); + this.editorReadyDeferred.resolve(); } protected checkSyntaxError() { diff --git a/pyprojects/secretnote/package.json b/pyprojects/secretnote/package.json index d36cefaa..ec0085db 100644 --- a/pyprojects/secretnote/package.json +++ b/pyprojects/secretnote/package.json @@ -11,7 +11,7 @@ "lint:ruff": "python -m ruff check src tests", "test:pytest": "python -m pytest", "typecheck:pyright": "pyright --project ../.. src tests", - "dev": "NODE_ENV=development python -m secretnote.server --config=./.jupyter/config_dev.py --debug --no-browser --mode=scql --party=alice --host=http://127.0.0.1:8991", + "dev": "NODE_ENV=development python -m secretnote.server --config=./.jupyter/config_dev.py --debug --no-browser", "build:py": "rye build --out ./dist", "publish:py": "exit 1", "build": "nx build:py"