From 4ae7533b855e1bd27cf393a7bbe9b209559553f9 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Wed, 8 Apr 2026 13:13:46 -0500 Subject: [PATCH] Hide unclaimed devices from dashboard, add unassigned API, add upgrade docs - Filter out devices with no user_id from the main device listing - Add GET /api/devices/unassigned endpoint (admin only) for unclaimed devices - Add "Updating" section to README with upgrade instructions Co-Authored-By: Claude Opus 4.6 --- README.md | 34 ++++++++++++++++++++++++++++++++++ server/routes/devices.js | 16 +++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a1b0c6..df6ddb9 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,40 @@ server { } ``` +### Updating + +To update a running instance to the latest version: + +```bash +cd /opt/screentinker + +# Back up the database first +sqlite3 server/db/remote_display.db ".backup server/db/backup-$(date +%F).db" + +# Pull latest code +git pull origin main + +# Install any new dependencies +cd server && npm install --production + +# Restart the service +sudo systemctl restart screentinker +``` + +If you deployed without git, you can initialize it: + +```bash +cd /opt/screentinker +git init +git remote add origin https://github.com/screentinker/screentinker.git +git fetch origin main +git checkout origin/main -- . +cd server && npm install --production +sudo systemctl restart screentinker +``` + +Your database, uploads, and configuration are preserved — only code files are updated. + ### Backups The SQLite database is at `server/db/remote_display.db`. Back it up regularly: diff --git a/server/routes/devices.js b/server/routes/devices.js index 2f7ffc6..7f78c25 100644 --- a/server/routes/devices.js +++ b/server/routes/devices.js @@ -24,13 +24,27 @@ router.get('/', (req, res) => { INNER JOIN (SELECT device_id, MAX(captured_at) as max_at FROM screenshots GROUP BY device_id) latest ON sc.device_id = latest.device_id AND sc.captured_at = latest.max_at ) s ON d.id = s.device_id - ${isAdmin ? '' : 'WHERE (d.user_id = ? OR d.team_id IN (SELECT team_id FROM team_members WHERE user_id = ?))'} + ${isAdmin ? 'WHERE d.user_id IS NOT NULL' : 'WHERE d.user_id IS NOT NULL AND (d.user_id = ? OR d.team_id IN (SELECT team_id FROM team_members WHERE user_id = ?))'} ORDER BY d.created_at ASC LIMIT ? OFFSET ? `).all(...(isAdmin ? [] : [req.user.id, req.user.id]), Math.min(parseInt(req.query.limit) || 100, 500), parseInt(req.query.offset) || 0); res.json(devices); }); +// List unclaimed provisioning devices (admin only) +router.get('/unassigned', (req, res) => { + if (!['admin', 'superadmin'].includes(req.user.role)) { + return res.status(403).json({ error: 'Admin access required' }); + } + const devices = db.prepare(` + SELECT id, pairing_code, status, ip_address, android_version, app_version, + screen_width, screen_height, created_at, last_heartbeat + FROM devices WHERE user_id IS NULL + ORDER BY created_at DESC + `).all(); + res.json(devices); +}); + // Get single device with telemetry history router.get('/:id', (req, res) => { const device = db.prepare('SELECT d.*, u.email as owner_email, u.name as owner_name FROM devices d LEFT JOIN users u ON d.user_id = u.id WHERE d.id = ?').get(req.params.id);