diff --git a/.eslintrc.js b/.eslintrc.js index 709b44b..88ff798 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,8 @@ module.exports = { 'camelcase': 'off', 'no-else-return': 'off', 'consistent-return': 'off', + 'no-use-before-define': 'off', + 'no-await-in-loop': 'off', "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 'react/function-component-definition': [ 2, diff --git a/src/common/channels.js b/src/common/channels.js index 5e49a42..c9d88d4 100644 --- a/src/common/channels.js +++ b/src/common/channels.js @@ -1,8 +1,16 @@ const Channels = { - OPEN_LINK_PLEASE: 'openLinkPlease', - OPEN_WINDOW: 'message:loginShow', STORE_GET: 'electron-store-get', STORE_SET: 'electron-store-set', + REMOVE_EXECUTOR_CONTAINER: 'remove-executor-container', + REMOVE_EXECUTOR_CONTAINER_RESPONSE: 'remove-executor-response', + RUN_EXECUTOR_CONTAINER_RESPONSE: 'run-executor-container-response', + RUN_EXECUTOR_GPU_CONTAINER: 'remove-executor-gpu-container', + RUN_EXECUTOR_GPU_CONTAINER_RESPONSE: 'run-executor-container-gpu-response', + RUN_EXECUTOR_CONTAINER: 'run-executor-container', + CHECK_CONTAINER_GPU_SUPPORT: 'check-container-gpu-support', + CHECK_CONTAINER_GPU_SUPPORT_RESPONSE: 'check-container-gpu-support-response', + CHECK_CONTAINER_EXIST: 'check-container-exist', + CHECK_CONTAINER_EXIST_RESPONSE: 'check-container-exist-response', APP_CLOSE_INITIATED: 'app-close-initiated', APP_CLOSE_CONFIRMED: 'app-close-confirmed', APP_RELOAD_INITIATED: 'app-reload-initiated', diff --git a/src/main/main.ts b/src/main/main.ts index 77d2cd8..9bbdf70 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -23,7 +23,8 @@ import log from 'electron-log/main'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; import Channels from '../common/channels'; - +const fs = require('fs'); +const os = require('os'); const Store = require('electron-store'); const io = require('socket.io')(); const Docker = require('dockerode'); @@ -125,20 +126,17 @@ appdev_server.on('connection', (socket) => { ipcMain.on(Channels.STORE_GET, async (event, key) => { try { const encryptedKey = store.get(key); - if (encryptedKey !== undefined) { const decryptedKey = safeStorage.decryptString( Buffer.from(encryptedKey, 'latin1') ); event.returnValue = decryptedKey; } else { - // Handle the case when the key is undefined (not found) - event.returnValue = null; // Or another appropriate default value + event.returnValue = null; } } catch (error) { - // Handle any errors that may occur during the decryption process console.error('Error while getting value:', error); - event.returnValue = null; // Or another appropriate default value + event.returnValue = null; } }); @@ -147,6 +145,288 @@ ipcMain.on(Channels.STORE_SET, async (event, key, val) => { store.set(key, buffer.toString('latin1')); }); +ipcMain.on(Channels.REMOVE_EXECUTOR_CONTAINER, async (event, containerName) => { + docker.listContainers({ all: true }, (err, containers) => { + if (err) { + console.error(err); + event.reply( + Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, + false, + err.message + ); + return; + } + + const containerInfo = containers.find((c) => + c.Names.some((n) => n === `/${containerName}`) + ); + + if (containerInfo) { + const container = docker.getContainer(containerInfo.Id); + + const removeContainer = () => { + container.remove((err) => { + if (err) { + console.error(err); + event.reply( + Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, + false, + err.message + ); + } else { + event.reply(Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, true); + console.log(`Container ${containerName} removed successfully.`); + } + }); + }; + + // Check if the container is already stopped + if (containerInfo.State === 'running') { + container.stop((err) => { + if (err) { + console.error(err); + event.reply( + Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, + false, + err.message + ); + return; + } + removeContainer(); + }); + } else { + removeContainer(); + } + } else { + console.log(`Container ${containerName} not found.`); + event.reply(Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, true); + } + }); +}); + +ipcMain.on(Channels.RUN_EXECUTOR_CONTAINER, async (event, containerName) => { + try { + docker.listContainers({ all: true }, (err, containers) => { + if (err) { + console.error(err); + return; + } + + const existingContainer = containers.find((c) => + c.Names.includes('/' + containerName) + ); + + if (existingContainer) { + // Container exists, start it if it's not running + if (existingContainer.State !== 'running') { + docker.getContainer(existingContainer.Id).start((err, data) => { + if (err) { + console.error(err); + } else { + log.info('Existing container started'); + event.reply(Channels.RUN_EXECUTOR_CONTAINER_RESPONSE, true); + } + }); + } else { + log.info('Container is already running'); + event.reply(Channels.RUN_EXECUTOR_CONTAINER_RESPONSE, true); + } + } else { + // Container does not exist, create and start it + const containerOptions = { + name: containerName, + Image: 'meca-executor:latest', + ExposedPorts: { '2591/tcp': {} }, + HostConfig: { + Binds: ['/var/run/docker.sock:/var/run/docker.sock'], + PortBindings: { '2591/tcp': [{ HostPort: '2591' }] }, + NetworkMode: 'meca', + }, + NetworkingConfig: { + EndpointsConfig: { + meca: { + IPAMConfig: { + IPv4Address: '173.18.0.255', + }, + }, + }, + }, + }; + + docker.createContainer(containerOptions, (err, container) => { + if (err) { + console.error(err); + return; + } + + container.start((err, data) => { + if (err) { + console.error(err); + } else { + log.info('New container started'); + event.reply(Channels.RUN_EXECUTOR_CONTAINER_RESPONSE, true); + } + }); + }); + } + }); + } catch (error) { + event.reply(Channels.RUN_EXECUTOR_CONTAINER_RESPONSE, false, error.message); + } +}); + +ipcMain.on( + Channels.RUN_EXECUTOR_GPU_CONTAINER, + async (event, containerName) => { + try { + // Configuration data + const configData = ` +type: "docker" +timeout: 1 +cpu: 4 +mem: 4096 +has_gpu: true +`; + + // Create a temporary file and write configuration data + const tempDir = os.tmpdir(); + const configFilePath = path.join(tempDir, 'meca_docker_gpu.yaml'); + fs.writeFileSync(configFilePath, configData); + + const containerOptions = { + name: containerName, + Image: 'meca-executor:latest', + ExposedPorts: { '2591/tcp': {} }, + HostConfig: { + Binds: [ + '/var/run/docker.sock:/var/run/docker.sock', + `${configFilePath}:/app/meca_executor.yaml`, + ], + PortBindings: { '2591/tcp': [{ HostPort: '2591' }] }, + NetworkMode: 'meca', + DeviceRequests: [ + { + Driver: '', + Count: -1, // -1 specifies "all GPUs" + DeviceIDs: [], + Capabilities: [['gpu']], + Options: {}, + }, + ], + }, + NetworkingConfig: { + EndpointsConfig: { + meca: { + IPAMConfig: { + IPv4Address: '173.18.0.255', + }, + }, + }, + }, + }; + docker.createContainer(containerOptions, (err, container) => { + if (err) { + console.error(err); + return; + } + container.start((err, data) => { + if (err) { + event.reply( + Channels.RUN_EXECUTOR_GPU_CONTAINER_RESPONSE, + false, + err.message + ); + console.error(err); + } else { + log.info('New container started'); + event.reply(Channels.RUN_EXECUTOR_GPU_CONTAINER_RESPONSE, true); + } + }); + }); + } catch (error) { + console.error(error); + event.reply( + Channels.RUN_EXECUTOR_GPU_CONTAINER_RESPONSE, + false, + error.message + ); + } + } +); +// Function to check if a container has GPU support +function checkIfContainerHasGpu(containerId, callback) { + const container = docker.getContainer(containerId); + container.inspect((err, data) => { + if (err) { + callback(err, null); + return; + } + const hasGpu = data.HostConfig.DeviceRequests?.some((deviceRequest) => + deviceRequest.Capabilities?.some((capability) => + capability.includes('gpu') + ) + ); + callback(null, hasGpu); + }); +} + + +ipcMain.on(Channels.CHECK_CONTAINER_EXIST, (event, containerName) => { + docker.listContainers({ all: true }, (err, containers) => { + if (err) { + console.error('Error listing containers:', err); + event.reply(Channels.CHECK_CONTAINER_EXIST_RESPONSE, false, err.message); + return; + } + + const containerExists = containers.some((container) => + container.Names.some((name) => name === `/${containerName}`) + ); + + event.reply(Channels.CHECK_CONTAINER_EXIST_RESPONSE, true, containerExists); + }); +}); +// IPC listener to check for GPU support in a container +ipcMain.on(Channels.CHECK_CONTAINER_GPU_SUPPORT, (event, containerName) => { + docker.listContainers({ all: true }, (err, containers) => { + if (err) { + console.error('Error listing containers:', err); + event.reply( + Channels.CHECK_CONTAINER_GPU_SUPPORT_RESPONSE, + false, + err.message + ); + return; + } + + const containerInfo = containers.find((c) => + c.Names.includes('/' + containerName) + ); + + if (containerInfo) { + checkIfContainerHasGpu(containerInfo.Id, (error, hasGpu) => { + if (error) { + console.error('Error inspecting container:', error); + event.reply( + Channels.CHECK_CONTAINER_GPU_SUPPORT_RESPONSE, + false, + error.message + ); + } else { + event.reply( + Channels.CHECK_CONTAINER_GPU_SUPPORT_RESPONSE, + true, + hasGpu + ); + } + }); + } else { + console.log(`Container ${containerName} not found.`); + event.reply(Channels.CHECK_CONTAINER_GPU_SUPPORT_RESPONSE, true, false); + } + }); +}); + ipcMain.on(Channels.JOB_RESULTS_RECEIVED, async (event, id, result) => { if (!mainWindow) { throw new Error('"mainWindow" is not defined'); @@ -194,75 +474,6 @@ if (process.env.NODE_ENV === 'production') { sourceMapSupport.install(); } -const stopAndRemoveContainer = (containerName) => { - docker.listContainers({ all: true }, (err, containers) => { - if (err) { - console.error(err); - return; - } - const containerInfo = containers.find((c) => - c.Names.some((n) => n === `/${containerName}`) - ); - - if (containerInfo) { - const container = docker.getContainer(containerInfo.Id); - container.stop((err) => { - if (err) { - console.error(err); - return; - } - container.remove((err) => { - if (err) { - console.error(err); - } else { - console.log( - `Container ${containerName} stopped and removed successfully.` - ); - } - }); - }); - } else { - console.log(`Container ${containerName} not found.`); - } - }); -}; -const startDockerContainer = (containerName: string) => { - const containerOptions = { - name: containerName, - Image: 'meca-executor:latest', - ExposedPorts: { '2591/tcp': {} }, - - HostConfig: { - Binds: ['/var/run/docker.sock:/var/run/docker.sock'], - PortBindings: { '2591/tcp': [{ HostPort: '2591' }] }, - NetworkMode: 'meca', - }, - NetworkingConfig: { - EndpointsConfig: { - meca: { - IPAMConfig: { - IPv4Address: '173.18.0.255', - }, - }, - }, - }, - }; - docker.createContainer(containerOptions, (err, container) => { - if (err) { - console.error(err); - return; - } - - container.start((err, data) => { - if (err) { - console.error(err); - } else { - console.log('Container started'); - } - }); - }); -}; - const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; @@ -342,20 +553,26 @@ const createWindow = async () => { } }); + // before closing mainWindow.on('close', (e) => { - log.info('mainWindow close'); + log.info('app closing'); e.preventDefault(); mainWindow?.webContents.send('app-close-initiated'); }); + + // signify app successfully closed mainWindow.on('closed', () => { - log.info('mainWindow closed'); + log.info('app successfully closed'); mainWindow = null; }); + + // intended for app reload, but not working currently mainWindow.webContents.on('will-navigate', (event) => { log.info('mainWindow did-start-navigation'); event.preventDefault(); mainWindow?.webContents.send('app-reload-initiated'); }); + const menuBuilder = new MenuBuilder(mainWindow); menuBuilder.buildMenu(); @@ -376,10 +593,6 @@ app const appReadyMs = performance.now() - start; console.log(`App ready in ${appReadyMs} ms`); createWindow(); - const containerName = 'meca_executor_test'; - stopAndRemoveContainer(containerName); - console.log("removed..?") - startDockerContainer(containerName); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. diff --git a/src/main/preload.ts b/src/main/preload.ts index c1cce75..c1987e8 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,7 +1,7 @@ // Disable no-unused-vars, broken for spread args /* eslint no-unused-vars: off */ import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; -// import log from 'electron-log/main'; +// import log from 'electron-log/preload'; import Channels from '../common/channels'; const subscribe = (channel: string, func: (...args: any[]) => void) => { @@ -11,10 +11,7 @@ const subscribe = (channel: string, func: (...args: any[]) => void) => { }; const electronHandler = { - openLinkPlease: () => ipcRenderer.invoke(Channels.OPEN_LINK_PLEASE), - openWindow: () => { - ipcRenderer.send(Channels.OPEN_WINDOW); - }, + once: (channel, func) => ipcRenderer.once(channel, func), store: { get(key) { return ipcRenderer.sendSync(Channels.STORE_GET, key); @@ -24,13 +21,94 @@ const electronHandler = { }, }, + removeExecutorContainer: (containerName: string) => { + return new Promise((resolve, reject) => { + ipcRenderer.send(Channels.REMOVE_EXECUTOR_CONTAINER, containerName); + ipcRenderer.once( + Channels.REMOVE_EXECUTOR_CONTAINER_RESPONSE, + (event, success, error) => { + if (success) { + resolve(); + } else { + reject(new Error(error)); + } + } + ); + }); + }, + + runExecutorContainer: (containerName: string) => { + return new Promise((resolve, reject) => { + ipcRenderer.send(Channels.RUN_EXECUTOR_CONTAINER, containerName); + // Setup a one-time listener for the response + ipcRenderer.once( + Channels.RUN_EXECUTOR_CONTAINER_RESPONSE, + (event, success, error) => { + if (success) { + resolve(); + } else { + reject(new Error(error)); + } + } + ); + }); + }, + + runExecutorGpuContainer: (containerName: string) => { + return new Promise((resolve, reject) => { + ipcRenderer.send(Channels.RUN_EXECUTOR_GPU_CONTAINER, containerName); + // Setup a one-time listener for the response + ipcRenderer.once( + Channels.RUN_EXECUTOR_GPU_CONTAINER_RESPONSE, + (event, success, error) => { + if (success) { + resolve(); + } else { + reject(new Error(error)); + } + } + ); + }); + }, + + checkContainerExist: (containerName: string) => { + return new Promise((resolve, reject) => { + ipcRenderer.send(Channels.CHECK_CONTAINER_EXIST, containerName); + ipcRenderer.once( + Channels.CHECK_CONTAINER_EXIST_RESPONSE, + (event, success, containerExists) => { + if (success) { + resolve(containerExists); + } else { + reject(new Error('Failed to check container existence')); + } + } + ); + }); + }, + + checkContainerGpuSupport: (containerName: string) => { + return new Promise((resolve, reject) => { + ipcRenderer.send(Channels.CHECK_CONTAINER_GPU_SUPPORT, containerName); + ipcRenderer.once( + Channels.CHECK_CONTAINER_GPU_SUPPORT_RESPONSE, + (event, success, hasGpuSupport) => { + if (success) { + resolve(hasGpuSupport); // Assuming 'hasGpuSupport' is a boolean + } else { + reject(new Error('Failed to check GPU support')); + } + } + ); + }); + }, + onAppCloseInitiated: (callback: (...args: any[]) => void) => { subscribe(Channels.APP_CLOSE_INITIATED, callback); }, confirmAppClose: () => { ipcRenderer.send(Channels.APP_CLOSE_CONFIRMED); }, - onAppReloadInitiated: (callback: (...args: any[]) => void) => { subscribe(Channels.APP_RELOAD_INITIATED, callback); }, diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 264e657..e6d2550 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -39,6 +39,7 @@ import { getDesignTokens } from './utils/theme'; import useHandleAppExitHook from './utils/useHandleAppExitHook'; import useClientHooks from './utils/useClientHooks'; + const PrivateRoutes = () => { const isAuthenticated = useSelector( (state: RootState) => state.userReducer.authenticated diff --git a/src/renderer/components/auth/handleAccountRegistration.tsx b/src/renderer/components/auth/handleAccountRegistration.tsx index 540a4c2..4fb8500 100644 --- a/src/renderer/components/auth/handleAccountRegistration.tsx +++ b/src/renderer/components/auth/handleAccountRegistration.tsx @@ -81,6 +81,15 @@ const handleAccountRegistration = async ( gpus: 0, }) ); + // window.electron.store.set( + // 'deviceStats', + // JSON.stringify({ + // totalCpuCores: 4, + // totalMem: 8192, + // totalGpus: 0, + // gpuModel: '', + // }) + // ); } catch (createAccountError) { throw createAccountError; } diff --git a/src/renderer/components/auth/handleLogin.tsx b/src/renderer/components/auth/handleLogin.tsx index 916324f..6219817 100644 --- a/src/renderer/components/auth/handleLogin.tsx +++ b/src/renderer/components/auth/handleLogin.tsx @@ -13,10 +13,11 @@ const handleLogin = async (password: string): Promise => { // Get key with (decryptWithPassword(window.electron.store.get('privateKey'), password)) const accessTokenResponse = await authenticate(did, credential); const { access_token, refresh_token } = accessTokenResponse; - console.log("did, ", did) - console.log("access_token", access_token) + console.log('did, ', did); + console.log('access_token', access_token); actions.setAccessToken(access_token); actions.setRefreshToken(refresh_token); + await handleStartExecutor('meca_executor_test'); return true; // should only return true if signed VP is verified // for future reference if the challenge-response scheme for authentication will be reused. @@ -38,4 +39,19 @@ const handleLogin = async (password: string): Promise => { // return res; }; +const handleStartExecutor = async (containerName: string) => { + const containerExist = await window.electron.checkContainerExist( + containerName + ); + if (containerExist) { + const hasGpuSupport = await window.electron.checkContainerGpuSupport( + containerName + ); + if (hasGpuSupport) { + await window.electron.removeExecutorContainer(containerName); + } + } + await window.electron.runExecutorContainer(containerName); +}; + export default handleLogin; diff --git a/src/renderer/components/common/ErrorDialogue.tsx b/src/renderer/components/common/ErrorDialogue.tsx index dac98a6..b575745 100644 --- a/src/renderer/components/common/ErrorDialogue.tsx +++ b/src/renderer/components/common/ErrorDialogue.tsx @@ -18,10 +18,21 @@ const ErrorDialog: FC = ({ open, onClose, errorMessage }) => { }; return ( - - Error + + Error - {errorMessage} + + {errorMessage} + diff --git a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/CoreSelectorWidget.tsx b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/CoreSelectorWidget.tsx index 7feb1f3..74571ea 100644 --- a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/CoreSelectorWidget.tsx +++ b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/CoreSelectorWidget.tsx @@ -9,14 +9,18 @@ import { import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { useTheme } from '@emotion/react'; +import { useSelector } from 'react-redux'; +import { RootState } from 'renderer/redux/store'; const CoreSelectorWidget = ({ executorSettings, setExecutorSettings, - totalCpuCores, + // totalCpuCores, }) => { const theme = useTheme(); - + const totalCpuCores = useSelector( + (state: RootState) => state.deviceStatsReducer.totalCpuCores + ); const incrementCore = () => { if (executorSettings.cpu_cores < totalCpuCores) { setExecutorSettings((prev) => ({ diff --git a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/GpuSelectorWidget.tsx b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/GpuSelectorWidget.tsx index 40f1a24..4c8cf9d 100644 --- a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/GpuSelectorWidget.tsx +++ b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/GpuSelectorWidget.tsx @@ -6,19 +6,23 @@ import { Box, IconButton, } from '@mui/material'; +import { useSelector } from 'react-redux'; +import { RootState } from 'renderer/redux/store'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import ErrorIcon from '@mui/icons-material/Error'; import { useTheme } from '@emotion/react'; -const GpuSelectorWidget = ({ - executorSettings, - setExecutorSettings, - deviceResource, -}) => { +const GpuSelectorWidget = ({ executorSettings, setExecutorSettings }) => { const theme = useTheme(); + const totalGpus = useSelector( + (state: RootState) => state.deviceStatsReducer.totalGpus + ); + const gpuModel = useSelector( + (state: RootState) => state.deviceStatsReducer.gpuModel + ); const incrementGpu = () => { - if (executorSettings.gpus < deviceResource.totalGpus) { + if (executorSettings.gpus < totalGpus) { setExecutorSettings((prev) => ({ ...prev, gpus: prev.gpus + 1, @@ -48,7 +52,7 @@ const GpuSelectorWidget = ({ - {deviceResource.gpuModel === '' ? ( + {gpuModel === '' ? ( <> ) : ( - - {`Model: ${deviceResource.gpuModel}`} - + {`Model: ${gpuModel}`} )} - {deviceResource.gpuModel !== '' && ( + {gpuModel !== '' && ( { const [isLoading, setIsLoading] = useState(false); + const [errorDialogOpen, setErrorDialogOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const handleCloseErrorDialog = () => { + setErrorDialogOpen(false); + }; + let initialExecutorSettings = { option: 'low', cpu_cores: 1, @@ -46,6 +47,48 @@ const HostSharingWidget = () => { ); } + useEffect(() => { + setIsLoading(true); + handleRetrieveDeviceStats(); + }, []); + + const handleRetrieveDeviceStats = async () => { + let success = false; + const maxRetries = 10; + const retryInterval = 500; + + for (let i = 0; i < maxRetries && !success; i++) { + try { + await unpauseExecutor(); + const resourceStats = await getResourceStats(); + console.log('resourceStats', resourceStats); + const totalCpuCores = resourceStats.total_cpu; + const totalMem = resourceStats.total_mem; + const totalGpus = resourceStats.task_gpu; + const gpuModel = resourceStats.gpu_model; + actions.setDeviceStats({ + totalCpuCores, + totalMem, + totalGpus, + gpuModel, + }); + await pauseExecutor(); + success = true; + setIsLoading(false); + } catch (error) { + console.error('Error retrieving device stats: ', error); + if (i < maxRetries - 1) { + console.log(`Retrying in ${retryInterval / 1000} seconds...`); + await new Promise((resolve) => setTimeout(resolve, retryInterval)); + } + } + } + + if (!success) { + console.error('Failed to retrieve device stats after several retries.'); + setIsLoading(false); + } + }; const [isExecutorSettingsSaved, setIsExecutorSettingsSaved] = useState( initialIsExecutorSettingsSaved ); @@ -54,42 +97,6 @@ const HostSharingWidget = () => { ); const [resourceSharingEnabled, setResourceSharingEnabled] = useState(false); - const [deviceResource, setDeviceResource] = useState({ - totalCpuCores: 4, - totalMem: 8192, - totalGpus: 0, - gpuModel: '', - }); - - const getDeviceResource = async () => { - await unpauseExecutor(); - const resourceStats = await getResourceStats(); - const totalCpuCores = resourceStats.total_cpu; - const totalMem = resourceStats.total_mem; - const totalGpus = resourceStats.task_gpu; - const gpuModel = resourceStats.gpu_model; - await pauseExecutor(); - return { totalCpuCores, totalMem, totalGpus, gpuModel }; - }; - - useEffect(() => { - console.log('deviceResource', deviceResource); - }, [deviceResource]); - - useEffect(() => { - const doGetDeviceResource = async () => { - const { totalCpuCores, totalMem, totalGpus, gpuModel } = - await getDeviceResource(); - setDeviceResource({ - totalCpuCores, - totalMem, - totalGpus, - gpuModel, - // gpuModel: 'NVIDIA GeForce RTX 3060 Ti', - }); - }; - doGetDeviceResource(); - }, []); const handleEnableResourceSharing = async () => { setIsLoading(true); @@ -99,15 +106,32 @@ const HostSharingWidget = () => { mem: executorSettings.memory_mb, microVM_runtime: 'kata', }; - await updateConfig(configToUpdate); - await handleRegisterHost( - executorSettings.cpu_cores, - executorSettings.memory_mb - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); // remove in production; solely for visualization of loading icon - setResourceSharingEnabled(true); - setIsLoading(false); + const containerName = 'meca_executor_test'; + try { + // const containerExist = await window.electron.checkContainerExist( + // containerName + // ); + + // if (containerExist) { + await updateConfig(configToUpdate); + await handleRegisterHost( + executorSettings.cpu_cores, + executorSettings.memory_mb + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); // for loading visualization + setResourceSharingEnabled(true); + // } else { + // return; + // } + } catch (error) { + setErrorMessage("Container is not valid or doesn't exist"); + setErrorDialogOpen(true); + console.error('Error:', error); + } finally { + setIsLoading(false); + } }; + const handleDisableResourceSharing = async () => { setIsLoading(true); await handleDeregisterHost(); @@ -115,6 +139,7 @@ const HostSharingWidget = () => { setResourceSharingEnabled(false); setIsLoading(false); }; + const BlurredBackground = styled('div')({ position: 'absolute', width: '100%', @@ -167,11 +192,11 @@ const HostSharingWidget = () => { ) : ( { /> )} + ); }; diff --git a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/MemorySelectorSlider.tsx b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/MemorySelectorSlider.tsx index edbfe84..d0594b8 100644 --- a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/MemorySelectorSlider.tsx +++ b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/MemorySelectorSlider.tsx @@ -1,10 +1,14 @@ import { Box, Stack, Typography, Slider } from '@mui/material'; +import { useSelector } from 'react-redux'; +import { RootState } from 'renderer/redux/store'; const MemorySelectorSlider = ({ executorSettings, setExecutorSettings, - totalMem, }) => { + const totalMem = useSelector( + (state: RootState) => state.deviceStatsReducer.totalMem + ); const handleSliderChange = (event, newValue) => { setExecutorSettings({ ...executorSettings, memory_mb: newValue }); }; diff --git a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PostSharingEnabledComponent.tsx b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PostSharingEnabledComponent.tsx index 2084965..f5038ff 100644 --- a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PostSharingEnabledComponent.tsx +++ b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PostSharingEnabledComponent.tsx @@ -53,7 +53,6 @@ const PostSharingEnabledComponent = ({ isLoading, setIsLoading, }) => { - const theme = useTheme(); const [resourcesLog, setResourcesLog] = useState({ total_cpu: 8, total_mem: 8192, @@ -69,7 +68,6 @@ const PostSharingEnabledComponent = ({ const interval = setInterval(async () => { const fetchResource = async () => { const resources = await getResourceStats(); - console.log("resources,", resources) setResourcesLog(resources); }; fetchResource(); diff --git a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PreSharingEnabledComponent.tsx b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PreSharingEnabledComponent.tsx index 47af298..5b8d65c 100644 --- a/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PreSharingEnabledComponent.tsx +++ b/src/renderer/components/navigation/leftDrawer/HostSharingWidget/PreSharingEnabledComponent.tsx @@ -3,7 +3,6 @@ import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import Checkbox from '@mui/material/Checkbox'; -import useIsLightTheme from 'renderer/components/common/useIsLightTheme'; import CoreSelectorWidget from './CoreSelectorWidget'; import MemorySelectorSlider from './MemorySelectorSlider'; import GpuSelectorWidget from './GpuSelectorWidget'; @@ -12,11 +11,11 @@ import PresetSelectorWidget from './PresetSelectorWidget'; const PreSharingEnabledComponent = ({ handleEnableResourceSharing, isLoading, + setIsLoading, isExecutorSettingsSaved, setIsExecutorSettingsSaved, executorSettings, setExecutorSettings, - deviceResource, }) => { const [allocateGPU, setAllocateGPU] = useState(false); @@ -27,8 +26,26 @@ const PreSharingEnabledComponent = ({ event.target.checked.toString() ); }; - const handleGPUCheckBoxChange = (event) => { - setAllocateGPU(event.target.checked); + + const handleGPUCheckBoxChange = async (event) => { + setIsLoading(true) + await window.electron.removeExecutorContainer('meca_executor_test'); + if (!allocateGPU) { + await window.electron + .runExecutorGpuContainer('meca_executor_test') + .catch((error) => { + console.error('Error starting GPU container:', error); + }); + setAllocateGPU(true); + } else { + await window.electron + .runExecutorContainer('meca_executor_test') + .catch((error) => { + console.error('Error starting container:', error); + }); + setAllocateGPU(false); + } + setIsLoading(false) }; useEffect(() => { @@ -86,12 +103,10 @@ const PreSharingEnabledComponent = ({ @@ -133,7 +148,6 @@ const PreSharingEnabledComponent = ({ diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index c38b24f..1dd95b7 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -3,6 +3,13 @@ import { Provider } from 'react-redux'; import reduxStore from './redux/store'; import App from './App'; + + +window.addEventListener('unhandledrejection', event => { + console.error('Unhandled rejection:', event.promise); + console.error('Reason:', event.reason); + // Additional actions like user notifications can be implemented here +}); const container = document.getElementById('root') as HTMLElement; const root = createRoot(container); root.render( diff --git a/src/renderer/redux/actionCreators.tsx b/src/renderer/redux/actionCreators.tsx index 0b2db5a..ebe2020 100644 --- a/src/renderer/redux/actionCreators.tsx +++ b/src/renderer/redux/actionCreators.tsx @@ -57,6 +57,11 @@ const setTransactionDetails: ActionCreator = (payload: any) => ({ payload, }); +const setDeviceStats: ActionCreator = (payload: any) => ({ + type: 'setDeviceStats', + payload, +}); + const boundToDoActions = bindActionCreators( { setAuthenticated, @@ -70,6 +75,7 @@ const boundToDoActions = bindActionCreators( setColor, setTransactionDetails, setImportingAccount, + setDeviceStats, }, reduxStore.dispatch ); diff --git a/src/renderer/redux/reducers.tsx b/src/renderer/redux/reducers.tsx index e1caed8..35bfb5d 100644 --- a/src/renderer/redux/reducers.tsx +++ b/src/renderer/redux/reducers.tsx @@ -47,6 +47,20 @@ const initialJobsState: JobsState = { jobResults: [], }; +interface DeviceStats { + totalCpuCores: number; + totalMem: number; + totalGpus: number; + gpuModel: string; +} + +const initialDeviceStats: DeviceStats = { + totalCpuCores: 4, + totalMem: 8192, + totalGpus: 0, + gpuModel: '', +}; + export const transactionDetailsReducer = (state = {}, action: any) => { switch (action.type) { case 'setTransactionDetails': @@ -129,6 +143,18 @@ export const importingAccountReducer = ( } }; +export const deviceStatsReducer = ( + state: DeviceStats = initialDeviceStats, + action: any +): DeviceStats => { + switch (action.type) { + case 'setDeviceStats': + return action.payload; + default: + return state; + } +}; + const jobsReducer = ( state: JobsState = initialJobsState, action: any @@ -174,6 +200,7 @@ const reducers = combineReducers({ userReducer: userReducer, themeReducer: themeReducer, importingAccountReducer: importingAccountReducer, + deviceStatsReducer: deviceStatsReducer, }); export default reducers; diff --git a/src/renderer/services/ExecutorServices.tsx b/src/renderer/services/ExecutorServices.tsx index 5921d49..b026923 100644 --- a/src/renderer/services/ExecutorServices.tsx +++ b/src/renderer/services/ExecutorServices.tsx @@ -12,6 +12,7 @@ export async function stopExecutor() { throw new Error(`Failed to stop executor: ${response.statusText}`); } const res = await response.json(); + console.log('stopped executor', res); return res; } catch (error) { console.error('There was a problem with the fetch operation:', error); @@ -29,6 +30,7 @@ export async function unpauseExecutor() { if (!response.ok) { throw new Error(`Failed to unpause executor: ${response.statusText}`); } + console.log('unpaused executor'); return true; } catch (error) { console.error('There was a problem with the fetch operation:', error); @@ -46,6 +48,7 @@ export async function pauseExecutor() { if (!response.ok) { throw new Error(`Failed to pause executor: ${response.statusText}`); } + console.log('paused executor'); return true; } catch (error) { console.error('There was a problem with the fetch operation:', error); diff --git a/src/renderer/services/RegistrationServices.tsx b/src/renderer/services/RegistrationServices.tsx index 0ee10b3..cdc6bda 100644 --- a/src/renderer/services/RegistrationServices.tsx +++ b/src/renderer/services/RegistrationServices.tsx @@ -72,8 +72,6 @@ export async function registerHost( retryCount = 0 ) { try { - console.log('token', token); - console.log('did', window.electron.store.get('did')); const response = await fetch(`${url}/registration/register_host`, { method: 'POST', headers: { @@ -90,7 +88,6 @@ export async function registerHost( } throw new Error('Network response not ok'); } - return true; } catch (error) { console.error('There was a problem with the fetch operation:', error); diff --git a/src/renderer/utils/useHandleAppExitHook.tsx b/src/renderer/utils/useHandleAppExitHook.tsx index b4cf15b..1e4b82e 100644 --- a/src/renderer/utils/useHandleAppExitHook.tsx +++ b/src/renderer/utils/useHandleAppExitHook.tsx @@ -1,15 +1,18 @@ +// import log from 'electron-log/renderer'; import { useEffect } from 'react'; +import { stopExecutor } from 'renderer/services/ExecutorServices'; import { handleDeregisterHost } from '../components/common/handleRegistration'; import reduxStore from '../redux/store'; -import log from 'electron-log/renderer'; const handleAppExit = async () => { const { accessToken } = reduxStore.getState().userReducer; - log.info('accessToken', accessToken); + // log.info('accessToken', accessToken); + await stopExecutor(); if (accessToken && accessToken !== '') { await handleDeregisterHost(); } }; + const useHandleAppExitHook = () => { useEffect(() => { const handleAppCloseInitiated = async () => {