618 lines
23 KiB
PHP
618 lines
23 KiB
PHP
<?php include '../src/session_check.php';
|
|
checkUserRole(['admin']);
|
|
?>
|
|
|
|
<html lang="en" data-bs-theme="dark">
|
|
<head>
|
|
<!-- Required meta tags -->
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<!--favicon-->
|
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
|
<!--plugins-->
|
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
|
<!-- loader-->
|
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
|
<script src="../assets/js/pace.min.js"></script>
|
|
<!-- Bootstrap CSS -->
|
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
|
|
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
|
<!-- Theme Style CSS -->
|
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
|
|
|
<title>TOD Dashboard</title>
|
|
</head>
|
|
|
|
<body>
|
|
<!--wrapper-->
|
|
<div class="wrapper">
|
|
<!--sidebar wrapper -->
|
|
<?php include '../src/nav.php'; ?>
|
|
<!--end sidebar wrapper -->
|
|
<!--start header -->
|
|
<?php include '../src/header.php'; ?>
|
|
<!--end header -->
|
|
|
|
<!--start page wrapper -->
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
|
|
<!-- Start Page Content -->
|
|
|
|
<!-- Start Drying Modal -->
|
|
<div class="modal fade" id="startDryingModal" tabindex="-1" aria-labelledby="startDryingModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="startDryingModalLabel">Start Drying</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="dryerControlForm">
|
|
<div class="mb-3">
|
|
<label for="filamentPreset" class="form-label">Filament Preset</label>
|
|
<select class="form-select" id="filamentPreset" name="filamentPreset" required>
|
|
<option value="">Select Filament Type</option>
|
|
<option value="PLA" data-temp="50">PLA</option>
|
|
<option value="ABS" data-temp="70">ABS</option>
|
|
<option value="PETG" data-temp="65">PETG</option>
|
|
<option value="Nylon" data-temp="80">Nylon</option>
|
|
<option value="TPU" data-temp="45">TPU</option>
|
|
<option value="Custom" data-temp="">Custom</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="dryingTime" class="form-label">Drying Time (Hours)</label>
|
|
<input type="number" class="form-control" id="dryingTime" name="dryingTime" min="1" max="24" required>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="temperature" class="form-label">Drying Temperature (°C)</label>
|
|
<input type="number" class="form-control" id="temperature" name="temperature" min="30" max="100" required>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between">
|
|
<button type="submit" class="btn btn-success">Start Drying</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stop Drying Modal -->
|
|
<div class="modal fade" id="stopDryingModal" tabindex="-1" aria-labelledby="stopDryingModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="stopDryingModalLabel">Stop Drying</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to stop the drying process?</p>
|
|
<div class="d-flex justify-content-between">
|
|
<button type="button" class="btn btn-danger" id="confirmStopDrying">Yes, Stop Drying</button>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Page Content -->
|
|
<div class="container mt-4">
|
|
<div class="row">
|
|
<!-- Dryer Status Cards -->
|
|
<div class="col-lg-4">
|
|
<div class="card bg-danger radius-10 overflow-hidden">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div>
|
|
<p class="mb-0">Dryer Status</p>
|
|
<h5 class="mb-0">STATUS</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="card bg-danger radius-10 overflow-hidden" id="heaterRelayStatus">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div>
|
|
<p class="mb-0">Heater Status</p>
|
|
<h5 class="mb-0">STATUS</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="card bg-danger radius-10 overflow-hidden" id="fanRelayStatus">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div>
|
|
<p class="mb-0">Fan Status</p>
|
|
<h5 class="mb-0">STATUS</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Left Column - Charts -->
|
|
<div class="col-lg-7">
|
|
<div class="row">
|
|
<!-- Temperature Chart -->
|
|
<div class="col-lg-12 mb-0">
|
|
<div class="card radius-10 overflow-hidden">
|
|
<div class="card-body d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<p class="mb-0 filament-name">Temp:</p>
|
|
<h5 class="mb-0" id="temperatureValue">--°C</h5>
|
|
</div>
|
|
</div>
|
|
<div id="tempChart"></div>
|
|
</div>
|
|
</div>
|
|
<!-- Humidity Chart -->
|
|
<div class="col-lg-12 mb-0">
|
|
<div class="card radius-10 overflow-hidden">
|
|
<div class="card-body d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<p class="mb-0 filament-name">Humidity:</p>
|
|
<h5 class="mb-0" id="humidityValue">--%</h5>
|
|
</div>
|
|
</div>
|
|
<div id="humidityChart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column - Dryer Control Card with Dropdown and Chart -->
|
|
<div class="col-lg-5">
|
|
<div class="card rounded-4 mb-0">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start justify-content-between mb-0">
|
|
<div class="">
|
|
<h6 class="mb-0">Dryer Control</h6>
|
|
</div>
|
|
<div class="dropdown">
|
|
<a href="javascript:;" class="dropdown-toggle-nocaret more-options dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class='bx bx-dots-vertical-rounded'></i>
|
|
</a>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#startDryingModal">Start Drying</a></li>
|
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#stopDryingModal">Stop Drying</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container2">
|
|
<div id="chartProgress"></div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="">
|
|
<h4 id="dryingTimeDisplay" class="mb-1">Loadig..</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- End Page Content -->
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
<!--end page wrapper -->
|
|
|
|
|
|
<!-- search modal -->
|
|
<div class="modal" id="SearchModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
|
<div class="modal-content">
|
|
<div class="modal-header gap-2">
|
|
<div class="position-relative popup-search w-100">
|
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
|
</div>
|
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end search modal -->
|
|
|
|
<!-- Bootstrap JS -->
|
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
|
<!--plugins-->
|
|
<script src="../assets/js/jquery.min.js"></script>
|
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
|
<!--app JS-->
|
|
<script src="../assets/js/app.js"></script>
|
|
|
|
<script src="../assets/js/index.js"></script>
|
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
|
<script>
|
|
$(".data-attributes span").peity("donut")
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const ESP32_IP = 'https://filamentdry.hickmeister.uk'; // Replace with your ESP32 IP
|
|
let tempChart, humidityChart;
|
|
let progressChart; // Declare a variable for the chart instance
|
|
const startDryingModal = new bootstrap.Modal(document.getElementById('startDryingModal'));
|
|
const stopDryingModal = new bootstrap.Modal(document.getElementById('stopDryingModal'));
|
|
|
|
// Fetch ESP32 Status Data
|
|
function fetchDryerStatus() {
|
|
fetch(`${ESP32_IP}/status`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.dryerOn !== undefined) {
|
|
updateTemperatureCard(data);
|
|
updateHumidityCard(data);
|
|
updateStatusCards(data);
|
|
updateChartProgress(data); // Update chart for drying progress
|
|
} else {
|
|
console.error("Dryer offline or no data available");
|
|
showOfflineStatus();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error("Failed to fetch dryer status:", error);
|
|
showOfflineStatus();
|
|
});
|
|
}
|
|
|
|
// Control Dryer
|
|
function controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes) {
|
|
const formData = new URLSearchParams();
|
|
formData.append("dryerOn", dryerOn);
|
|
formData.append("targetTemperature", targetTemperature);
|
|
formData.append("dryingTime", dryingTimeInMinutes);
|
|
|
|
fetch(`${ESP32_IP}/control`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: formData.toString(),
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
console.log('Dryer control updated:', data);
|
|
fetchDryerStatus(); // Refresh status after control update
|
|
if (dryerOn) {
|
|
startDryingModal.hide();
|
|
} else {
|
|
stopDryingModal.hide();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Failed to update dryer control:', error);
|
|
alert('Failed to update dryer control. Check your connection.');
|
|
});
|
|
}
|
|
|
|
// Handle Form Submission for Starting Drying
|
|
const dryerControlForm = document.getElementById("dryerControlForm");
|
|
dryerControlForm.addEventListener("submit", function (event) {
|
|
event.preventDefault();
|
|
|
|
// Collect form data
|
|
const formData = new FormData(dryerControlForm);
|
|
const dryerOn = true; // Always true when starting the dryer
|
|
const dryingTimeInHours = parseFloat(formData.get("dryingTime"));
|
|
const targetTemperature = parseFloat(formData.get("temperature"));
|
|
|
|
// Validate Input
|
|
if (isNaN(dryingTimeInHours) || isNaN(targetTemperature)) {
|
|
alert("Please complete all required fields.");
|
|
return;
|
|
}
|
|
|
|
// Convert drying time to minutes
|
|
const dryingTimeInMinutes = Math.round(dryingTimeInHours * 60);
|
|
|
|
// Control Dryer
|
|
controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes);
|
|
});
|
|
|
|
// Handle Stop Dryer
|
|
document.getElementById("stopDryingModal").addEventListener("click", function () {
|
|
stopDryingModal.show();
|
|
});
|
|
|
|
document.getElementById("confirmStopDrying").addEventListener("click", function() {
|
|
controlDryer(false, 0, 0); // Turn off the dryer
|
|
});
|
|
|
|
// Auto-Fill Temperature Input Based on Filament Preset
|
|
const filamentPresetSelect = document.getElementById("filamentPreset");
|
|
const temperatureInput = document.getElementById("temperature");
|
|
|
|
filamentPresetSelect.addEventListener("change", function () {
|
|
const selectedOption = filamentPresetSelect.options[filamentPresetSelect.selectedIndex];
|
|
const presetTemperature = selectedOption.getAttribute("data-temp");
|
|
|
|
if (presetTemperature) {
|
|
temperatureInput.value = presetTemperature; // Auto-fill temperature input
|
|
} else {
|
|
temperatureInput.value = ""; // Clear the temperature input for "Custom"
|
|
}
|
|
});
|
|
|
|
// Update chart progress
|
|
function updateChartProgress(data) {
|
|
const chartProgressElement = document.getElementById("chartProgress");
|
|
|
|
const dryingTime = data.dryingTime || 0; // Total drying time in minutes
|
|
const remainingTime = data.remainingTime || 0; // Remaining time in minutes
|
|
|
|
// Convert remaining time and total drying time to h:mm format
|
|
const remainingTimeFormatted = convertToHoursAndMinutes(remainingTime);
|
|
const dryingTimeFormatted = convertToHoursAndMinutes(dryingTime);
|
|
|
|
const progressPercent = dryingTime > 0 ? ((dryingTime - remainingTime) / dryingTime) * 100 : 0; // Calculate the percentage of drying progress
|
|
|
|
// Initialize the chart if it doesn't exist yet
|
|
if (!progressChart) {
|
|
const options = {
|
|
series: [progressPercent],
|
|
chart: {
|
|
height: 385,
|
|
type: 'radialBar',
|
|
toolbar: {
|
|
show: false
|
|
}
|
|
},
|
|
plotOptions: {
|
|
radialBar: {
|
|
hollow: {
|
|
margin: 0,
|
|
size: '80%',
|
|
background: 'transparent',
|
|
},
|
|
track: {
|
|
background: 'rgba(0, 0, 0, 0.1)',
|
|
strokeWidth: '67%',
|
|
},
|
|
dataLabels: {
|
|
show: true,
|
|
name: {
|
|
offsetY: -10,
|
|
show: false,
|
|
},
|
|
value: {
|
|
offsetY: 10,
|
|
color: 'rgba(13, 160, 8, 0.82)',
|
|
fontSize: '20px',
|
|
show: true,
|
|
formatter: function () {
|
|
return remainingTimeFormatted; // Display remaining time in the center
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
fill: {
|
|
type: 'gradient',
|
|
gradient: {
|
|
shade: 'dark',
|
|
type: 'horizontal',
|
|
shadeIntensity: 02,
|
|
gradientToColors: ['#44FF00'],
|
|
opacityFrom: 1,
|
|
opacityTo: 1,
|
|
stops: [0, 100]
|
|
}
|
|
},
|
|
colors: ["#289600"],
|
|
stroke: {
|
|
lineCap: 'round'
|
|
},
|
|
labels: ['Drying Progress'],
|
|
};
|
|
|
|
progressChart = new ApexCharts(chartProgressElement, options);
|
|
progressChart.render();
|
|
} else {
|
|
progressChart.updateSeries([progressPercent]);
|
|
|
|
// Dynamically update the remaining time in the center
|
|
progressChart.updateOptions({
|
|
plotOptions: {
|
|
radialBar: {
|
|
dataLabels: {
|
|
value: {
|
|
formatter: function () {
|
|
return remainingTimeFormatted; // Dynamically update the remaining time
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Display drying time below the chart
|
|
const timeSetElement = document.getElementById('dryingTimeDisplay');
|
|
if (timeSetElement) {
|
|
timeSetElement.innerText = `Time Set: ${dryingTimeFormatted}`;
|
|
}
|
|
}
|
|
|
|
// Convert minutes to hours and minutes (e.g., 90 minutes -> 1h 30m)
|
|
function convertToHoursAndMinutes(minutes) {
|
|
const hours = Math.floor(minutes / 60);
|
|
const mins = minutes % 60;
|
|
return `${hours}h ${mins}m`;
|
|
}
|
|
|
|
// Update Temperature Card and Chart
|
|
function updateTemperatureCard(data) {
|
|
const temperature = data.temperature.toFixed(1); // Limit temperature to 1 decimal place
|
|
const tempHistory = data.tempHistory.map(temp => parseFloat(temp.toFixed(1))); // Limit history data
|
|
const timestamps = Array.from({ length: tempHistory.length }, (_, i) => `${i + 1}`);
|
|
|
|
document.getElementById('temperatureValue').innerText = `${temperature}°C`;
|
|
|
|
const options = {
|
|
series: [{ name: "Temperature", data: tempHistory }],
|
|
chart: {
|
|
type: 'area',
|
|
color: '#1df00a',
|
|
height: 150,
|
|
toolbar: { show: false },
|
|
sparkline: { enabled: true },
|
|
id: 'tempChart',
|
|
},
|
|
stroke: { curve: 'smooth' },
|
|
xaxis: { categories: timestamps, labels: { show: false } },
|
|
tooltip: {
|
|
theme: 'dark',
|
|
x: { show: false },
|
|
y: { formatter: value => `${value.toFixed(1)}°C` },
|
|
},
|
|
};
|
|
|
|
if (!tempChart) {
|
|
tempChart = new ApexCharts(document.querySelector("#tempChart"), options);
|
|
tempChart.render();
|
|
} else {
|
|
tempChart.updateSeries([{ data: tempHistory }]);
|
|
}
|
|
}
|
|
|
|
// Update Humidity Card and Chart
|
|
function updateHumidityCard(data) {
|
|
const humidity = data.humidity.toFixed(1);
|
|
const humidityHistory = data.humidityHistory || [];
|
|
const timestamps = Array.from({ length: humidityHistory.length }, (_, i) => `${i + 1}`);
|
|
|
|
document.getElementById('humidityValue').innerText = `${humidity}%`;
|
|
|
|
const options = {
|
|
series: [{ name: "Humidity", data: humidityHistory }],
|
|
chart: {
|
|
type: 'area',
|
|
height: 150,
|
|
toolbar: { show: false },
|
|
sparkline: { enabled: true },
|
|
id: 'humidityChart',
|
|
},
|
|
stroke: { curve: 'smooth' },
|
|
xaxis: { categories: timestamps, labels: { show: false } },
|
|
tooltip: {
|
|
theme: 'dark',
|
|
x: { show: false },
|
|
y: { formatter: value => `${value.toFixed(1)}%` },
|
|
},
|
|
};
|
|
|
|
if (!humidityChart) {
|
|
humidityChart = new ApexCharts(document.querySelector("#humidityChart"), options);
|
|
humidityChart.render();
|
|
} else {
|
|
humidityChart.updateSeries([{ data: humidityHistory }]);
|
|
}
|
|
}
|
|
|
|
// Update Status Cards (Dryer, Heater, Fan)
|
|
function updateStatusCards(data) {
|
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
|
const fanStatusCard = document.getElementById('fanRelayStatus');
|
|
|
|
const dryerState = data.dryerOn ? "On" : "Off";
|
|
const fanRelayState = data.fanRelayState ? "On" : "Off";
|
|
const heaterRelayState = data.heaterRelayState ? "On" : "Off";
|
|
|
|
if (dryerStatusCard) {
|
|
dryerStatusCard.classList.toggle("bg-success", dryerState === "On");
|
|
dryerStatusCard.classList.toggle("bg-danger", dryerState === "Off");
|
|
dryerStatusCard.querySelector("h5").innerText = dryerState;
|
|
}
|
|
|
|
if (heaterStatusCard) {
|
|
heaterStatusCard.classList.toggle("bg-success", heaterRelayState === "On");
|
|
heaterStatusCard.classList.toggle("bg-danger", heaterRelayState === "Off");
|
|
heaterStatusCard.querySelector("h5").innerText = heaterRelayState;
|
|
}
|
|
|
|
if (fanStatusCard) {
|
|
fanStatusCard.classList.toggle("bg-success", fanRelayState === "On");
|
|
fanStatusCard.classList.toggle("bg-danger", fanRelayState === "Off");
|
|
fanStatusCard.querySelector("h5").innerText = fanRelayState;
|
|
}
|
|
}
|
|
|
|
// Show Offline Status
|
|
function showOfflineStatus() {
|
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
|
const fanStatusCard = document.getElementById('fanRelayStatus');
|
|
|
|
if (dryerStatusCard) {
|
|
dryerStatusCard.classList.remove("bg-success");
|
|
dryerStatusCard.classList.add("bg-danger");
|
|
dryerStatusCard.querySelector("h5").innerText = "Offline";
|
|
}
|
|
|
|
if (heaterStatusCard) {
|
|
heaterStatusCard.classList.remove("bg-success");
|
|
heaterStatusCard.classList.add("bg-danger");
|
|
heaterStatusCard.querySelector("h5").innerText = "Offline";
|
|
}
|
|
|
|
if (fanStatusCard) {
|
|
fanStatusCard.classList.remove("bg-success");
|
|
fanStatusCard.classList.add("bg-danger");
|
|
fanStatusCard.querySelector("h5").innerText = "Offline";
|
|
}
|
|
}
|
|
|
|
// Fetch data every 10 seconds
|
|
fetchDryerStatus();
|
|
setInterval(fetchDryerStatus, 10000);
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|