Skip to content

Commit

Permalink
Add water level settings view and update API schema #1
Browse files Browse the repository at this point in the history
  • Loading branch information
hcwinsemius committed Jan 15, 2025
1 parent e63475e commit ec3be2f
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 42 deletions.
31 changes: 2 additions & 29 deletions dashboard/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Footer from './nav/Footer';
import Home from './views/home';
import Device from './views/device';
import DiskManagement from './views/diskManagement'
import WaterLevel from './views/waterLevel'
import CameraAim from './views/cameraAim'
import api from './api'

Expand All @@ -24,6 +25,7 @@ const App = () => {
<Route path="/" element={<Home />} />
<Route path="/device" element={<Device />} />
<Route path="/disk_management" element={<DiskManagement />} />
<Route path="/water_level" element={<WaterLevel />} />
<Route path="/camera_aim" element={<CameraAim />} />
</Routes>
</div>
Expand All @@ -32,34 +34,5 @@ const App = () => {
</Router>
)
}
//
// function App() {
// const [count, setCount] = useState(0)
//
// return (
// <>
// <div>
// <a href="https://vite.dev" target="_blank">
// <img src={orcLogo} className="logo" alt="Vite logo" />
// </a>
// <a href="https://react.dev" target="_blank">
// <img src={reactLogo} className="logo react" alt="React logo" />
// </a>
// </div>
// <h1>NodeORC configuration</h1>
// <div className="card">
// <button onClick={() => setCount((count) => count + 1)}>
// count is {count}
// </button>
// <p>
// Edit <code>src/App.jsx</code> and save to test HMR
// </p>
// </div>
// <p className="read-the-docs">
// Click on the Vite and React logos to learn more
// </p>
// </>
// )
// }

export default App
11 changes: 9 additions & 2 deletions dashboard/src/nav/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,21 @@ const Navbar = () => {
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/disk_management" onClick={handleClose}>
Disk management
Disk management settings
</NavLink>
</li>
<li className="nav-item">
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/water_level" onClick={handleClose}>
Water level settings
</NavLink>
</li>
<li className="nav-item">
<NavLink
className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}
to="/camera_aim" onClick={handleClose}>
Camera aiming
Aim your camera in the field
</NavLink>
</li>
</ul>
Expand Down
1 change: 0 additions & 1 deletion dashboard/src/views/device.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ const Device = () => {
});
}
const handleInputIntChange = (event) => {
console.log(event.target);
const { name, value, type } = event.target;
event.target.value = value;
setFormData({
Expand Down
2 changes: 0 additions & 2 deletions dashboard/src/views/diskManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import Message from './message'
const DiskManagement = () => {

const [diskManagement, updateDiskManagement] = useState([]);
const [loading, setLoading] = useState(true); // State for loading indicator
const [message, setMessage] = useState(null); // State for message handling
const [messageType, setMessageType] = useState(null); // State for message type
const [folderName, setFolderName] = useState("");
const [formData, setFormData] = useState({
created_at: '',
home_folder: '',
Expand Down
157 changes: 157 additions & 0 deletions dashboard/src/views/waterLevel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, {useState, useEffect} from 'react';
import api from '../api';
import Message from './message'

const WaterLevel = () => {
const [scriptTypeStatus, setScriptTypeStatus] = useState([]);
const [waterLevel, updateWaterLevel] = useState([]);
const [message, setMessage] = useState(null); // State for message handling
const [messageType, setMessageType] = useState(null); // State for message type
const [formData, setFormData] = useState({
created_at: '',
datetime_fmt: '',
file_template: '',
frequency: '',
script_type: '',
script: ''
});

const fetchWaterLevel = async () => {
const response = await api.get('/water_level/');
updateWaterLevel(response.data);
};
useEffect(() => {
fetchWaterLevel();
}, []);
useEffect(() => {
if (waterLevel) {
if (waterLevel.created_at) {
waterLevel.created_at = waterLevel.created_at.slice(0, 19);
}
setFormData({
created_at: waterLevel.created_at || '',
datetime_fmt: waterLevel.datetime_fmt || '',
file_template: waterLevel.file_template || '',
frequency: waterLevel.frequency || '',
script_type: waterLevel.script_type || '',
script: waterLevel.script || ''
});
}
}, [waterLevel]);
const handleInputChange = (event) => {
const { name, value, type } = event.target;
event.target.value = value;
setFormData({
...formData,
[name]: type === "number" ? parseFloat(value) : value
});
};
const handleInputIntChange = (event) => {
console.log(event.target);
const { name, value, type } = event.target;
event.target.value = value;
setFormData({
...formData,
[name]: type === "number" | type === "select-one" ? parseInt(value) : value
});
};
const handleScriptTypeChange = (e) => {
setScriptTypeStatus(e.target.value); // Update selected status in state
handleInputIntChange(e); // Pass the change event to the parent handler
};

const handleFormSubmit = async (event) => {
event.preventDefault();
console.log(formData);
// get rid of created_at field as this must be autocompleted
try {
delete formData.created_at;
const response = await api.post('/water_level/', formData);
if (response.status === 500) {
const errorData = await response.json()
throw new Error(errorData.message || `Invalid form data. Status Code: ${response.status}`);
}
setMessage("Water level settings updated successfully!");
setMessageType("success");
// read back the device after posting
fetchWaterLevel();
// set the form data to new device settings
setFormData({
created_at: '',
datetime_fmt: '',
file_template: '',
frequency: '',
script_type: '',
script: ''
});
} catch (err) {
setMessage(err.response.data);
setMessageType("error")
}
};
return (
<div className='container'>
Change your water level settings. You can let NodeORC read and store water levels automatically using
a user-defined script or as fall-back, read water levels from a file or files with a datetime template.
<Message
message={message}
messageType={messageType}
clearMessage={() => {
setMessage("");
setMessageType("");
}}
/>

<hr/>
<form onSubmit={handleFormSubmit}>
<div className='mb-3 mt-3'>
<label htmlFor='created_at' className='form-label'>
Date of creation
</label>
<input type='datetime-local' className='form-control' id='created_at' name='created_at' onChange={handleInputChange} value={formData.created_at} disabled/>
</div>
<div className='mb-3 mt-3'>
<label htmlFor='datetime_fmt' className='form-label'>
Date/time format used in backup files (if you have any)
</label>
<input type='str' className='form-control' id='datetime_fmt' name='datetime_fmt' placeholder="%Y-%m-%dT%H:%M:%SZ" onChange={handleInputChange} value={formData.datetime_fmt} />
</div>
<div className='mb-3 mt-3'>
<label htmlFor='file_template' className='form-label'>
File template format for water level files (e.g. water_level_&#123;%Y%m%d&#125;.csv). These files will we
expected in &lt;home folder&gt;/water_level.
</label>
<input type='str' className='form-control' id='file_template' name='file_template' placeholder="water_level_&#123;%Y%m%d&#125;.csv" onChange={handleInputChange} value={formData.file_template} />
</div>
<div className='mb-3 mt-3'>
<label htmlFor='frequency' className='form-label'>
Frequency [s] for checking for new water level using the script.
</label>
<input type='number' className='form-control' id='frequency' name='frequency' step="1" onChange={handleInputChange} value={formData.frequency} />
</div>
<div className='mb-3 mt-3'>
<label htmlFor='script_type' className='form-label'>
Script type of the provided script.
</label>
<select name="script_type" id="script_type" className="form-select" onChange={handleScriptTypeChange} value={scriptTypeStatus}>
<option value="0">PYTHON</option>
<option value="1">BASH</option>
</select>
</div>
<div className='mb-3 mt-3'>
<label htmlFor='script' className='form-label'>
Script content. The script should produce a last line with the water level in the following
form: &lt;%Y-%m-%dT%H:%M%SZ&gt;, &lt;value&gt;
</label>
{/* <input type='text' className='form-control' id='script' name='script' placeholder="#!/bin/bash&#10;...write your script" onChange={handleInputChange} value={formData.script} style={{ height: '300px' }}/> */}
<textarea className='form-control' id='script' name='script' placeholder="#!/bin/bash&#10;...write your script" onChange={handleInputChange} value={formData.script} style={{ height: '300px' }}/>
</div>
<button type='submit' className='btn btn-primary'>
Submit
</button>
</form>
</div>

);
};
export default WaterLevel;
2 changes: 1 addition & 1 deletion nodeorc_api/schemas/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DeviceResponse(DeviceBase):
id: uuid.UUID# = Field(description="Device ID")

class Config:
orm_mode = True
from_attributes = True

class DeviceCreate(DeviceBase):
pass
Expand Down
16 changes: 9 additions & 7 deletions nodeorc_api/schemas/water_level.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
from datetime import datetime
from enum import Enum
from pydantic import BaseModel, Field
from typing import Optional
from typing import Optional, Literal

from nodeorc.db import ScriptType

# Pydantic model for responses
class WaterLevelBase(BaseModel):
datetime_fmt: Optional[str] = Field(default=None, description="Datestring format used in files containing water level data.")
file_template: Optional[str] = Field(default=None, description="File name template for water level data files. May contain {%Y%m%d} to accomodate several files for different dates.")
frequency: Optional[float] = Field(default=None, description="Frequency [s] for checking for new water levels using the script.")
script_type: Optional[float] = Field(default=None, description="Script type provided for checking water levels. Can be 'python' or 'bash'.")
script: Optional[str] = Field(default=None, description="Content of the script to be executed to retrieve water levels. Script must print a water level value to screen (stdout) in the form '%Y-%m-%dT%H:%M:%SZ, <value>'")
datetime_fmt: str = Field(description="Datestring format used in files containing water level data.")
file_template: str = Field(description="File name template for water level data files. May contain {%Y%m%d} to accomodate several files for different dates.")
frequency: float = Field(description="Frequency [s] for checking for new water levels using the script.")
script_type: ScriptType = Field(description="Script type provided for checking water levels. Can be 'python' or 'bash'.")
script: str = Field(description="Content of the script to be executed to retrieve water levels. Script must print a water level value to screen (stdout) in the form '%Y-%m-%dT%H:%M:%SZ, <value>'")


class WaterLevelResponse(WaterLevelBase):
id: int = Field(description="Water level settings ID")
created_at: datetime = Field(description="Creation date")

class Config:
orm_mode = True
from_attributes = True


class WaterLevelCreate(WaterLevelBase):
Expand Down

0 comments on commit ec3be2f

Please sign in to comment.