From c1344dccd0c1ec2895f673e9db6f49151fafd0c3 Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Wed, 11 Mar 2026 13:02:00 +0100 Subject: [PATCH 01/19] refact upload file via ftp + upload and use batch script --- electron/main.js | 9 +++------ electron/utils/sshConnections.js | 10 +++++----- src/lib/utils/sshMessages.ts | 24 ++++++++++++++++++------ 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/electron/main.js b/electron/main.js index 6dd102c5..17605a69 100644 --- a/electron/main.js +++ b/electron/main.js @@ -3,7 +3,7 @@ import path from 'path' import { fileURLToPath } from 'url' import { - connectAndUploadGraph, + uploadFileViaSftp, connectToSSHWithKey, connectToSSHWithPassword, } from './utils/sshConnections.js' @@ -65,16 +65,13 @@ ipcMain.handle('execute-ssh-with-key', async (event, { command }) => { return await connectToSSHWithKey(command, pathToSsh) }) -ipcMain.handle('export-graph-ssh', async (event, { graph }) => { +ipcMain.handle('upload-file-ssh', async (event, { content, remotePath }) => { const settings = store.get('settings', {}) const pathToSsh = settings.sshPathKey if (!pathToSsh) { throw new Error('SSH key path not configured in settings') } - const jsonGraph = JSON.stringify(graph) - console.log('exported graph', jsonGraph) - const remotePath = '/app/shared-data/graph.json' - return await connectAndUploadGraph(jsonGraph, remotePath, pathToSsh) + return await uploadFileViaSftp(content, remotePath, pathToSsh) }) ipcMain.handle('open-external-url', async (event, url) => { diff --git a/electron/utils/sshConnections.js b/electron/utils/sshConnections.js index 9c27d0f9..e046512c 100644 --- a/electron/utils/sshConnections.js +++ b/electron/utils/sshConnections.js @@ -86,23 +86,23 @@ function connectToSSHWithKey(command, pathToSsh) { }) } -function connectAndUploadGraph(jsonGraph, remotePath, pathToSsh) { +function uploadFileViaSftp(content, remotePath, pathToSsh) { return new Promise((resolve, reject) => { - console.log('connectAndUploadGraph called') + console.log('uploadFileViaSftp called') const conn = new Client() conn .on('ready', () => { console.log('SSH Connection with key established') conn.sftp((err, sftp) => { if (err) return reject(err) - sftp.writeFile(remotePath, jsonGraph, (err) => { + sftp.writeFile(remotePath, content, (err) => { if (err) { conn.end() return reject(err) } console.log('File uploaded successfully') conn.end() - resolve(`Graph uploaded to: ${remotePath}`) + resolve(`File uploaded to: ${remotePath}`) }) }) }) @@ -120,4 +120,4 @@ function connectAndUploadGraph(jsonGraph, remotePath, pathToSsh) { }) } -export { connectToSSHWithPassword, connectToSSHWithKey, connectAndUploadGraph } +export { connectToSSHWithPassword, connectToSSHWithKey, uploadFileViaSftp } diff --git a/src/lib/utils/sshMessages.ts b/src/lib/utils/sshMessages.ts index 865785fb..037f6951 100644 --- a/src/lib/utils/sshMessages.ts +++ b/src/lib/utils/sshMessages.ts @@ -58,19 +58,31 @@ export const exportAndEvalGraph = async ( const parsedGraph = parseGraphWithQualifiedIds(nodes, edges) // export graph - const resultExport = await window.electron.invoke('export-graph-ssh', { - graph: parsedGraph, + const resultExport = await window.electron.invoke('upload-file-ssh', { + content: JSON.stringify(parsedGraph), + remotePath: '/app/shared-data/graph.json', }) console.log('SSH Connection Result:', resultExport) // get next available internal job Id and use it as name of the directory where nodes' execution status will be placed const internalJobId = jobIdMapState.getNextKey() - // execute graph - // const sbatchCommand = 'sbatch --wrap="sleep 20" --output=hello.out' - const sbatchCommand = `sbatch --chdir=/app/shared-data --wrap="/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir ${internalJobId}"` + // generate and upload batch script + const scriptContent = `#!/bin/bash +#SBATCH --chdir=/app/shared-data +#SBATCH --output=/app/shared-data/slurm-%j.out +#SBATCH --job-name=coral-${internalJobId} + +/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir ${internalJobId} +` + await window.electron.invoke('upload-file-ssh', { + content: scriptContent, + remotePath: '/app/shared-data/job.sh', + }) + + // submit job const resultExecute = await window.electron.invoke('execute-ssh-with-key', { - command: sbatchCommand, + command: 'sbatch /app/shared-data/job.sh', }) console.log('SSH Connection Result:', resultExecute) toastState.add({ message: resultExecute }) From ca0ad55dc8fd8f2add2255acb848a6faf075da06 Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Wed, 11 Mar 2026 15:53:18 +0100 Subject: [PATCH 02/19] #79 batch script as template file imported via vite and cached with electron-store --- electron/utils/storage.js | 1 + src/lib/templates/sbatch.template.sh | 6 ++++++ src/lib/utils/sshMessages.ts | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 src/lib/templates/sbatch.template.sh diff --git a/electron/utils/storage.js b/electron/utils/storage.js index d032f07a..81f88580 100644 --- a/electron/utils/storage.js +++ b/electron/utils/storage.js @@ -16,6 +16,7 @@ const store = new Store({ registered_network_nodes: { type: 'object' }, jobs: { type: 'array', default: [] }, jobIdMap: { type: 'object', default: {} }, + sbatch_template: { type: 'string' }, }, }) diff --git a/src/lib/templates/sbatch.template.sh b/src/lib/templates/sbatch.template.sh new file mode 100644 index 00000000..d0f6799a --- /dev/null +++ b/src/lib/templates/sbatch.template.sh @@ -0,0 +1,6 @@ +#!/bin/bash +#SBATCH --chdir=/app/shared-data +#SBATCH --output=/app/shared-data/slurm-%j.out +#SBATCH --job-name=coral-{{INTERNAL_JOB_ID}} + +/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir {{INTERNAL_JOB_ID}} diff --git a/src/lib/utils/sshMessages.ts b/src/lib/utils/sshMessages.ts index 037f6951..71a5b45c 100644 --- a/src/lib/utils/sshMessages.ts +++ b/src/lib/utils/sshMessages.ts @@ -5,6 +5,10 @@ import { toastState } from '../stores/toastsStore.svelte' import { parseGraphWithQualifiedIds } from './graphParser' import { setPanelContent } from './panelContent.js' import { JobStatus } from '../types/executionStatus' +// The `?raw` Vite suffix imports the file contents as a plain string at build time. +// It works identically in dev, built app, and packaged Electron binaries. +// Docs: https://vite.dev/guide/assets#importing-asset-as-string +import defaultSbatchTemplate from '../templates/sbatch.template.sh?raw' /** * Executes a test SSH command using password authentication. @@ -67,14 +71,14 @@ export const exportAndEvalGraph = async ( // get next available internal job Id and use it as name of the directory where nodes' execution status will be placed const internalJobId = jobIdMapState.getNextKey() - // generate and upload batch script - const scriptContent = `#!/bin/bash -#SBATCH --chdir=/app/shared-data -#SBATCH --output=/app/shared-data/slurm-%j.out -#SBATCH --job-name=coral-${internalJobId} - -/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir ${internalJobId} -` + // generate batch script from stored template (or default) + const template = + (await window.electron.store.get('sbatch_template')) ?? + defaultSbatchTemplate + const scriptContent = template.replaceAll( + '{{INTERNAL_JOB_ID}}', + String(internalJobId) + ) await window.electron.invoke('upload-file-ssh', { content: scriptContent, remotePath: '/app/shared-data/job.sh', From a4846a0a276e72b90ced3a0e50a556df3a899e28 Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Wed, 11 Mar 2026 16:04:54 +0100 Subject: [PATCH 03/19] updated submodule coral-visulizer to show solution and boundary id assigment new features --- coral-visualizer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coral-visualizer b/coral-visualizer index 3bb6042d..37c1bac4 160000 --- a/coral-visualizer +++ b/coral-visualizer @@ -1 +1 @@ -Subproject commit 3bb6042dd85d17fd7b7e6b9c67bfeff2facde7ea +Subproject commit 37c1bac418bbff1c0e3407894907706387790926 From 6cf4e409191c011b025a56a003011c94495fec9c Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Wed, 11 Mar 2026 18:13:55 +0100 Subject: [PATCH 04/19] save node exec status in dedicated dir + add docs to inspect electron-store store --- README.md | 16 +++++++++++++++- src/lib/templates/sbatch.template.sh | 2 +- src/lib/utils/sshMessages.ts | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a833e424..d19dcc48 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,21 @@ Run unit tests For more options see the [general instructions](https://www.electronjs.org/docs/latest/tutorial/debugging-main-process) or the [specific ones](https://www.electronjs.org/docs/latest/tutorial/debugging-vscode) for VS Code -### Debugging Svelte +#### Inspecting the Electron Store + +The app uses [electron-store](https://github.com/sindresorhus/electron-store) to persist data. You can inspect and modify it from the DevTools console (**CTRL+SHIFT+I**): + +```js +// Get a value +await window.electron.store.get('sbatch_template') + +// Remove a value +await window.electron.store.remove('sbatch_template') +``` + +Available keys are defined in [electron/utils/storage.js](electron/utils/storage.js). + +## Debugging Svelte - Execute `npm run dev` and open the Source tab in the Chormium dev tools (**CTRL+SHIFT+I**). Then manually add the folder containing this repository from the Workspace sub-tab. Now add your breakpoints and start debugging. - In Svelte code you can also use [`{@debug}`](https://svelte.dev/docs/svelte/@debug) or [`$inspect`](https://svelte.dev/docs/svelte/$inspect). diff --git a/src/lib/templates/sbatch.template.sh b/src/lib/templates/sbatch.template.sh index d0f6799a..e42a908c 100644 --- a/src/lib/templates/sbatch.template.sh +++ b/src/lib/templates/sbatch.template.sh @@ -3,4 +3,4 @@ #SBATCH --output=/app/shared-data/slurm-%j.out #SBATCH --job-name=coral-{{INTERNAL_JOB_ID}} -/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir {{INTERNAL_JOB_ID}} +/app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir nodes-exec-status/{{INTERNAL_JOB_ID}} diff --git a/src/lib/utils/sshMessages.ts b/src/lib/utils/sshMessages.ts index 71a5b45c..64332c27 100644 --- a/src/lib/utils/sshMessages.ts +++ b/src/lib/utils/sshMessages.ts @@ -226,7 +226,7 @@ export const getNodesExecutionStatus = async ( jobIdInternal: number ): Promise> => { // define the command to list the files in the touch-dir - const command = `ls -tr /app/shared-data/${jobIdInternal}` + const command = `ls -tr /app/shared-data/nodes-exec-status/${jobIdInternal}` // get the raw output string with lines in format "nodeId.status" const output = await window.electron.invoke('execute-ssh-with-key', { From 11c4b9b7cb72ad5e3860f7abdff8a9529c4aefaf Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Thu, 12 Mar 2026 12:39:35 +0100 Subject: [PATCH 05/19] MPI support via two sbatch templates + user choose them via Settings --- README.md | 4 +- docker-compose.yml | 3 ++ electron/utils/storage.js | 1 - src/lib/components/Settings.svelte | 68 ++++++++++++++++++++++++ src/lib/stores/settingsStore.svelte.js | 1 + src/lib/templates/sbatch-mpi.template.sh | 9 ++++ src/lib/utils/sshMessages.ts | 9 ++-- 7 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/lib/templates/sbatch-mpi.template.sh diff --git a/README.md b/README.md index d19dcc48..62ba44ec 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,10 @@ The app uses [electron-store](https://github.com/sindresorhus/electron-store) to ```js // Get a value -await window.electron.store.get('sbatch_template') +await window.electron.store.get('jobIdMap') // Remove a value -await window.electron.store.remove('sbatch_template') +await window.electron.store.remove('jobIdMap') ``` Available keys are defined in [electron/utils/storage.js](electron/utils/storage.js). diff --git a/docker-compose.yml b/docker-compose.yml index dd7050c2..b27c2f70 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,9 @@ services: - '8000:8000' volumes: - ./containers/shared-data:/app/shared-data + environment: + - OMPI_ALLOW_RUN_AS_ROOT=1 + - OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 networks: - coral-network # restart: unless-stopped diff --git a/electron/utils/storage.js b/electron/utils/storage.js index 81f88580..d032f07a 100644 --- a/electron/utils/storage.js +++ b/electron/utils/storage.js @@ -16,7 +16,6 @@ const store = new Store({ registered_network_nodes: { type: 'object' }, jobs: { type: 'array', default: [] }, jobIdMap: { type: 'object', default: {} }, - sbatch_template: { type: 'string' }, }, }) diff --git a/src/lib/components/Settings.svelte b/src/lib/components/Settings.svelte index d7bea3f5..19b09286 100644 --- a/src/lib/components/Settings.svelte +++ b/src/lib/components/Settings.svelte @@ -4,6 +4,7 @@ SSH_PATH, URL_VISUALIZER, URL_REMOTE_SERVER, + USE_MPI, } from '../stores/settingsStore.svelte' import { toastState } from '../stores/toastsStore.svelte' import Button from './layout/Button.svelte' @@ -18,6 +19,7 @@ let isEditingVisualizer = $state(false) let urlRemoteServer = $state(settingsState.getKey(URL_REMOTE_SERVER)) let isEditingRemote = $state(false) + let useMpi = $state(settingsState.getKey(USE_MPI) ?? false) // Put here all the logic needed to reset the states when modal is re-opened. // Triggers when parent modal changes visibility. @@ -52,6 +54,12 @@ toastState.add({ message: 'URL Remote Server saved' }) } + const toggleMpi = async () => { + useMpi = !useMpi + await settingsState.setKey(USE_MPI, useMpi) + toastState.add({ message: `MPI ${useMpi ? 'enabled' : 'disabled'}` }) + } + const closeModal = () => getModal(modalId).close() @@ -128,6 +136,15 @@ {/if} +
+
+ Use MPI + +
+
@@ -175,6 +192,57 @@ border-color: red; } + .switch { + position: relative; + display: inline-block; + width: 48px; + height: 27px; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.4s; + } + + .slider:before { + position: absolute; + content: ''; + height: 21px; + width: 21px; + left: 3px; + bottom: 3px; + background-color: white; + transition: 0.4s; + } + + input:checked + .slider { + background-color: var(--button-action-bg); + } + + input:checked + .slider:before { + transform: translateX(21px); + } + + .slider.round { + border-radius: 27px; + } + + .slider.round:before { + border-radius: 50%; + } + .button-container { margin-top: 2vh; } diff --git a/src/lib/stores/settingsStore.svelte.js b/src/lib/stores/settingsStore.svelte.js index b4fb1768..30daa561 100644 --- a/src/lib/stores/settingsStore.svelte.js +++ b/src/lib/stores/settingsStore.svelte.js @@ -13,6 +13,7 @@ loadSettings() export const SSH_PATH = 'sshPathKey' export const URL_VISUALIZER = 'urlVisualizerKey' export const URL_REMOTE_SERVER = 'urlRemoteServerKey' +export const USE_MPI = 'useMpiKey' export const settingsState = { getKey(key) { diff --git a/src/lib/templates/sbatch-mpi.template.sh b/src/lib/templates/sbatch-mpi.template.sh new file mode 100644 index 00000000..18f48a06 --- /dev/null +++ b/src/lib/templates/sbatch-mpi.template.sh @@ -0,0 +1,9 @@ +#!/bin/bash +#SBATCH --chdir=/app/shared-data +#SBATCH --output=/app/shared-data/slurm-%j.out +#SBATCH --job-name=coral-{{INTERNAL_JOB_ID}} +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=4 +# #SBATCH --time=01:00:00 + +mpirun --allow-run-as-root -np ${SLURM_NTASKS:-1} /app/build/core/coral --plugin /app/build/backends/dealii/libcoral_backend_dealii.so run /app/shared-data/graph.json --touch-dir nodes-exec-status/{{INTERNAL_JOB_ID}} diff --git a/src/lib/utils/sshMessages.ts b/src/lib/utils/sshMessages.ts index 64332c27..cf7ae03c 100644 --- a/src/lib/utils/sshMessages.ts +++ b/src/lib/utils/sshMessages.ts @@ -9,6 +9,8 @@ import { JobStatus } from '../types/executionStatus' // It works identically in dev, built app, and packaged Electron binaries. // Docs: https://vite.dev/guide/assets#importing-asset-as-string import defaultSbatchTemplate from '../templates/sbatch.template.sh?raw' +import defaultSbatchMpiTemplate from '../templates/sbatch-mpi.template.sh?raw' +import { settingsState, USE_MPI } from '../stores/settingsStore.svelte' /** * Executes a test SSH command using password authentication. @@ -71,10 +73,9 @@ export const exportAndEvalGraph = async ( // get next available internal job Id and use it as name of the directory where nodes' execution status will be placed const internalJobId = jobIdMapState.getNextKey() - // generate batch script from stored template (or default) - const template = - (await window.electron.store.get('sbatch_template')) ?? - defaultSbatchTemplate + // generate batch script based on MPI setting + const useMpi = settingsState.getKey(USE_MPI) ?? false + const template = useMpi ? defaultSbatchMpiTemplate : defaultSbatchTemplate const scriptContent = template.replaceAll( '{{INTERNAL_JOB_ID}}', String(internalJobId) From b960fd4b5ca81fbe35d8f12f4ab99344dc996ae9 Mon Sep 17 00:00:00 2001 From: Pablo Colturi Date: Thu, 12 Mar 2026 15:27:05 +0100 Subject: [PATCH 06/19] new modal and logic to configure sbatch mpi template + eslint config ignore no_unused_var false positive in interfaces when _prefixed args --- CLAUDE.md | 3 +- eslint.config.js | 10 +- src/lib/components/MpiConfigModal.svelte | 122 ++++++++++++++++++ src/lib/components/ProjectCard.svelte | 4 +- .../components/layout/SidebarButtons.svelte | 24 +++- src/lib/templates/sbatch-mpi.template.sh | 4 +- src/lib/utils/dragAndDrop.svelte.ts | 4 +- src/lib/utils/sshMessages.ts | 17 ++- 8 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/lib/components/MpiConfigModal.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 1d00566d..259dfaf5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ Stores use Svelte 5 runes (`.svelte.js` / `.svelte.ts` files): - `nodes.svelte.ts` - Central store for flow nodes/edges and the registry. Exports `getNodes()`, `getNodesSnapshot()`, `setNodes()`, `getEdges()`, `getEdgesSnapshot()`, `setEdges()`, `setRegistry()`. Network nodes use dedicated `RegisteredNetworkNodes` / `NetworkNodeOfTypeNetwork` types. - `auth.svelte.js` - JWT token for coral-remote-server API -- `settingsStore.svelte.js` - User settings (SSH key path, visualizer URL) +- `settingsStore.svelte.js` - User settings stored under a single `'settings'` key in electron-store. Exports named key constants (`SSH_PATH`, `URL_VISUALIZER`, `URL_REMOTE_SERVER`, `USE_MPI`) and a `settingsState` object with `getKey(key)` / `setKey(key, value)` methods. - `currentProjectStore.svelte.js` - Current project metadata - `jobsStore.svelte.js` - Slurm job tracking @@ -225,6 +225,7 @@ cd /app && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build - **IPC channels**: `execute-ssh-with-key`, `export-graph-ssh`, `set-theme`, `open-external-url` - **Pre-commit hooks**: Husky runs `npm run lint` then Prettier; lint failures abort commit - **API requests**: All authenticated requests go through `src/lib/requests/api.js` which auto-attaches the Bearer token +- **Slurm batch templates**: Two templates in `src/lib/templates/` — `sbatch.template.sh` (non-MPI) and `sbatch-mpi.template.sh` (MPI via `mpirun --allow-run-as-root -np ${SLURM_NTASKS:-1}`). Imported at build time via Vite's `?raw` suffix. `sshMessages.ts` selects between them based on the `USE_MPI` setting. The MPI template additionally exposes `{{NODES}}` and `{{NTASKS_PER_NODE}}` placeholders filled at runtime from `MpiConfig` (defaults: 1 node, 4 tasks/node). When MPI is enabled, clicking Execute opens `MpiConfigModal.svelte` to let the user configure these values before submission. ## Git Workflow diff --git a/eslint.config.js b/eslint.config.js index efbe8913..58234663 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -44,6 +44,14 @@ export default defineConfig([ }, { plugins: {}, - rules: {}, + rules: { + // Allow underscore-prefixed names to be declared but unused. + // This is needed for callback parameter names in TypeScript interface definitions, + // See: https://eslint.org/docs/latest/rules/no-unused-vars#argsignorepattern + 'no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_' }, + ], + }, }, ]) diff --git a/src/lib/components/MpiConfigModal.svelte b/src/lib/components/MpiConfigModal.svelte new file mode 100644 index 00000000..5c2469cc --- /dev/null +++ b/src/lib/components/MpiConfigModal.svelte @@ -0,0 +1,122 @@ + + + +
+

MPI Configuration

+
+
+ + +
+
+ + +
+
+ Total MPI processes + {totalProcesses} +
+
+
+ + +
+
+
+ + diff --git a/src/lib/components/ProjectCard.svelte b/src/lib/components/ProjectCard.svelte index e93f3622..27cbf502 100644 --- a/src/lib/components/ProjectCard.svelte +++ b/src/lib/components/ProjectCard.svelte @@ -34,8 +34,8 @@ interface Props { project: Project - // eslint-disable-next-line no-unused-vars - onDelete: (projectId: number) => void + // underscore-prefixed arg name to avoid eslint no-unused-vars error in interfaces + onDelete: (_projectId: number) => void onLoad: () => void onShare: () => void onEdit: () => void diff --git a/src/lib/components/layout/SidebarButtons.svelte b/src/lib/components/layout/SidebarButtons.svelte index c674b9b5..d893a484 100644 --- a/src/lib/components/layout/SidebarButtons.svelte +++ b/src/lib/components/layout/SidebarButtons.svelte @@ -11,7 +11,7 @@ removeQualifiedIds, validateGraphData, } from '../../utils/graphParser' - import { exportAndEvalGraph, openNewWindow } from '../../utils/sshMessages' + import { exportAndEvalGraph, openNewWindow, type MpiConfig } from '../../utils/sshMessages' import Modal, { getModal } from './Modal.svelte' import LoginForm from '../LoginForm.svelte' import SaveProjectForm from '../SaveProjectForm.svelte' @@ -26,6 +26,7 @@ import { settingsState, URL_VISUALIZER, + USE_MPI, } from '../../stores/settingsStore.svelte' import { currentProjectState } from '../../stores/currentProjectStore.svelte' import { parseGraphWithQualifiedIds } from '../../utils/graphParser' @@ -35,6 +36,7 @@ import CreateNetworkNodeModal from '../nodes/CreateNetworkNodeModal.svelte' import SidebarGroupButton from './SidebarGroupButton.svelte' import SidebarGroupButtonItem from './SidebarGroupButtonItem.svelte' + import MpiConfigModal from '../MpiConfigModal.svelte' const loginModalId = 'login-modal' const logoutConfirmModalId = 'logout-confirm-modal' @@ -42,6 +44,7 @@ const projectsModalId = 'projects-modal' const saveProjectModalId = 'save-project-modal' const createNetworkNodeModalId = 'create-network-node-modal' + const mpiConfigModalId = 'mpi-config-modal' const token = $derived(auth.token) const username = $derived(auth.username) const loginText = $derived.by(() => { @@ -67,9 +70,22 @@ toastState.add({ message: 'Logged out', type: 'success' }) } - const handleExecution = async () => { + const handleExecution = () => { + const useMpi = settingsState.getKey(USE_MPI) ?? false + if (useMpi) { + getModal(mpiConfigModalId)?.open() + } else { + executeGraph() + } + } + + const handleMpiConfirm = (config: MpiConfig) => { + executeGraph(config) + } + + const executeGraph = async (mpiConfig?: MpiConfig) => { try { - await exportAndEvalGraph(getNodesSnapshot(), getEdgesSnapshot()) + await exportAndEvalGraph(getNodesSnapshot(), getEdgesSnapshot(), mpiConfig) } catch (error) { console.error('Failed to execute graph:', error) toastState.add({ @@ -313,6 +329,8 @@ Execute
+ +