From 08e3952c299b9af0b7fa8016b5fd3efa82168ad0 Mon Sep 17 00:00:00 2001 From: Rui Gomes Date: Mon, 26 Aug 2024 12:41:12 +0100 Subject: [PATCH 1/5] add cpu and memory and, UI enhancements --- .../AddModelModal/AddModelModal.jsx | 56 +++++++++---- .../ModelRunDetailsModal.css | 27 +++++++ .../ModelRunDetailsModal.jsx | 32 +++++--- .../RunModelModal/RunModelModal.jsx | 45 +++++++++-- src/index.css | 2 + src/pages/ModelManagement/ModelManagement.css | 28 +++++++ src/pages/ModelManagement/ModelManagement.jsx | 38 +++++++-- src/pages/ModelRunner/ModelRunner.css | 28 +++++++ src/pages/ModelRunner/ModelRunner.jsx | 79 ++++++++++++++++--- src/pages/SignIn/SignIn.css | 37 +++++++++ src/pages/SignIn/SignIn.jsx | 11 ++- 11 files changed, 328 insertions(+), 55 deletions(-) create mode 100644 src/pages/ModelManagement/ModelManagement.css create mode 100644 src/pages/SignIn/SignIn.css diff --git a/src/components/AddModelModal/AddModelModal.jsx b/src/components/AddModelModal/AddModelModal.jsx index c3d13c2..8646356 100644 --- a/src/components/AddModelModal/AddModelModal.jsx +++ b/src/components/AddModelModal/AddModelModal.jsx @@ -12,8 +12,10 @@ function AddModelModal({ show, handleClose, refreshModels }) { const [serialization, setSerialization] = useState('joblib'); const [features, setFeatures] = useState([{ name: '', type: 'int' }]); const [dependencies, setDependencies] = useState([{ library: '', version: '' }]); + const [memLimit, setMemLimit] = useState('256M'); + const [cpuPercentage, setCpuPercentage] = useState(50); const [file, setFile] = useState(null); - const [error, setError] = useState(null); + const [formError, setFormError] = useState(null); const handleAddFeature = () => setFeatures([...features, { name: '', type: 'int' }]); const handleRemoveFeature = (index) => setFeatures(features.filter((_, i) => i !== index)); @@ -23,15 +25,15 @@ function AddModelModal({ show, handleClose, refreshModels }) { const handleFileChange = (e) => { const selectedFile = e.target.files[0]; - const allowedExtensions = ['.py', '.sav', '.rds', '.zip']; + const allowedExtensions = ['.pkl', '.sav', '.rds', '.zip']; const fileExtension = selectedFile ? `.${selectedFile.name.split('.').pop()}` : ''; if (allowedExtensions.includes(fileExtension)) { setFile(selectedFile); - setError(null); + setFormError(null); } else { setFile(null); - setError('Only .py, .sav, .rds, and .zip files are allowed.'); + setFormError('Only .pkl, .sav, .rds, and .zip files are allowed.'); } }; @@ -45,6 +47,8 @@ function AddModelModal({ show, handleClose, refreshModels }) { if (engine === 'docker') { formData.append('language', language); + formData.append('mem_limit', memLimit); + formData.append('cpu_percentage', cpuPercentage); } if (type === 'predictive') { @@ -65,7 +69,7 @@ function AddModelModal({ show, handleClose, refreshModels }) { handleClose(); } catch (error) { const errorMessage = error.response?.data?.error; - setError( + setFormError( Array.isArray(errorMessage) ? errorMessage.map(err => err.msg).join(', ') : errorMessage || 'An unexpected error occurred.' @@ -79,7 +83,6 @@ function AddModelModal({ show, handleClose, refreshModels }) { Add New Model - {error && {error}}
Name @@ -98,18 +101,41 @@ function AddModelModal({ show, handleClose, refreshModels }) { Engine setEngine(e.target.value)}> - {engine === 'docker' && ( - - Language - setLanguage(e.target.value)}> - - - - + <> + + Language + setLanguage(e.target.value)}> + + {type === 'predictive' && } + + + + + Memory Limit + setMemLimit(e.target.value)} + placeholder="e.g., 256M" + required + /> + + + + CPU Percentage + setCpuPercentage(e.target.value)} + placeholder="e.g., 50" + required + /> + + )} {type === 'predictive' && engine === 'docker' && language === 'Python3' && ( @@ -200,6 +226,8 @@ function AddModelModal({ show, handleClose, refreshModels }) { + {formError && {formError}} +
diff --git a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.css b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.css index 986744f..f9728a2 100644 --- a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.css +++ b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.css @@ -1,6 +1,33 @@ +/* src/components/ModelRunDetailsModal/ModelRunDetailsModal.css */ + .custom-modal-width { max-width: 80%; /* Adjust the percentage as needed */ width: 800px; /* Or set a fixed width */ +} + +.state-queue { + color: gray; +} + +.state-building { + color: #0d6efd; +} + +.state-running { + color: orange; +} + +.state-finished { + color: green; +} + +.state-failed { + color: red; +} + +.modal-body { + word-wrap: break-word; + /* Ensure long words break and wrap */ } \ No newline at end of file diff --git a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx index 5961288..62256fa 100644 --- a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx +++ b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx @@ -17,7 +17,7 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { headers: { Authorization: `Bearer ${token}` }, }); - const { data: modelData } = await axios.get(`${API_ENDPOINTS.MODEL}/${runData.model_id}`, { + const { data: modelData } = await axios.get(`${API_ENDPOINTS.MODELS}/${runData.model_id}`, { headers: { Authorization: `Bearer ${token}` }, }); @@ -47,6 +47,8 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { )); }; + const stateClass = runDetails ? `state-${runDetails.state}` : ''; + return ( @@ -55,18 +57,28 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { {runDetails ? ( <> -
Model ID: {runDetails.model_id}
-
State: {runDetails.state}
-
Created At: {new Date(runDetails.createdAt).toLocaleString()}
-
Updated At: {new Date(runDetails.updatedAt).toLocaleString()}
-
Container ID: {runDetails.container_id}
-
Container Exit Code: {runDetails.container_exit_code}
-
Result:
+ Model ID: {runDetails.model_id}
+ State: {runDetails.state}
+ Created At: {new Date(runDetails.createdAt).toLocaleString()}
+ Updated At: {new Date(runDetails.updatedAt).toLocaleString()}
+ {runDetails.container_id !== undefined && runDetails.container_id !== null && ( + <> + Container ID: {runDetails.container_id}
+ + )} + + {runDetails.container_exit_code !== undefined && runDetails.container_exit_code !== null && ( + <> + Container Exit Code: {runDetails.container_exit_code}
+ + )} +
+ Result:
{runDetails.result}
{modelType !== 'optimization' && ( <> -
Input Features:
- + Input Features: +
diff --git a/src/components/RunModelModal/RunModelModal.jsx b/src/components/RunModelModal/RunModelModal.jsx index 36dbc53..0e57d25 100644 --- a/src/components/RunModelModal/RunModelModal.jsx +++ b/src/components/RunModelModal/RunModelModal.jsx @@ -1,6 +1,6 @@ // src/components/RunModelModal/RunModelModal.jsx -import React, { useState, useEffect, useCallback, useMemo } from 'react'; -import { Modal, Button, Form } from 'react-bootstrap'; +import React, { useState, useEffect, useCallback } from 'react'; +import { Modal, Button, Form, Alert } from 'react-bootstrap'; import axios from 'axios'; import { API_ENDPOINTS } from '../../config/config'; @@ -12,6 +12,7 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { const [inputValues, setInputValues] = useState({}); const [file, setFile] = useState(null); const [fileError, setFileError] = useState(''); + const [formError, setFormError] = useState(''); const token = localStorage.getItem('token'); @@ -22,7 +23,12 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { }); setModels(response.data); } catch (error) { - console.error('Error fetching models:', error); + const errorMessage = error.response?.data?.error; + setFormError( + Array.isArray(errorMessage) + ? errorMessage.map(err => err.msg).join(', ') + : errorMessage || 'Failed to load models. Please try again.' + ); } }, [token]); @@ -40,7 +46,12 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { setFeatures(features); setModelType(type); } catch (error) { - console.error('Error fetching model details:', error); + const errorMessage = error.response?.data?.error; + setFormError( + Array.isArray(errorMessage) + ? errorMessage.map(err => err.msg).join(', ') + : errorMessage || 'Failed to load model details. Please try again.' + ); } } }, [selectedModel, token]); @@ -54,6 +65,7 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { setInputValues({}); setFile(null); setFileError(''); + setFormError(''); }; const handleInputChange = (feature, event) => { @@ -105,7 +117,7 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { model_id: selectedModel, input_features: features.map(feature => ({ name: feature.name, - value: inputValues[feature.name] == '0' ? 0 : (inputValues[feature.name] || '') + value: inputValues[feature.name] === '0' || inputValues[feature.name] === '0.0' ? 0 : (inputValues[feature.name] || '') })) }; headers['Content-Type'] = 'application/json'; @@ -125,10 +137,25 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { handleClose(); refreshModelRuns(); } catch (error) { - console.error('Error running model:', error); + const errorMessage = error.response?.data?.error; + setFormError( + Array.isArray(errorMessage) + ? errorMessage.map(err => err.msg).join(', ') + : errorMessage || 'Failed to run the model. Please try again.' + ); } }; + useEffect(() => { + if (show) { + const params = new URLSearchParams(window.location.search); + const modelId = params.get('model_id'); + if (modelId && models.length > 0) { + setSelectedModel(modelId); + } + } + }, [show, models]); + return ( @@ -146,6 +173,8 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { + {selectedModel &&
} + {modelType === 'predictive' && features.map(feature => ( {feature.name} @@ -168,6 +197,10 @@ const RunModelModal = ({ show, handleClose, refreshModelRuns }) => { )} +
+ + {formError && {formError}} + diff --git a/src/index.css b/src/index.css index 3e3b6a1..cf72ae7 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,5 @@ +/* src/index.css */ + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', diff --git a/src/pages/ModelManagement/ModelManagement.css b/src/pages/ModelManagement/ModelManagement.css new file mode 100644 index 0000000..314b082 --- /dev/null +++ b/src/pages/ModelManagement/ModelManagement.css @@ -0,0 +1,28 @@ +/* src/pages/ModelManagement/ModelManagement.css */ + +/* Arrow styling */ +th { + cursor: pointer; + position: relative; +} + +th::after { + content: ''; + display: inline-block; + width: 0; + height: 0; + margin-left: 5px; + vertical-align: middle; +} + +th.sort-asc::after { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid black; +} + +th.sort-desc::after { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid black; +} \ No newline at end of file diff --git a/src/pages/ModelManagement/ModelManagement.jsx b/src/pages/ModelManagement/ModelManagement.jsx index 757a26d..0f05df8 100644 --- a/src/pages/ModelManagement/ModelManagement.jsx +++ b/src/pages/ModelManagement/ModelManagement.jsx @@ -1,10 +1,11 @@ // src/pages/ModelManagement/ModelManagement.jsx import React, { useEffect, useState } from 'react'; +import { Table, Button } from 'react-bootstrap'; import axios from 'axios'; import AddModelModal from '../../components/AddModelModal/AddModelModal'; -import { Button } from 'react-bootstrap'; import { useNavigate } from 'react-router-dom'; import { API_ENDPOINTS } from '../../config/config'; +import './ModelManagement.css'; function ModelManagement() { const [models, setModels] = useState([]); @@ -51,17 +52,38 @@ function ModelManagement() { navigate(`/model-runner?model_id=${modelId}`); }; + const getSortArrow = (key) => { + if (sortConfig.key === key) { + return sortConfig.direction === 'asc' ? '↑' : '↓'; + } + return ''; + }; + return (

Model Management

-
Name
+
- - - - + + + + + + @@ -70,11 +92,13 @@ function ModelManagement() { + + ))} -
handleSort('name')}>Name handleSort('type')}>Type handleSort('engine')}>Engine handleSort('created_at')}>Created At handleSort('name')}> + Name {getSortArrow('name')} + handleSort('type')}> + Type {getSortArrow('type')} + handleSort('engine')}> + Engine {getSortArrow('engine')} + handleSort('language')}> + Language {getSortArrow('language')} + handleSort('serialization')}> + Serialization {getSortArrow('serialization')} + handleSort('created_at')}> + Created On {getSortArrow('created_at')} +
{model.name} {model.type} {model.engine}{model.language}{model.serialization} {new Date(model.created_at).toLocaleString()}
+ ); diff --git a/src/pages/ModelRunner/ModelRunner.css b/src/pages/ModelRunner/ModelRunner.css index 143bc18..9258ae3 100644 --- a/src/pages/ModelRunner/ModelRunner.css +++ b/src/pages/ModelRunner/ModelRunner.css @@ -1,3 +1,5 @@ +/* src/components/ModelRunner/ModelRunner.css */ + .spinner { animation: spin 1s linear infinite; } @@ -10,4 +12,30 @@ 100% { transform: rotate(360deg); } +} + +/* Arrow styling */ +th { + cursor: pointer; +} + +th::after { + content: ''; + display: inline-block; + width: 0; + height: 0; + margin-left: 5px; + vertical-align: middle; +} + +th.sort-asc::after { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid black; +} + +th.sort-desc::after { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid black; } \ No newline at end of file diff --git a/src/pages/ModelRunner/ModelRunner.jsx b/src/pages/ModelRunner/ModelRunner.jsx index 4d8f2f9..7b24d77 100644 --- a/src/pages/ModelRunner/ModelRunner.jsx +++ b/src/pages/ModelRunner/ModelRunner.jsx @@ -1,8 +1,8 @@ // src/components/ModelRunner/ModelRunner.jsx import React, { useEffect, useState } from 'react'; +import { Table, Button } from 'react-bootstrap'; import axios from 'axios'; import { FaCheckCircle, FaTimesCircle, FaSpinner } from 'react-icons/fa'; -import { Button } from 'react-bootstrap'; import { useLocation } from 'react-router-dom'; import './ModelRunner.css'; import RunModelModal from '../../components/RunModelModal/RunModelModal'; @@ -14,6 +14,7 @@ function ModelRunner() { const [showModal, setShowModal] = useState(false); const [showDetailsModal, setShowDetailsModal] = useState(false); const [selectedRunId, setSelectedRunId] = useState(null); + const [selectedModelId, setSelectedModelId] = useState(null); const [sortConfig, setSortConfig] = useState({ key: 'updatedAt', direction: 'desc' }); const location = useLocation(); @@ -26,7 +27,13 @@ function ModelRunner() { 'Authorization': `Bearer ${token}` } }); - setModelRuns(response.data); + + const runsWithDuration = response.data.map(run => { + const durationMs = new Date(run.updatedAt) - new Date(run.createdAt); + return { ...run, durationMs }; + }); + + setModelRuns(runsWithDuration); } catch (error) { console.error('Error fetching model runs:', error); } @@ -35,13 +42,14 @@ function ModelRunner() { useEffect(() => { const queryParams = new URLSearchParams(location.search); const modelId = queryParams.get('model_id'); + setSelectedModelId(modelId); fetchModelRuns(modelId); const intervalId = setInterval(() => { fetchModelRuns(modelId); - }, 5000); + }, 3000); - return () => clearInterval(intervalId); // Clear interval on component unmount + return () => clearInterval(intervalId); }, [location.search]); const getStateIcon = (state) => { @@ -74,36 +82,83 @@ function ModelRunner() { }; const sortedModelRuns = [...modelRuns].sort((a, b) => { + if (sortConfig.key === 'duration') { + // Sorting by duration in milliseconds + if (a.durationMs < b.durationMs) return sortConfig.direction === 'asc' ? -1 : 1; + if (a.durationMs > b.durationMs) return sortConfig.direction === 'asc' ? 1 : -1; + return 0; + } + // Default sorting by other keys if (a[sortConfig.key] < b[sortConfig.key]) return sortConfig.direction === 'asc' ? -1 : 1; if (a[sortConfig.key] > b[sortConfig.key]) return sortConfig.direction === 'asc' ? 1 : -1; return 0; }); + const formatDuration = (start, end) => { + const durationMs = new Date(end) - new Date(start); + + const days = Math.floor(durationMs / (1000 * 60 * 60 * 24)); + const hours = Math.floor((durationMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((durationMs % (1000 * 60)) / 1000); + + let durationStr = ''; + if (days > 0) durationStr += `${days}d `; + if (hours > 0 || days > 0) durationStr += `${hours}h `; + if (minutes > 0 || hours > 0 || days > 0) durationStr += `${minutes}m `; + durationStr += `${seconds}s`; + + return durationStr.trim(); + }; + + const getSortArrow = (key) => { + if (sortConfig.key === key) { + return sortConfig.direction === 'asc' ? '↑' : '↓'; + } + return ''; + }; + return (

Model Runner

- +
- - - - + + + + + + {sortedModelRuns.map(run => ( handleRowClick(run._id)}> + + - + ))} -
handleSort('model_name')}>Model Name handleSort('updatedAt')}>Updated At handleSort('duration')}>Duration (s) handleSort('state')}>State handleSort('model_name')}> + Model Name {getSortArrow('model_name')} + handleSort('model_type')}> + Type {getSortArrow('model_type')} + handleSort('model_engine')}> + Engine {getSortArrow('model_engine')} + handleSort('updatedAt')}> + Last Updated On {getSortArrow('updatedAt')} + handleSort('duration')}> + Duration {getSortArrow('duration')} + handleSort('state')}> + State {getSortArrow('state')} +
{run.model_name}{run.model_type}{run.model_engine} {new Date(run.updatedAt).toLocaleString()}{Math.round((new Date(run.updatedAt) - new Date(run.createdAt)) / 1000)}{formatDuration(run.createdAt, run.updatedAt)} {getStateIcon(run.state)}
- fetchModelRuns()} /> + + fetchModelRuns(selectedModelId)} />
); diff --git a/src/pages/SignIn/SignIn.css b/src/pages/SignIn/SignIn.css new file mode 100644 index 0000000..70f345b --- /dev/null +++ b/src/pages/SignIn/SignIn.css @@ -0,0 +1,37 @@ +/* src/components/SignIn/SignIn.css */ + +.sign-in-container { + max-width: 400px; + /* Adjust the max-width as needed */ + margin: 0 auto; + padding: 20px; +} + +.sign-in-form { + display: flex; + flex-direction: column; +} + +.mb-2 { + margin-bottom: 1rem; + /* Reduced margin for compact spacing */ +} + +.form-label { + margin-bottom: 0.5rem; + /* Reduced margin for compact spacing */ +} + +.form-control-sm { + font-size: 0.875rem; + /* Smaller font size for compact input */ + padding: 0.375rem 0.75rem; + /* Adjust padding for smaller input */ +} + +.btn-sm { + padding: 0.25rem 0.5rem; + /* Smaller padding for compact button */ + font-size: 0.875rem; + /* Smaller font size for button */ +} \ No newline at end of file diff --git a/src/pages/SignIn/SignIn.jsx b/src/pages/SignIn/SignIn.jsx index 7baba54..e5c6597 100644 --- a/src/pages/SignIn/SignIn.jsx +++ b/src/pages/SignIn/SignIn.jsx @@ -1,13 +1,12 @@ // src/components/SignIn/SignIn.jsx import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import { API_ENDPOINTS } from '../../config/config'; +import './SignIn.css'; function SignIn() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); @@ -28,13 +27,13 @@ function SignIn() { }; return ( -
-
+
+
setEmail(e.target.value)} @@ -45,7 +44,7 @@ function SignIn() { setPassword(e.target.value)} From 100f19fefdff17a220bb3c215449496704748a7e Mon Sep 17 00:00:00 2001 From: Rui Gomes Date: Mon, 26 Aug 2024 15:23:02 +0100 Subject: [PATCH 2/5] add docker tag --- .../AddModelModal/AddModelModal.jsx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/AddModelModal/AddModelModal.jsx b/src/components/AddModelModal/AddModelModal.jsx index 8646356..8786088 100644 --- a/src/components/AddModelModal/AddModelModal.jsx +++ b/src/components/AddModelModal/AddModelModal.jsx @@ -9,6 +9,7 @@ function AddModelModal({ show, handleClose, refreshModels }) { const [type, setType] = useState('predictive'); const [engine, setEngine] = useState('docker'); const [language, setLanguage] = useState('Python3'); + const [dockerTag, setDockerTag] = useState('3.9'); const [serialization, setSerialization] = useState('joblib'); const [features, setFeatures] = useState([{ name: '', type: 'int' }]); const [dependencies, setDependencies] = useState([{ library: '', version: '' }]); @@ -37,6 +38,17 @@ function AddModelModal({ show, handleClose, refreshModels }) { } }; + const handleLanguageChange = (e) => { + const selectedLanguage = e.target.value; + setLanguage(selectedLanguage); + + if (selectedLanguage === 'R') { + setDockerTag('24.04'); + } else if (selectedLanguage === 'Python3') { + setDockerTag('3.9'); + } + }; + const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(); @@ -47,6 +59,7 @@ function AddModelModal({ show, handleClose, refreshModels }) { if (engine === 'docker') { formData.append('language', language); + formData.append('docker_tag', dockerTag); formData.append('mem_limit', memLimit); formData.append('cpu_percentage', cpuPercentage); } @@ -108,12 +121,22 @@ function AddModelModal({ show, handleClose, refreshModels }) { <> Language - setLanguage(e.target.value)}> + handleLanguageChange(e)}> {type === 'predictive' && } + + Docker Tag + setDockerTag(e.target.value)} + required + /> + + Memory Limit Date: Tue, 27 Aug 2024 15:20:24 +0100 Subject: [PATCH 3/5] fix --- src/components/AddModelModal/AddModelModal.jsx | 2 +- .../ModelRunDetailsModal/ModelRunDetailsModal.jsx | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/AddModelModal/AddModelModal.jsx b/src/components/AddModelModal/AddModelModal.jsx index 8786088..dbfbd3a 100644 --- a/src/components/AddModelModal/AddModelModal.jsx +++ b/src/components/AddModelModal/AddModelModal.jsx @@ -43,7 +43,7 @@ function AddModelModal({ show, handleClose, refreshModels }) { setLanguage(selectedLanguage); if (selectedLanguage === 'R') { - setDockerTag('24.04'); + setDockerTag('4.1.3'); } else if (selectedLanguage === 'Python3') { setDockerTag('3.9'); } diff --git a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx index 62256fa..ab18876 100644 --- a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx +++ b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx @@ -61,20 +61,26 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { State: {runDetails.state}
Created At: {new Date(runDetails.createdAt).toLocaleString()}
Updated At: {new Date(runDetails.updatedAt).toLocaleString()}
- {runDetails.container_id !== undefined && runDetails.container_id !== null && ( + {runDetails.container_id !== undefined && runDetails.container_id !== null && runDetails.container_id !== '' && ( <> Container ID: {runDetails.container_id}
)} - {runDetails.container_exit_code !== undefined && runDetails.container_exit_code !== null && ( + {(runDetails.state == 'failed' || runDetails.state == 'finished') && ( <> Container Exit Code: {runDetails.container_exit_code}
)}
- Result: -
{runDetails.result}
+ + {(runDetails.state == 'failed' || runDetails.state == 'finished') && ( + <> + Result: +
{runDetails.result}
+ + )} + {modelType !== 'optimization' && ( <> Input Features: From 99e424dda73c0cd9afacd819f79ce592fe22a92c Mon Sep 17 00:00:00 2001 From: Rui Gomes Date: Tue, 27 Aug 2024 15:25:21 +0100 Subject: [PATCH 4/5] fix --- .../ModelRunDetailsModal.jsx | 39 ++++++++++--------- src/pages/ModelRunner/ModelRunner.jsx | 4 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx index ab18876..fbd6ee7 100644 --- a/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx +++ b/src/components/ModelRunDetailsModal/ModelRunDetailsModal.jsx @@ -9,28 +9,31 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { const [runDetails, setRunDetails] = useState(null); const [modelType, setModelType] = useState(''); - useEffect(() => { - const fetchRunDetails = async () => { - const token = localStorage.getItem('token'); - try { - const { data: runData } = await axios.get(`${API_ENDPOINTS.MODEL_RUNS}/${runId}`, { - headers: { Authorization: `Bearer ${token}` }, - }); + const fetchRunDetails = async () => { + const token = localStorage.getItem('token'); + try { + const { data: runData } = await axios.get(`${API_ENDPOINTS.MODEL_RUNS}/${runId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); - const { data: modelData } = await axios.get(`${API_ENDPOINTS.MODELS}/${runData.model_id}`, { - headers: { Authorization: `Bearer ${token}` }, - }); + const { data: modelData } = await axios.get(`${API_ENDPOINTS.MODELS}/${runData.model_id}`, { + headers: { Authorization: `Bearer ${token}` }, + }); - setRunDetails(runData); - setModelType(modelData.type); - } catch (error) { - console.error('Error fetching run details:', error); - setRunDetails(null); - } - }; + setRunDetails(runData); + setModelType(modelData.type); + } catch (error) { + console.error('Error fetching run details:', error); + setRunDetails(null); + } + }; + useEffect(() => { if (show && runId) { fetchRunDetails(); + const intervalId = setInterval(fetchRunDetails, 3000); // Update every 3 seconds + + return () => clearInterval(intervalId); // Clean up interval on component unmount } }, [show, runId]); @@ -78,7 +81,7 @@ const ModelRunDetailsModal = ({ show, handleClose, runId }) => { <> Result:
{runDetails.result}
- + )} {modelType !== 'optimization' && ( diff --git a/src/pages/ModelRunner/ModelRunner.jsx b/src/pages/ModelRunner/ModelRunner.jsx index 7b24d77..75595eb 100644 --- a/src/pages/ModelRunner/ModelRunner.jsx +++ b/src/pages/ModelRunner/ModelRunner.jsx @@ -47,9 +47,9 @@ function ModelRunner() { const intervalId = setInterval(() => { fetchModelRuns(modelId); - }, 3000); + }, 3000); // Update every 3 seconds - return () => clearInterval(intervalId); + return () => clearInterval(intervalId); // Clean up interval on component unmount }, [location.search]); const getStateIcon = (state) => { From 8015f450d25cdf011ec7bd2bf55d6b70b8654a02 Mon Sep 17 00:00:00 2001 From: Rui Gomes Date: Tue, 27 Aug 2024 15:34:33 +0100 Subject: [PATCH 5/5] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c83a30f..bed6197 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

ABI Deployment Thesis - Web Application

-Welcome to the abi-deployment-webapp repository! This project is a key part of a master's thesis at the University of Minho. It's a Proof of Concept for a proposed architecture designed to deploy and integrate intelligent models within ABI (Adaptive Business Intelligence) systems. +Welcome to the abi-deployment-webapp repository! This project is a key part of a master's thesis at the University of Minho. It's a Proof of Concept for a proposed architecture designed to deploy and integrate intelligent models within Adaptive Business Intelligence (ABI) systems. **This repository provides a web application that serves as the user interface for interacting with the PoC.**