sing-song-player-3vx0f39v / web-socket.html
soiz1's picture
Update web-socket.html
dc53e23
raw
history blame
6.03 kB
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>管理ページ(IP・接続時間表示 + 地図表示)</title>
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
/>
<script
src="https://unpkg.com/[email protected]/dist/leaflet.js"
></script>
<style>
table { border-collapse: collapse; width: 100%; margin-bottom: 20px;}
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #eee; }
.highlight {
background-color: #f99;
transition: background-color 1s ease;
}
#map {
height: 400px;
width: 100%;
margin-top: 20px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>現在の接続ユーザー数: <span id="userCount">0</span></h1>
<div>更新はページ再読み込みで行います。</div>
<table>
<thead>
<tr>
<th>Socket ID</th>
<th>IPアドレス</th>
<th>接続時間</th>
<th>切断時間</th>
</tr>
</thead>
<tbody id="userTableBody"></tbody>
</table>
<div id="map"></div>
<script>
const socket = io("https://web-socket-server-14ap.onrender.com/");
const STORAGE_KEY = "userSessions";
// 前回セッション情報
let previousSessions = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
const userCountElem = document.getElementById("userCount");
const tbody = document.getElementById("userTableBody");
const mapDiv = document.getElementById("map");
// Leafletマップ初期化
const map = L.map('map').setView([35.681236, 139.767125], 5); // 東京を中心に
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// マーカー管理用(socketId => marker)
let markers = {};
// IPアドレスから緯度経度を取得(ip-api.comを使用)
async function fetchGeo(ip) {
if(!ip || ip === "-") return null;
try {
const response = await fetch(`https://ip-api.com/json/${ip}?fields=status,message,lat,lon`);
const data = await response.json();
if(data.status === "success") {
return { lat: data.lat, lon: data.lon };
} else {
console.warn(`IP位置情報取得失敗: ${ip} - ${data.message}`);
return null;
}
} catch (e) {
console.error("IP位置情報取得エラー", e);
return null;
}
}
// マーカーの色を指定するためのCircleMarkerを作る関数
function createCircleMarker(lat, lon, color, popupText) {
return L.circleMarker([lat, lon], {
radius: 8,
fillColor: color,
color: '#000',
weight: 1,
opacity: 1,
fillOpacity: 0.8
}).bindPopup(popupText);
}
socket.on("updateUserCount", (count) => {
userCountElem.textContent = count;
});
socket.on("userSessionsUpdate", async (userSessions) => {
tbody.innerHTML = "";
// まず、すべてのマーカーを地図から削除
for(let key in markers) {
map.removeLayer(markers[key]);
}
markers = {};
// すべてのIP位置取得を並列で実施
const positions = {};
await Promise.all(Object.entries(userSessions).map(async ([socketId, info]) => {
positions[socketId] = await fetchGeo(info.ip);
}));
for (const [socketId, info] of Object.entries(userSessions)) {
const tr = document.createElement("tr");
function createTd(text, elementKey) {
const td = document.createElement("td");
td.textContent = text;
const prevValue = previousSessions[socketId] ? previousSessions[socketId][elementKey] : undefined;
const currValue = info[elementKey];
let changed = prevValue !== currValue;
if (changed) {
td.classList.add("highlight");
setTimeout(() => td.classList.remove("highlight"), 1000);
}
return td;
}
tr.appendChild(createTd(socketId, "socketId"));
tr.appendChild(createTd(info.ip || "-", "ip"));
tr.appendChild(createTd(info.connectTime ? new Date(info.connectTime).toLocaleString() : "-", "connectTime"));
tr.appendChild(createTd(info.disconnectTime ? new Date(info.disconnectTime).toLocaleString() : "-", "disconnectTime"));
tbody.appendChild(tr);
// 位置情報が取得できた場合、マーカー表示
const pos = positions[socketId];
if(pos) {
// 前回位置と比較(緯度経度を比較)
const prevPos = previousSessions[socketId]?.geoPosition;
let changedPos = true;
if(prevPos && Math.abs(prevPos.lat - pos.lat) < 0.0001 && Math.abs(prevPos.lon - pos.lon) < 0.0001) {
changedPos = false;
}
// 色を決定:変化あれば赤丸、なければ黒丸
const color = changedPos ? 'red' : 'black';
const marker = createCircleMarker(pos.lat, pos.lon, color, `SocketID: ${socketId}<br>IP: ${info.ip || "-"}`);
marker.addTo(map);
markers[socketId] = marker;
}
}
// 今回のデータにジオ位置も追加してlocalStorageに保存
const sessionsToStore = {};
for(const [socketId, info] of Object.entries(userSessions)) {
sessionsToStore[socketId] = {
...info,
geoPosition: positions[socketId] || null
};
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(sessionsToStore));
previousSessions = sessionsToStore;
});
// 初回ロード時に前回のセッションを表示(あるなら)
if (Object.keys(previousSessions).length > 0) {
socket.emit("userSessionsUpdate", previousSessions);
}
</script>
</body>
</html>