158 lines
4.2 KiB
Vue
158 lines
4.2 KiB
Vue
<template>
|
|
<div class="table-container">
|
|
<table class="responsive-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Node/Name</th>
|
|
<th>Status</th>
|
|
<th>Latitude</th>
|
|
<th>Longitude</th>
|
|
<th>Battery</th>
|
|
<th>Temperature</th>
|
|
<th>Runtime</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(node, index) in tableData" :key="index">
|
|
<td>{{ node.name }}</td>
|
|
<td>
|
|
<span :class="node.sensorData.voltage ? 'status-online' : 'status-offline'">
|
|
{{ node.sensorData.voltage ? 'ONLINE' : 'OFFLINE' }}
|
|
</span>
|
|
</td>
|
|
<td
|
|
contenteditable="true"
|
|
@blur="validateAndUpdateLatLng(node, 'coordla', $event)"
|
|
>{{ node.coordla }}</td>
|
|
<td
|
|
contenteditable="true"
|
|
@blur="validateAndUpdateLatLng(node, 'coordlong', $event)"
|
|
>{{ node.coordlong }}</td>
|
|
<td>{{ calculateBatteryPercentage(node.sensorData.voltage, node.batteryMinimum, node.batteryMaximum) }}%</td>
|
|
<td>{{ node.sensorData.temperature }}°C</td>
|
|
<td>{{ formatRuntime(node.sensorData.uptime) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import axios from 'axios';
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
tableData: [],
|
|
};
|
|
},
|
|
mounted() {
|
|
this.fetchNodesAndData();
|
|
},
|
|
methods: {
|
|
async fetchNodesAndData() {
|
|
try {
|
|
const nodesResponse = await axios.get('http://localhost:8080/api/v1/nodes');
|
|
const nodes = nodesResponse.data;
|
|
|
|
const sensorDataResponse = await axios.get('http://localhost:8080/api/v1/data');
|
|
const sensorData = sensorDataResponse.data;
|
|
|
|
this.tableData = nodes.map((node) => {
|
|
const nodeSensorData = sensorData.find((data) => data.id === node.id);
|
|
return {
|
|
...node,
|
|
sensorData: nodeSensorData || {
|
|
temperature: 'N/A',
|
|
voltage: 'N/A',
|
|
uptime: 'N/A',
|
|
},
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching node or sensor data:', error);
|
|
}
|
|
},
|
|
calculateBatteryPercentage(voltage, batteryMinimum, batteryMaximum) {
|
|
if (voltage <= batteryMinimum) {
|
|
return 0;
|
|
} else if (voltage >= batteryMaximum) {
|
|
return 100;
|
|
} else {
|
|
return ((voltage - batteryMinimum) / (batteryMaximum - batteryMinimum) * 100).toFixed(2);
|
|
}
|
|
},
|
|
formatRuntime(uptime) {
|
|
const hours = Math.floor(uptime / 3600);
|
|
const minutes = Math.floor((uptime % 3600) / 60);
|
|
const seconds = uptime % 60;
|
|
return `${hours}h ${minutes}m ${seconds}s`;
|
|
},
|
|
validateAndUpdateLatLng(node, field, event) {
|
|
const originalValue = node[field];
|
|
let newValue = event.target.innerText;
|
|
|
|
// Normalize separated values
|
|
newValue = newValue.replace(',', '.');
|
|
|
|
// Check if it's a valid float value
|
|
const validNumberRegex = /^-?\d+(\.\d+)?$/;
|
|
|
|
if (validNumberRegex.test(newValue)) {
|
|
const parsedValue = parseFloat(newValue);
|
|
|
|
// Update if valid
|
|
node[field] = parsedValue;
|
|
console.log(`Updated ${field} of ${node.name}: ${parsedValue}`);
|
|
} else {
|
|
// Reset to original value if invalid
|
|
event.target.innerText = originalValue;
|
|
console.log(`Failed to set ${field} of ${node.name}: Invalid input "${newValue}"`);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Styling remains the same */
|
|
.table-container {
|
|
background-color: white;
|
|
padding: 0.5rem 1rem;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.responsive-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 0;
|
|
table-layout: auto;
|
|
}
|
|
|
|
.responsive-table th,
|
|
.responsive-table td {
|
|
padding: 0.5rem;
|
|
text-align: center;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.responsive-table th {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.responsive-table tr:nth-child(even) {
|
|
background-color: #f9f9f9;
|
|
}
|
|
|
|
.status-online {
|
|
color: green;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.status-offline {
|
|
color: red;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|