Add v9.1.1: Device registry and remote blocking system
- Implement DeviceRegistry for remote device management - Add dashboard for device tracking and blocking - Remote device blocking capability with admin control - Support for device aliasing and notes - Enhanced device management interface - Dashboard with real-time device status - Configurable registry URL in build config 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
165
dashboard/server.js
Normal file
165
dashboard/server.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const morgan = require('morgan');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const DATA_PATH = path.join(__dirname, 'data', 'devices.json');
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.static(path.join(__dirname, 'public'), {
|
||||
extensions: ['html']
|
||||
}));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
next();
|
||||
});
|
||||
|
||||
function ensureDataFile() {
|
||||
if (!fs.existsSync(DATA_PATH)) {
|
||||
fs.mkdirSync(path.dirname(DATA_PATH), { recursive: true });
|
||||
fs.writeFileSync(DATA_PATH, JSON.stringify([] , null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
function readDevices() {
|
||||
ensureDataFile();
|
||||
const raw = fs.readFileSync(DATA_PATH, 'utf-8');
|
||||
try {
|
||||
const devices = JSON.parse(raw);
|
||||
return Array.isArray(devices) ? devices : [];
|
||||
} catch (err) {
|
||||
console.error('Error parsing devices.json', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function writeDevices(devices) {
|
||||
ensureDataFile();
|
||||
fs.writeFileSync(DATA_PATH, JSON.stringify(devices, null, 2));
|
||||
}
|
||||
|
||||
function sanitizeId(input) {
|
||||
return String(input || '').trim();
|
||||
}
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get('/api/devices', (req, res) => {
|
||||
const devices = readDevices();
|
||||
res.json({ devices });
|
||||
});
|
||||
|
||||
app.post('/api/devices/register', (req, res) => {
|
||||
const {
|
||||
deviceId,
|
||||
deviceName,
|
||||
model,
|
||||
manufacturer,
|
||||
osVersion,
|
||||
appVersionName,
|
||||
appVersionCode
|
||||
} = req.body || {};
|
||||
|
||||
const trimmedId = sanitizeId(deviceId);
|
||||
if (!trimmedId) {
|
||||
return res.status(400).json({ error: 'deviceId is required' });
|
||||
}
|
||||
|
||||
const devices = readDevices();
|
||||
const now = new Date().toISOString();
|
||||
let existing = devices.find(d => d.deviceId === trimmedId);
|
||||
if (!existing) {
|
||||
existing = {
|
||||
deviceId: trimmedId,
|
||||
alias: '',
|
||||
deviceName: deviceName || '',
|
||||
model: model || '',
|
||||
manufacturer: manufacturer || '',
|
||||
osVersion: osVersion || '',
|
||||
appVersionName: appVersionName || '',
|
||||
appVersionCode: appVersionCode || 0,
|
||||
firstSeen: now,
|
||||
lastSeen: now,
|
||||
blocked: false,
|
||||
notes: '',
|
||||
installs: 1
|
||||
};
|
||||
devices.push(existing);
|
||||
} else {
|
||||
existing.deviceName = deviceName || existing.deviceName;
|
||||
existing.model = model || existing.model;
|
||||
existing.manufacturer = manufacturer || existing.manufacturer;
|
||||
existing.osVersion = osVersion || existing.osVersion;
|
||||
existing.appVersionName = appVersionName || existing.appVersionName;
|
||||
existing.appVersionCode = appVersionCode || existing.appVersionCode;
|
||||
existing.lastSeen = now;
|
||||
existing.installs = (existing.installs || 1) + 1;
|
||||
}
|
||||
|
||||
writeDevices(devices);
|
||||
res.json({ blocked: existing.blocked, device: existing });
|
||||
});
|
||||
|
||||
app.post('/api/devices/:deviceId/block', (req, res) => {
|
||||
const { deviceId } = req.params;
|
||||
const { reason } = req.body || {};
|
||||
const devices = readDevices();
|
||||
const existing = devices.find(d => d.deviceId === deviceId);
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
existing.blocked = true;
|
||||
existing.notes = reason || existing.notes;
|
||||
existing.blockedAt = new Date().toISOString();
|
||||
writeDevices(devices);
|
||||
res.json({ blocked: true, device: existing });
|
||||
});
|
||||
|
||||
app.post('/api/devices/:deviceId/unblock', (req, res) => {
|
||||
const { deviceId } = req.params;
|
||||
const devices = readDevices();
|
||||
const existing = devices.find(d => d.deviceId === deviceId);
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
existing.blocked = false;
|
||||
writeDevices(devices);
|
||||
res.json({ blocked: false, device: existing });
|
||||
});
|
||||
|
||||
app.put('/api/devices/:deviceId/alias', (req, res) => {
|
||||
const { deviceId } = req.params;
|
||||
const { alias } = req.body || {};
|
||||
const devices = readDevices();
|
||||
const existing = devices.find(d => d.deviceId === deviceId);
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
existing.alias = alias || '';
|
||||
writeDevices(devices);
|
||||
res.json({ device: existing });
|
||||
});
|
||||
|
||||
app.delete('/api/devices/:deviceId', (req, res) => {
|
||||
const { deviceId } = req.params;
|
||||
const devices = readDevices();
|
||||
const filtered = devices.filter(d => d.deviceId !== deviceId);
|
||||
if (filtered.length === devices.length) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
writeDevices(filtered);
|
||||
res.json({ removed: true });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
ensureDataFile();
|
||||
console.log(`StreamPlayer dashboard server listening on port ${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user