From c2fea6f1c27354b4cc00b707cd5dfbb1b001a026 Mon Sep 17 00:00:00 2001 From: Nikolay Martyanov Date: Tue, 24 Jun 2025 16:29:20 +0200 Subject: [PATCH] feat: allow updating device info --- backend/devmateback/devices.py | 24 +++++++++++++++++++++++ backend/tests/test_app.py | 31 +++++++++++++++++++++++++++++ webui/src/App.js | 36 ++++++++++++++++++++++++++++++++++ webui/src/DeviceRow.jsx | 3 ++- webui/src/DevicesList.jsx | 3 ++- webui/src/MaintenanceGroup.jsx | 26 +++++++++++++++++++++--- 6 files changed, 118 insertions(+), 5 deletions(-) diff --git a/backend/devmateback/devices.py b/backend/devmateback/devices.py index c9ec1ed..4c58010 100644 --- a/backend/devmateback/devices.py +++ b/backend/devmateback/devices.py @@ -170,3 +170,27 @@ def delete_device(device): else: logger.error(f'Device {device} not found') return jsonify({'message': 'Device not found'}), HTTPStatus.NOT_FOUND + + +@devices_bp.route('/update', methods=['POST']) +def update_device_info(): + """Update information field for a device.""" + logger.debug('Updating device info') + + is_valid, error_response, status_code = validate_request(request, ['device', 'info']) + if not is_valid: + logger.error('Invalid request') + return error_response, status_code + + device_name = request.json['device'] + info = request.json['info'] + + device = Device.query.filter_by(name=device_name).first() + if device: + device.info = info + db.session.commit() + logger.info(f'Device {device} info updated') + return jsonify({'message': 'Device info updated'}), HTTPStatus.OK + else: + logger.error(f'Device {device_name} not found') + return jsonify({'message': 'Device not found'}), HTTPStatus.NOT_FOUND diff --git a/backend/tests/test_app.py b/backend/tests/test_app.py index 74a09ee..c54e268 100644 --- a/backend/tests/test_app.py +++ b/backend/tests/test_app.py @@ -253,6 +253,37 @@ def test_delete_nonexistent_device(self): self.assertEqual(response.get_json(), {'message': 'Device not found'}) +class TestUpdateDeviceInfo(BaseTestCase): + + def test_update_device_info_success(self): + with app.app_context(): + device = Device(name='Device1', model='Model1', status='free', info='old') + db.session.add(device) + db.session.commit() + + response = self.client.post('/devices/update', json={'device': 'Device1', 'info': 'new info'}) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual(response.get_json(), {'message': 'Device info updated'}) + with app.app_context(): + device = Device.query.filter_by(name='Device1').first() + self.assertEqual(device.info, 'new info') + + def test_update_device_info_not_found(self): + response = self.client.post('/devices/update', json={'device': 'NonExist', 'info': 'info'}) + self.assertEqual(HTTPStatus.NOT_FOUND, response.status_code) + self.assertEqual(response.get_json(), {'message': 'Device not found'}) + + def test_update_device_info_missing_params(self): + response = self.client.post('/devices/update', json={'device': 'Device1'}) + self.assertEqual(HTTPStatus.BAD_REQUEST, response.status_code) + self.assertEqual(response.get_json(), {'message': 'Missing parameters: info'}) + + def test_update_device_info_empty_params(self): + response = self.client.post('/devices/update', json={'device': '', 'info': ''}) + self.assertEqual(HTTPStatus.BAD_REQUEST, response.status_code) + self.assertEqual(response.get_json(), {'message': 'Empty parameters: device, info'}) + + class TestValidation(unittest.TestCase): def setUp(self): diff --git a/webui/src/App.js b/webui/src/App.js index 81ef318..4e86c01 100644 --- a/webui/src/App.js +++ b/webui/src/App.js @@ -439,6 +439,41 @@ const App = () => { await handleApiCall(`/devices/delete/${deviceName}`, 'delete', null).then(handleSuccess).catch(handleError); }; + const handleUpdateInfo = async (deviceName, info) => { + const handleError = (error) => { + if (error.response) { + switch (error.response.status) { + case 404: + console.warn(`Device ${deviceName} does not exist.`); + showSnackbar(`Device ${deviceName} does not exist.`); + break; + case 400: + console.warn(`Bad request. Missing parameters.`); + break; + default: + console.error('An error occurred:', error); + } + } else { + console.error('An error occurred:', error); + handleHealth(); + } + }; + + const handleSuccess = async (response) => { + switch (response.status) { + case 200: + console.log('Device info updated successfully.'); + break; + default: + console.warn('Unexpected response status:', response.status); + } + await handleList(); + }; + + const payload = {device: deviceName, info: info}; + await handleApiCall(`/devices/update`, 'post', payload).then(handleSuccess).catch(handleError); + }; + const handleGetCLI = async () => { setShowHelp(true); } @@ -459,6 +494,7 @@ const App = () => { handleOnline={handleOnline} handleOffline={handleOffline} handleDelete={handleDelete} + handleUpdateInfo={handleUpdateInfo} /> { return duration.format("d [days] h [hrs] m [min] s [sec]"); }; -const DeviceRow = ({device, handleUsernameChange, deviceUsernames, handleReserve, handleRelease, handleOffline, handleDelete, handleOnline, showMaintenanceMode}) => { +const DeviceRow = ({device, handleUsernameChange, deviceUsernames, handleReserve, handleRelease, handleOffline, handleDelete, handleOnline, handleUpdateInfo, showMaintenanceMode}) => { return ( @@ -60,6 +60,7 @@ const DeviceRow = ({device, handleUsernameChange, deviceUsernames, handleReserve handleOffline={handleOffline} handleDelete={handleDelete} handleOnline={handleOnline} + handleUpdateInfo={handleUpdateInfo} showMaintenanceMode={showMaintenanceMode}/> ) diff --git a/webui/src/DevicesList.jsx b/webui/src/DevicesList.jsx index b450440..20bdb8f 100644 --- a/webui/src/DevicesList.jsx +++ b/webui/src/DevicesList.jsx @@ -2,7 +2,7 @@ import {Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, T import DeviceRow from "./DeviceRow"; import React, {useState} from "react"; -const DevicesList = ({devices, handleUsernameChange, deviceUsernames, handleReserve, handleRelease, handleOffline, handleDelete, handleOnline}) => { +const DevicesList = ({devices, handleUsernameChange, deviceUsernames, handleReserve, handleRelease, handleOffline, handleDelete, handleOnline, handleUpdateInfo}) => { const [showMaintenanceMode, setShowMaintenanceMode] = useState(false); return ( @@ -33,6 +33,7 @@ const DevicesList = ({devices, handleUsernameChange, deviceUsernames, handleRes handleOffline={handleOffline} handleDelete={handleDelete} handleOnline={handleOnline} + handleUpdateInfo={handleUpdateInfo} showMaintenanceMode={showMaintenanceMode} /> ))} diff --git a/webui/src/MaintenanceGroup.jsx b/webui/src/MaintenanceGroup.jsx index 216d6b0..752a820 100644 --- a/webui/src/MaintenanceGroup.jsx +++ b/webui/src/MaintenanceGroup.jsx @@ -1,10 +1,30 @@ -import React from 'react'; -import { Button, Box, TableCell } from '@mui/material'; +import React, {useState} from 'react'; +import { Button, Box, TableCell, TextField } from '@mui/material'; +const MaintenanceGroup = ({device, handleOffline, handleDelete, handleOnline, handleUpdateInfo, showMaintenanceMode}) => { + const [editInfoMode, setEditInfoMode] = useState(false); + const [infoValue, setInfoValue] = useState(device.info || ''); + + const handleEditClick = () => { + if (editInfoMode) { + handleUpdateInfo(device.name, infoValue); + } + setEditInfoMode(!editInfoMode); + } -const MaintenanceGroup = ({device, handleOffline, handleDelete, handleOnline, showMaintenanceMode}) => { return ( + {editInfoMode && ( + setInfoValue(e.target.value)} + /> + )} + {device.status !== "offline" ? (