Skip to content

Commit 035bb4a

Browse files
committed
Version 1.10.0: Add kiosk mode features and fix UPS card updates
1 parent 33ae3ec commit 035bb4a

File tree

8 files changed

+200
-7
lines changed

8 files changed

+200
-7
lines changed

API.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,44 @@ Endpoints for managing UPS systems.
806806
GET /api/ups-systems
807807
```
808808

809-
Returns a list of all UPS systems with their current status.
809+
Returns a list of all UPS systems with their current status. Requires authentication.
810+
811+
**Response:**
812+
```json
813+
[
814+
{
815+
"id": 1,
816+
"name": "ups1",
817+
"nickname": "Server Room UPS",
818+
"displayName": "Server Room UPS",
819+
"upsName": "ups1",
820+
"nutServerId": 1,
821+
"model": "APC Smart-UPS 1500",
822+
"status": "Online",
823+
"batteryCharge": 100,
824+
"batteryVoltage": 27.3,
825+
"inputVoltage": 230.1,
826+
"outputVoltage": 230.0,
827+
"runtimeRemaining": 120,
828+
"load": 30,
829+
"temperature": 25.5,
830+
"batteryDetails": { ... },
831+
"deviceDetails": { ... },
832+
"driverDetails": { ... },
833+
"inputDetails": { ... },
834+
"outputDetails": { ... },
835+
"upsDetails": { ... }
836+
}
837+
]
838+
```
839+
840+
### Get UPS Systems for Kiosk Mode
841+
842+
```
843+
GET /api/ups-systems/kiosk
844+
```
845+
846+
Returns a list of all UPS systems with their current status for kiosk mode. This endpoint does not require authentication and is specifically designed for public displays.
810847

811848
**Response:**
812849
```json

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Added toggle button to switch between single and multi-view modes
2020
- Created compact UPS cards for multi-view display
2121
- Added URL parameters to control view mode and UPS selection
22+
- Disabled inactivity timer for kiosk mode to prevent automatic logout
23+
- Created dedicated API endpoint for kiosk mode that doesn't require authentication
2224

2325
## [1.9.1] - 2025-03-02
2426

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# PowerPulse
22

33
![PowerPulse Logo](https://img.shields.io/badge/PowerPulse-UPS%20Monitoring-blue)
4-
![Version](https://img.shields.io/badge/version-1.9.1-green)
4+
![Version](https://img.shields.io/badge/version-1.10.0-green)
55
![License](https://img.shields.io/badge/license-MIT-lightgrey)
66

77
PowerPulse is a modern UPS (Uninterruptible Power Supply) monitoring dashboard integrated with Network UPS Tools (NUT). It provides a clean, responsive interface for monitoring and managing your UPS systems.
@@ -21,6 +21,7 @@ PowerPulse is a modern UPS (Uninterruptible Power Supply) monitoring dashboard i
2121
## Features
2222

2323
- **UPS Monitoring Dashboard**: View all your UPS systems in a clean, modern interface
24+
- **Kiosk Mode**: Dedicated full-screen view for public displays without authentication.
2425
- **UPS System Nicknaming**: Assign custom nicknames to your UPS systems for easier identification
2526
- **Detailed UPS Information**: Access comprehensive details about each UPS system
2627
- **Battery History Tracking**: Monitor battery performance over time

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "powerpulse-client",
33
"private": true,
4-
"version": "1.9.1",
4+
"version": "1.10.0",
55
"author": "blink-zero",
66
"repository": {
77
"type": "git",

client/src/pages/KioskMode.jsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ import { FiBattery, FiAlertCircle, FiClock, FiZap, FiActivity, FiThermometer, Fi
44
import { useSettings } from '../context/SettingsContext';
55
import { useLocation, useNavigate } from 'react-router-dom';
66

7+
// Disable inactivity timer for kiosk mode
8+
const disableInactivityTimer = () => {
9+
// Clear any existing inactivity timer
10+
const existingTimer = localStorage.getItem('lastActivityTimestamp');
11+
if (existingTimer) {
12+
// Set the timestamp to the current time and keep updating it
13+
const updateTimestamp = () => {
14+
localStorage.setItem('lastActivityTimestamp', Date.now().toString());
15+
};
16+
17+
// Update immediately and then every minute
18+
updateTimestamp();
19+
const intervalId = setInterval(updateTimestamp, 60000);
20+
21+
// Return cleanup function
22+
return () => clearInterval(intervalId);
23+
}
24+
25+
return () => {}; // No cleanup needed if no timer exists
26+
};
27+
728
// Dashboard reducer for state management
829
const initialState = {
930
upsSystems: [],
@@ -98,14 +119,15 @@ const KioskMode = () => {
98119
const fetchUpsSystems = async () => {
99120
try {
100121
dispatch({ type: 'SET_LOADING', payload: true });
101-
const response = await axios.get('/api/ups/systems');
122+
// Use the non-authenticated kiosk endpoint
123+
const response = await axios.get('/api/ups/systems/kiosk');
102124
dispatch({ type: 'SET_UPS_SYSTEMS', payload: response.data });
103125
} catch (err) {
104126
dispatch({
105127
type: 'SET_ERROR',
106128
payload: 'Failed to fetch UPS systems. Please check your connection to the NUT server.'
107129
});
108-
console.error('Error fetching UPS systems:', err);
130+
console.error('Error fetching UPS systems for kiosk mode:', err);
109131
} finally {
110132
dispatch({ type: 'SET_LOADING', payload: false });
111133
}
@@ -119,6 +141,13 @@ const KioskMode = () => {
119141
return () => clearInterval(dataInterval);
120142
}, [getPollIntervalMs]);
121143

144+
// Disable inactivity timer for kiosk mode
145+
useEffect(() => {
146+
// This will keep updating the lastActivityTimestamp to prevent auto-logout
147+
const cleanup = disableInactivityTimer();
148+
return cleanup;
149+
}, []);
150+
122151
// Auto-rotate between UPS systems every 10 seconds if there are multiple and not in multi-view mode
123152
useEffect(() => {
124153
if (upsSystems.length <= 1 || multiView) return;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "powerpulse",
3-
"version": "1.9.1",
3+
"version": "1.10.0",
44
"description": "Modern UPS monitoring dashboard integrated with Network UPS Tools (NUT)",
55
"main": "index.js",
66
"scripts": {

server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "powerpulse-server",
3-
"version": "1.9.1",
3+
"version": "1.10.0",
44
"description": "Backend server for PowerPulse - A modern UPS monitoring dashboard",
55
"author": "blink-zero",
66
"repository": {

server/routes/upsSystems.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,130 @@ const { getNutUpsData } = require('../utils/nutClient');
55

66
const router = express.Router();
77

8+
// Get all UPS systems for kiosk mode (no authentication required)
9+
router.get('/kiosk', async (req, res) => {
10+
try {
11+
// First check if we have any UPS systems in the database
12+
db.all('SELECT * FROM ups_systems', async (err, dbSystems) => {
13+
if (err) {
14+
console.error('Database error:', err.message);
15+
return res.status(500).json({ message: 'Database error', error: err.message });
16+
}
17+
18+
// Get all NUT servers from database
19+
db.all('SELECT id, host, port, username, password FROM nut_servers', async (err, servers) => {
20+
if (err) {
21+
console.error('Database error:', err.message);
22+
return res.status(500).json({ message: 'Database error', error: err.message });
23+
}
24+
25+
if (servers.length === 0) {
26+
return res.status(404).json({ message: 'No NUT servers configured' });
27+
}
28+
29+
try {
30+
// Get UPS data from all NUT servers
31+
const allUpsData = [];
32+
33+
// Process each server
34+
for (const server of servers) {
35+
try {
36+
const nutHost = server.host;
37+
const nutPort = server.port || 3493;
38+
const nutUsername = server.username;
39+
const nutPassword = server.password;
40+
41+
console.log(`Connecting to NUT server at ${nutHost}:${nutPort} for kiosk mode`);
42+
43+
const upsData = await getNutUpsData(nutHost, nutPort, nutUsername, nutPassword);
44+
45+
// Map UPS data to include server ID
46+
const mappedUpsData = upsData.map(ups => ({
47+
...ups,
48+
nutServerId: server.id
49+
}));
50+
51+
allUpsData.push(...mappedUpsData);
52+
} catch (serverErr) {
53+
console.error(`Error connecting to NUT server ${server.host}:${server.port}:`, serverErr);
54+
// Continue with other servers even if one fails
55+
}
56+
}
57+
58+
// If we have database systems, combine with live data
59+
if (dbSystems.length > 0) {
60+
const combinedData = dbSystems.map(dbSystem => {
61+
// Find matching UPS in live data
62+
const liveUps = allUpsData.find(ups =>
63+
ups.name === dbSystem.ups_name &&
64+
ups.nutServerId === dbSystem.nut_server_id
65+
);
66+
67+
if (liveUps) {
68+
// Combine database and live data
69+
return {
70+
id: dbSystem.id,
71+
name: dbSystem.name,
72+
nickname: dbSystem.nickname,
73+
displayName: dbSystem.nickname || liveUps.displayName || dbSystem.name,
74+
upsName: dbSystem.ups_name,
75+
nutServerId: dbSystem.nut_server_id,
76+
model: liveUps.model,
77+
status: liveUps.status,
78+
batteryCharge: liveUps.batteryCharge,
79+
batteryVoltage: liveUps.batteryVoltage,
80+
inputVoltage: liveUps.inputVoltage,
81+
outputVoltage: liveUps.outputVoltage,
82+
runtimeRemaining: liveUps.runtimeRemaining,
83+
load: liveUps.load,
84+
temperature: liveUps.temperature,
85+
// Include extended details
86+
batteryDetails: liveUps.batteryDetails,
87+
deviceDetails: liveUps.deviceDetails,
88+
driverDetails: liveUps.driverDetails,
89+
inputDetails: liveUps.inputDetails,
90+
outputDetails: liveUps.outputDetails,
91+
upsDetails: liveUps.upsDetails,
92+
rawVariables: liveUps.rawVariables
93+
};
94+
} else {
95+
// Return basic info if live data not available
96+
return {
97+
id: dbSystem.id,
98+
name: dbSystem.name,
99+
nickname: dbSystem.nickname,
100+
displayName: dbSystem.nickname || dbSystem.name,
101+
upsName: dbSystem.ups_name,
102+
nutServerId: dbSystem.nut_server_id,
103+
status: 'Unknown',
104+
batteryCharge: null,
105+
runtimeRemaining: null,
106+
load: null
107+
};
108+
}
109+
});
110+
111+
return res.json(combinedData);
112+
} else if (allUpsData.length > 0) {
113+
// If no database systems but we have live data, return that
114+
return res.json(allUpsData);
115+
} else {
116+
// If no UPS systems are found, return empty array
117+
console.log('No UPS systems found for kiosk mode');
118+
return res.json([]);
119+
}
120+
} catch (nutErr) {
121+
console.error('NUT server error:', nutErr);
122+
return res.status(500).json({ message: 'Error connecting to NUT servers', error: nutErr.message });
123+
}
124+
});
125+
});
126+
} catch (error) {
127+
console.error('Error in /api/ups/systems/kiosk:', error);
128+
res.status(500).json({ message: 'Server error', error: error.message });
129+
}
130+
});
131+
8132
// Get all UPS systems from database
9133
router.get('/', authenticateToken, async (req, res) => {
10134
try {

0 commit comments

Comments
 (0)