diff --git a/backend/app/internal/flowsheet_manager.py b/backend/app/internal/flowsheet_manager.py index ea815daa..b3468771 100644 --- a/backend/app/internal/flowsheet_manager.py +++ b/backend/app/internal/flowsheet_manager.py @@ -2,6 +2,8 @@ import sys import types from xml.etree.ElementTree import QName +import os +import importlib if sys.version_info < (3, 10): from importlib_resources import files @@ -74,6 +76,10 @@ def __init__(self, **kwargs): self.app_settings = AppSettings(**kwargs) self._objs, self._flowsheets = {}, {} + # Add custom flowsheets path to the system path + self.custom_flowsheets_path = Path.home() / ".watertap" / "custom_flowsheets" + sys.path.append(str(self.custom_flowsheets_path)) + for package in self.app_settings.packages: _log.debug(f"Collect flowsheet interfaces from package '{package}'") for name, fsi in self._get_flowsheet_interfaces(package).items(): @@ -395,15 +401,71 @@ def _get_flowsheet_interfaces( return interfaces + def add_custom_flowsheet(self, new_files, new_id): + """Add new custom flowsheet to the mini db.""" + + query = tinydb.Query() + try: + custom_flowsheets_dict = self._histdb.search(query.fragment({"custom_flowsheets_version": VERSION})) + if(len(custom_flowsheets_dict) == 0): + _log.error('unable to find custom flowsheets dictionary') + custom_flowsheets_dict = {} + else: + custom_flowsheets_dict = custom_flowsheets_dict[0]["custom_flowsheets_dict"] + except Exception as e: + _log.error(f'error trying to find custom flowsheets dictionary: {e}') + _log.error(f'setting it as empty dictionary') + custom_flowsheets_dict = {} + custom_flowsheets_dict[new_id] = new_files + + self._histdb.upsert( + {"custom_flowsheets_version": VERSION, "custom_flowsheets_dict": custom_flowsheets_dict}, + (query.custom_flowsheets_version == VERSION), + ) + + self.add_custom_flowsheets() + + def remove_custom_flowsheet(self, id_): + """Remove a custom flowsheet from the mini db.""" + query = tinydb.Query() + try: + custom_flowsheets_dict = self._histdb.search(query.fragment({"custom_flowsheets_version": VERSION})) + if(len(custom_flowsheets_dict) == 0): + _log.error('unable to find custom flowsheets dictionary') + custom_flowsheets_dict = {} + else: + custom_flowsheets_dict = custom_flowsheets_dict[0]["custom_flowsheets_dict"] + except Exception as e: + _log.error(f'error trying to find custom flowsheets dictionary: {e}') + _log.error(f'setting it as empty dictionary') + custom_flowsheets_dict = {} + + # remove each file + flowsheet_files = custom_flowsheets_dict[id_] + for flowsheet_file in flowsheet_files: + flowsheet_file_path = self.custom_flowsheets_path / flowsheet_file + _log.info(f'flowsheet file path: {flowsheet_file_path}') + if os.path.isfile(flowsheet_file_path): + _log.info(f'removing file: {flowsheet_file_path}') + os.remove(flowsheet_file_path) + + + # delete from DB + del custom_flowsheets_dict[id_] + self._histdb.upsert( + {"custom_flowsheets_version": VERSION, "custom_flowsheets_dict": custom_flowsheets_dict}, + (query.custom_flowsheets_version == VERSION), + ) + + # remove from flowsheets list + del self._flowsheets[id_] + + self.add_custom_flowsheets() + def add_custom_flowsheets(self): """Search for user uploaded flowsheets. If found, add them as flowsheet interfaces.""" - from os import walk - import importlib - custom_flowsheets_path = Path.home() / ".watertap" / "custom_flowsheets" - sys.path.append(str(custom_flowsheets_path)) - files = [] - for (_, _, filenames) in walk(custom_flowsheets_path): + for (_, _, filenames) in os.walk(self.custom_flowsheets_path): files.extend(filenames) break diff --git a/backend/app/routers/flowsheets.py b/backend/app/routers/flowsheets.py index bc214649..601968aa 100644 --- a/backend/app/routers/flowsheets.py +++ b/backend/app/routers/flowsheets.py @@ -223,20 +223,33 @@ async def upload_flowsheet(files: List[UploadFile]) -> str: custom_flowsheets_path = Path.home() / ".watertap" / "custom_flowsheets" try: # get file contents - + new_files = [] + print('trying to read files with aiofiles') for file in files: # for file in files: print(file.filename) + new_files.append(file.filename) + if '_ui.py' in file.filename: + new_id = file.filename.replace('.py', '') async with aiofiles.open(f'{str(custom_flowsheets_path)}/{file.filename}', 'wb') as out_file: content = await file.read() # async read await out_file.write(content) - flowsheet_manager.add_custom_flowsheets() + flowsheet_manager.add_custom_flowsheet(new_files, new_id) return {'return': 'success boy'} except Exception as e: _log.error(f"error on file upload: {str(e)}") raise HTTPException(400, detail=f"File upload failed: {e}") + +@router.post("/{flowsheet_id}/remove_flowsheet") +async def remove_flowsheet(flowsheet_id: str): + try: + flowsheet_manager.remove_custom_flowsheet(flowsheet_id) + return {'return': 'success boy'} + except Exception as e: + _log.error(f"error on flowsheet deletion: {str(e)}") + raise HTTPException(400, detail=f"failed: {e}") @router.get("/{flowsheet_id}/load") diff --git a/electron/ui/src/components/FlowsheetsListTable/FlowsheetsListTable.js b/electron/ui/src/components/FlowsheetsListTable/FlowsheetsListTable.js index 3252c1d7..1cf9423b 100644 --- a/electron/ui/src/components/FlowsheetsListTable/FlowsheetsListTable.js +++ b/electron/ui/src/components/FlowsheetsListTable/FlowsheetsListTable.js @@ -1,8 +1,11 @@ import { useState } from 'react' import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, Icon, Button } from '@mui/material' import { useNavigate } from "react-router-dom"; +import { deleteFlowsheet } from "../../services/flowsheetsList.service"; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import ClearIcon from '@mui/icons-material/Clear'; + export default function FlowsheetsListTable(props) { @@ -56,6 +59,26 @@ export default function FlowsheetsListTable(props) { } + const handleRemoveCustomFlowsheet = (e, id) => { + e.stopPropagation() + deleteFlowsheet(id) + .then(response => { + if (response.status === 200) { + response.json() + .then((data)=>{ + console.log('delete successful: ',data) + window.location.reload() + + }).catch((err)=>{ + console.error("error on flowshete deletion: ",err) + }) + } + else if (response.status === 400) { + console.error("error on flowshete deletion: ",response.statusText) + } + }) + } + function compare( a, b ) { if ( a[sortKey] < b[sortKey] ){ if(sortDirection === "ascending") return -1; @@ -99,7 +122,13 @@ export default function FlowsheetsListTable(props) { > {row.description} {formatLastRun(row.last_run)} - + + {row.custom && + handleRemoveCustomFlowsheet(e, row.id_)}> + + + } + ))} diff --git a/electron/ui/src/components/NewFlowsheetDialog/NewFlowsheetDialog.js b/electron/ui/src/components/NewFlowsheetDialog/NewFlowsheetDialog.js index 25cee921..ccbce66d 100644 --- a/electron/ui/src/components/NewFlowsheetDialog/NewFlowsheetDialog.js +++ b/electron/ui/src/components/NewFlowsheetDialog/NewFlowsheetDialog.js @@ -14,7 +14,7 @@ export default function NewFlowsheetDialog(props) { const [ showWarning, setShowWarning ] = useState(false) const [ warningMessage, setWarningMessage ] = useState("") const [ files, setFiles ] = useState({"Model File": null, "Export File": null, "Diagram File": null, "Data Files": []}) - const fileTypes = {"Model File": ["py"], "Export File": ["py"], "Diagram File": ["png"], "Data Files": ["yaml", "yml", "json", "csv"], }; + const fileTypes = {"Model File": ["py"], "Export File": ["py"], "Diagram File": ["png", "jpeg", "jpg"], "Data Files": ["yaml", "yml", "json", "csv", "txt", "zip"], }; const styles = { modalStyle: { position: 'absolute', @@ -148,7 +148,7 @@ export default function NewFlowsheetDialog(props) { try { let newWarningMessage = "Please choose a valid file type from these options: " for (let fileType of fileTypes[fileId]) { - newWarningMessage+= fileType+", " + newWarningMessage+= fileType+" " } setWarningMessage(newWarningMessage) setShowWarning(true) diff --git a/electron/ui/src/services/flowsheetsList.service.js b/electron/ui/src/services/flowsheetsList.service.js index 9ff285d8..7216f9d8 100644 --- a/electron/ui/src/services/flowsheetsList.service.js +++ b/electron/ui/src/services/flowsheetsList.service.js @@ -13,4 +13,11 @@ export const uploadFlowsheet = (data) => { mode: 'cors', body: data }); +}; + +export const deleteFlowsheet = (id) => { + return fetch('http://localhost:8001/flowsheets/'+id+'/remove_flowsheet', { + method: 'POST', + mode: 'cors' + }); }; \ No newline at end of file