ip list (testing)

This commit is contained in:
legop3
2026-01-22 22:48:31 -05:00
parent dbc5111c53
commit 98ca2e0626
5 changed files with 99 additions and 12 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-CP3udCxF.js"></script>
<script type="module" crossorigin src="/assets/index-AuPBIRIK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-GYsISCRb.css">
</head>
<body>
+25 -1
View File
@@ -165,7 +165,31 @@ function pickRover() {
if (candidates.length === 0) {
return null;
}
candidates.sort((a, b) => a.drivers.size - b.drivers.size);
const dockedRank = (rover) => {
if (!rover) return 0;
if (rover.docked === true) return -1;
if (rover.docked === false) return 1;
const sensors = rover.lastSensor?.decoded || rover.lastSensor?.sensors || null;
const docked = sensors?.chargingSources?.homeBase;
if (docked === true) return -1;
if (docked === false) return 1;
return 0;
};
const idleRank = (rover) => (rover?.drivers?.size === 0 ? 1 : 0);
candidates.sort((a, b) => {
const aEmpty = idleRank(a);
const bEmpty = idleRank(b);
if (aEmpty !== bEmpty) return bEmpty - aEmpty;
const aDockRank = dockedRank(a);
const bDockRank = dockedRank(b);
if (aEmpty === 1 && aDockRank !== bDockRank) {
return bDockRank - aDockRank;
}
if (a.drivers.size !== b.drivers.size) {
return a.drivers.size - b.drivers.size;
}
return bDockRank - aDockRank;
});
return candidates[0];
}
+27 -2
View File
@@ -22,10 +22,33 @@ logger.info('Discord invite loaded:', discordInvite ? 'present' : 'not configure
logger.info('Ko-fi link loaded:', kofiLink ? 'present' : 'not configured');
const ACTIVITY_SYNC_COOLDOWN_MS = 3000;
const ADMIN_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
let lastActivitySync = 0;
let pendingActivitySync = null;
function buildUserEntry(socket) {
function extractSocketIp(socket) {
if (!socket) return null;
const headers = socket.handshake?.headers || {};
const forwardedFor = headers['x-forwarded-for'];
if (typeof forwardedFor === 'string' && forwardedFor.trim()) {
return forwardedFor.split(',')[0].trim();
}
if (Array.isArray(forwardedFor) && forwardedFor.length) {
return String(forwardedFor[0]).trim();
}
const realIp = headers['x-real-ip'];
if (typeof realIp === 'string' && realIp.trim()) {
return realIp.trim();
}
return (
socket.handshake?.address ||
socket.conn?.remoteAddress ||
socket.request?.connection?.remoteAddress ||
null
);
}
function buildUserEntry(socket, includeIp = false) {
if (!socket) return null;
const role = getRole(socket);
const assignment = assignmentService.describeAssignment(socket.id);
@@ -35,12 +58,14 @@ function buildUserEntry(socket) {
nickname: getNickname(socket) || null,
role,
roverId: primaryRover || assignment?.roverId || null,
ip: includeIp ? extractSocketIp(socket) : null,
};
}
function buildSession(socket) {
const includeIps = ADMIN_ROLES.has(getRole(socket));
const users = Array.from(io.sockets.sockets.values())
.map((sock) => buildUserEntry(sock))
.map((sock) => buildUserEntry(sock, includeIps))
.filter(Boolean);
return {
socketId: socket?.id || null,
+38
View File
@@ -17,6 +17,16 @@ export default function AdminPanel() {
const currentGoal = session?.communityGoal?.text || '';
const goalUpdatedAt = session?.communityGoal?.updatedAt || null;
const [goalDraft, setGoalDraft] = useState(currentGoal);
const socketIps = useMemo(() => {
const users = session?.users ?? [];
return users
.map((user) => ({
socketId: user.socketId,
label: user.nickname || user.socketId?.slice(0, 6) || 'unknown',
ip: user.ip || null,
}))
.filter((entry) => entry.ip);
}, [session?.users]);
const isAdmin =
session?.role === 'admin' ||
@@ -135,6 +145,7 @@ export default function AdminPanel() {
)}
/>
<ReplaySnapshotHealth health={health} />
<WebsocketIpPanel entries={socketIps} />
</section>
);
}
@@ -200,3 +211,30 @@ function ReplaySnapshotHealth({ health }) {
</div>
);
}
function WebsocketIpPanel({ entries }) {
const total = entries?.length || 0;
const uniqueCount = useMemo(() => new Set((entries || []).map((entry) => entry.ip)).size, [entries]);
return (
<div className="space-y-0.5">
<div className="flex items-center justify-between text-xs text-slate-400">
<span className="panel-muted text-xs uppercase">Websocket IPs</span>
<span className="text-slate-500">
{uniqueCount} unique / {total} sockets
</span>
</div>
<div className="surface space-y-0.5 text-xs text-slate-200">
{total === 0 ? (
<p className="text-xs text-slate-500">No IPs reported yet.</p>
) : (
entries.map((entry) => (
<div key={entry.socketId} className="flex items-center justify-between">
<span className="text-slate-300">{entry.label}</span>
<span className="rounded bg-slate-800 px-1 text-[0.7rem] text-slate-200">{entry.ip}</span>
</div>
))
)}
</div>
</div>
);
}