diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..ad8711f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+ caddy:
+ image: caddy:2
+ container_name: lambdaiot-web
+ ports:
+ - "18080:80"
+ volumes:
+ - ./webroot:/srv:ro
+ command: ["caddy", "file-server", "--root", "/srv", "--listen", ":80"]
+ restart: unless-stopped
diff --git a/app.js b/webroot/app.js
similarity index 82%
rename from app.js
rename to webroot/app.js
index b16075f..7b3c533 100644
--- a/app.js
+++ b/webroot/app.js
@@ -8,12 +8,15 @@ const state = {
actors: [],
selectedDeviceId: null,
readingsLimit: 50,
+ deviceRefreshIntervalId: null,
};
const elements = {
authStatus: document.getElementById("authStatus"),
authMessage: document.getElementById("authMessage"),
loginForm: document.getElementById("loginForm"),
+ authBlock: document.getElementById("authBlock"),
+ authActions: document.getElementById("authActions"),
logoutButton: document.getElementById("logoutButton"),
deviceList: document.getElementById("deviceList"),
deviceMeta: document.getElementById("deviceMeta"),
@@ -39,13 +42,32 @@ function setAuthMessage(message, isError = false) {
elements.authMessage.style.color = isError ? "#b0412e" : "var(--muted)";
}
+function setDeviceRefreshInterval(enabled) {
+ if (state.deviceRefreshIntervalId) {
+ clearInterval(state.deviceRefreshIntervalId);
+ state.deviceRefreshIntervalId = null;
+ }
+
+ if (enabled) {
+ state.deviceRefreshIntervalId = setInterval(loadDevices, 15000);
+ }
+}
+
function updateAuthUI() {
if (state.token) {
setAuthStatus("Signed in");
setAuthMessage("Token stored locally.");
+ elements.authBlock.classList.add("is-hidden");
+ elements.loginForm.classList.add("is-hidden");
+ elements.authActions.classList.remove("is-hidden");
+ setDeviceRefreshInterval(false);
} else {
setAuthStatus("Not signed in");
setAuthMessage("Login required for sensors, actors, and readings.");
+ elements.authBlock.classList.remove("is-hidden");
+ elements.loginForm.classList.remove("is-hidden");
+ elements.authActions.classList.add("is-hidden");
+ setDeviceRefreshInterval(true);
}
}
@@ -80,13 +102,38 @@ async function apiFetch(path, options = {}, authRequired = true) {
async function loadDevices() {
elements.deviceList.innerHTML = "";
- state.devices = [];
try {
const data = await apiFetch("/devices", {}, false);
state.devices = Array.isArray(data) ? data : [];
renderDeviceList();
} catch (error) {
- elements.deviceList.innerHTML = `
Failed to load devices: ${error.message}`;
+ const errorItem = document.createElement("li");
+ errorItem.className = "device-error";
+ errorItem.textContent = `Failed to load devices: ${error.message}`;
+ elements.deviceList.prepend(errorItem);
+ }
+}
+
+async function refreshAll() {
+ await loadDevices();
+
+ if (!state.token || !state.selectedDeviceId) {
+ return;
+ }
+
+ const selectedDevice = state.devices.find(
+ (device) => device.id === state.selectedDeviceId
+ );
+
+ if (selectedDevice) {
+ selectDevice(selectedDevice);
+ } else {
+ state.selectedDeviceId = null;
+ elements.deviceMeta.textContent = "Select a device";
+ elements.sensorList.innerHTML = "";
+ elements.actorList.innerHTML = "";
+ elements.sensorNote.textContent = "No device selected.";
+ elements.actorNote.textContent = "No device selected.";
}
}
@@ -99,9 +146,20 @@ function renderDeviceList() {
state.devices.forEach((device) => {
const item = document.createElement("li");
+ const statusLabel =
+ device.status_id === 2 ? "Pending" : device.status_id === 3 ? "Lost" : "";
+ const statusClass =
+ device.status_id === 2 ? "pending" : device.status_id === 3 ? "lost" : "";
+ const statusMarkup = statusLabel
+ ? `${statusLabel}`
+ : "";
+
item.classList.toggle("active", device.id === state.selectedDeviceId);
item.innerHTML = `
- ${device.name}
+
${device.location || "No location"}
`;
item.addEventListener("click", () => selectDevice(device));
@@ -320,6 +378,7 @@ async function handleLogin(event) {
localStorage.setItem("lambdaiot.token", state.token);
updateAuthUI();
setAuthMessage("Login successful.");
+ refreshAll();
if (state.selectedDeviceId) {
loadDeviceDetails(state.selectedDeviceId);
@@ -334,6 +393,7 @@ function handleLogout() {
localStorage.removeItem("lambdaiot.token");
updateAuthUI();
setAuthMessage("Logged out.");
+ refreshAll();
elements.sensorList.innerHTML = "";
elements.actorList.innerHTML = "";
elements.sensorNote.textContent = "Login required to view sensors.";
@@ -342,7 +402,7 @@ function handleLogout() {
elements.loginForm.addEventListener("submit", handleLogin);
elements.logoutButton.addEventListener("click", handleLogout);
-elements.refreshDevices.addEventListener("click", loadDevices);
+elements.refreshDevices.addEventListener("click", refreshAll);
updateAuthUI();
-loadDevices();
+refreshAll();
diff --git a/index.html b/webroot/index.html
similarity index 95%
rename from index.html
rename to webroot/index.html
index 29c1115..443f167 100644
--- a/index.html
+++ b/webroot/index.html
@@ -23,7 +23,7 @@