soiz1 commited on
Commit
4bf0c24
·
1 Parent(s): b31641d

Update web-socket.html

Browse files
Files changed (1) hide show
  1. web-socket.html +115 -124
web-socket.html CHANGED
@@ -2,27 +2,38 @@
2
  <html lang="ja">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <title>管理ページ(IP・接続時間表示・位置情報)</title>
6
  <script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
7
- <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
8
- <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
 
 
 
 
 
 
 
 
 
9
  <style>
10
- table { border-collapse: collapse; width: 100%; margin-top: 10px; }
11
  th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
12
  th { background-color: #eee; }
13
  .highlight {
14
  background-color: #f99;
15
  transition: background-color 1s ease;
16
  }
17
- #map { height: 400px; margin-top: 20px; }
18
- .marker-highlight {
19
- filter: drop-shadow(0 0 5px red);
 
 
20
  }
21
  </style>
22
  </head>
23
  <body>
24
  <h1>現在の接続ユーザー数: <span id="userCount">0</span></h1>
25
-
26
  <table>
27
  <thead>
28
  <tr>
@@ -39,118 +50,54 @@
39
 
40
  <script>
41
  const socket = io("https://web-socket-server-14ap.onrender.com/");
 
42
  const STORAGE_KEY = "userSessions";
 
 
43
  let previousSessions = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
44
 
45
  const userCountElem = document.getElementById("userCount");
46
  const tbody = document.getElementById("userTableBody");
 
47
 
48
- // Leaflet地図初期化
49
- const map = L.map('map').setView([35.68, 139.76], 5); // 東京中心にズーム5
50
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 
51
  attribution: '© OpenStreetMap contributors'
52
  }).addTo(map);
53
 
54
- // IP => 緯度経度をキャッシュ(session中の重複問い合わせ防止)
55
- const ipCache = {};
56
 
57
- // マーカーを保存(socketId => marker)
58
- const markers = {};
59
-
60
- // IPから位置情報を取得する関数(非同期)
61
- async function fetchLatLngFromIP(ip) {
62
- if (!ip) return null;
63
- if (ipCache[ip]) return ipCache[ip];
64
  try {
65
- const res = await fetch(`https://ipapi.co/${ip}/json/`);
66
- if (!res.ok) throw new Error('API error');
67
- const data = await res.json();
68
- if (data.latitude && data.longitude) {
69
- ipCache[ip] = [data.latitude, data.longitude];
70
- return ipCache[ip];
 
71
  }
72
- } catch(e) {
73
- console.warn("IP位置情報取得失敗:", ip, e);
 
74
  }
75
- return null;
76
  }
77
 
78
- // 表の行とマーカーを作る関数
79
- async function createRowAndMarker(socketId, info) {
80
- const tr = document.createElement("tr");
81
- const ip = info.ip || "-";
82
-
83
- // IP位置情報取得
84
- const latlng = await fetchLatLngFromIP(ip);
85
-
86
- // 位置情報変化判定
87
- const prevInfo = previousSessions[socketId];
88
- let locationChanged = false;
89
- if (prevInfo) {
90
- // 前回の位置と今回の位置が違ったらtrue
91
- if (latlng && prevInfo.latitude && prevInfo.longitude) {
92
- if (latlng[0] !== prevInfo.latitude || latlng[1] !== prevInfo.longitude) {
93
- locationChanged = true;
94
- }
95
- } else if ((latlng && (!prevInfo.latitude || !prevInfo.longitude)) ||
96
- ((!latlng) && (prevInfo.latitude || prevInfo.longitude))) {
97
- locationChanged = true;
98
- }
99
- } else if (latlng) {
100
- locationChanged = true;
101
- }
102
-
103
- // td作成関数
104
- function createTd(text) {
105
- const td = document.createElement("td");
106
- td.textContent = text;
107
- return td;
108
- }
109
-
110
- tr.appendChild(createTd(socketId));
111
- tr.appendChild(createTd(ip));
112
- tr.appendChild(createTd(info.connectTime ? new Date(info.connectTime).toLocaleString() : "-"));
113
- tr.appendChild(createTd(info.disconnectTime ? new Date(info.disconnectTime).toLocaleString() : "-"));
114
-
115
- if (locationChanged) {
116
- tr.classList.add("highlight");
117
- }
118
-
119
- tbody.appendChild(tr);
120
-
121
- // マーカー作成または更新
122
- if (latlng) {
123
- if (markers[socketId]) {
124
- // 既存マーカー更新
125
- markers[socketId].setLatLng(latlng);
126
- if (locationChanged) {
127
- markers[socketId].getElement().classList.add("marker-highlight");
128
- setTimeout(() => {
129
- if(markers[socketId]?.getElement()) {
130
- markers[socketId].getElement().classList.remove("marker-highlight");
131
- }
132
- }, 2000);
133
- }
134
- } else {
135
- // 新規マーカー作成
136
- const marker = L.marker(latlng).addTo(map).bindPopup(
137
- `Socket ID: ${socketId}<br>IP: ${ip}`
138
- );
139
- markers[socketId] = marker;
140
- if (locationChanged && marker.getElement()) {
141
- marker.getElement().classList.add("marker-highlight");
142
- setTimeout(() => {
143
- if(marker.getElement()) {
144
- marker.getElement().classList.remove("marker-highlight");
145
- }
146
- }, 2000);
147
- }
148
- }
149
- }
150
-
151
- // sessionに緯度経度を保存
152
- info.latitude = latlng ? latlng[0] : null;
153
- info.longitude = latlng ? latlng[1] : null;
154
  }
155
 
156
  socket.on("updateUserCount", (count) => {
@@ -159,38 +106,82 @@
159
 
160
  socket.on("userSessionsUpdate", async (userSessions) => {
161
  tbody.innerHTML = "";
162
- // すべてのマーカーを一旦削除(更新用)
163
- Object.values(markers).forEach(marker => map.removeLayer(marker));
164
- for (const key in markers) {
165
- delete markers[key];
166
  }
 
 
 
 
 
 
 
167
 
168
- // IP位置取得を含むため、順次await
169
  for (const [socketId, info] of Object.entries(userSessions)) {
170
- await createRowAndMarker(socketId, info);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
 
173
- // 地図の表示範囲調整(全マーカーが見えるように)
174
- const allLatLngs = Object.values(markers).map(m => m.getLatLng());
175
- if (allLatLngs.length > 0) {
176
- const bounds = L.latLngBounds(allLatLngs);
177
- map.fitBounds(bounds, { padding: [50, 50] });
178
- } else {
179
- map.setView([35.68, 139.76], 5);
180
  }
181
 
182
- // localStorageに保存(緯度経度も含む)
183
- localStorage.setItem(STORAGE_KEY, JSON.stringify(userSessions));
184
- previousSessions = JSON.parse(JSON.stringify(userSessions));
185
  });
186
 
187
- // 初回ロード時に前回のセッションを表示(あれば)
188
  if (Object.keys(previousSessions).length > 0) {
189
  socket.emit("userSessionsUpdate", previousSessions);
190
  }
191
-
192
- // 初回の更新要求(手動更新なしのためここで1回だけ)
193
- socket.emit("requestUserSessions");
194
  </script>
195
  </body>
196
  </html>
 
2
  <html lang="ja">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <title>管理ページ(IP・接続時間表示 + 地図表示)</title>
6
  <script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
7
+ <link
8
+ rel="stylesheet"
9
+ href="https://unpkg.com/[email protected]/dist/leaflet.css"
10
+ integrity="sha256-sA+e0L5rG3tXZm7sn+v3z4xn7As9H9i57VFlLx4Q3uU="
11
+ crossorigin=""
12
+ />
13
+ <script
14
+ src="https://unpkg.com/[email protected]/dist/leaflet.js"
15
+ integrity="sha256-o2eScTq2gk1sCewkwKJmJY4W1EfA/HC2n20MRKdA5h0="
16
+ crossorigin=""
17
+ ></script>
18
  <style>
19
+ table { border-collapse: collapse; width: 100%; margin-bottom: 20px;}
20
  th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
21
  th { background-color: #eee; }
22
  .highlight {
23
  background-color: #f99;
24
  transition: background-color 1s ease;
25
  }
26
+ #map {
27
+ height: 400px;
28
+ width: 100%;
29
+ margin-top: 20px;
30
+ border: 1px solid #ccc;
31
  }
32
  </style>
33
  </head>
34
  <body>
35
  <h1>現在の接続ユーザー数: <span id="userCount">0</span></h1>
36
+ <div>更新はページ再読み込みで行います。</div>
37
  <table>
38
  <thead>
39
  <tr>
 
50
 
51
  <script>
52
  const socket = io("https://web-socket-server-14ap.onrender.com/");
53
+
54
  const STORAGE_KEY = "userSessions";
55
+
56
+ // 前回セッション情報
57
  let previousSessions = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
58
 
59
  const userCountElem = document.getElementById("userCount");
60
  const tbody = document.getElementById("userTableBody");
61
+ const mapDiv = document.getElementById("map");
62
 
63
+ // Leafletマップ初期化
64
+ const map = L.map('map').setView([35.681236, 139.767125], 5); // 東京を中心に
65
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
66
+ maxZoom: 18,
67
  attribution: '© OpenStreetMap contributors'
68
  }).addTo(map);
69
 
70
+ // マーカー管理用(socketId => marker)
71
+ let markers = {};
72
 
73
+ // IPアドレスから緯度経度を取得(ip-api.comを使用)
74
+ async function fetchGeo(ip) {
75
+ if(!ip || ip === "-") return null;
 
 
 
 
76
  try {
77
+ const response = await fetch(`https://ip-api.com/json/${ip}?fields=status,message,lat,lon`);
78
+ const data = await response.json();
79
+ if(data.status === "success") {
80
+ return { lat: data.lat, lon: data.lon };
81
+ } else {
82
+ console.warn(`IP位置情報取得失敗: ${ip} - ${data.message}`);
83
+ return null;
84
  }
85
+ } catch (e) {
86
+ console.error("IP位置情報取得エラー", e);
87
+ return null;
88
  }
 
89
  }
90
 
91
+ // マーカーの色を指定するためのCircleMarkerを作る関数
92
+ function createCircleMarker(lat, lon, color, popupText) {
93
+ return L.circleMarker([lat, lon], {
94
+ radius: 8,
95
+ fillColor: color,
96
+ color: '#000',
97
+ weight: 1,
98
+ opacity: 1,
99
+ fillOpacity: 0.8
100
+ }).bindPopup(popupText);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
  socket.on("updateUserCount", (count) => {
 
106
 
107
  socket.on("userSessionsUpdate", async (userSessions) => {
108
  tbody.innerHTML = "";
109
+
110
+ // まず、すべてのマーカーを地図から削除
111
+ for(let key in markers) {
112
+ map.removeLayer(markers[key]);
113
  }
114
+ markers = {};
115
+
116
+ // すべてのIP位置取得を並列で実施
117
+ const positions = {};
118
+ await Promise.all(Object.entries(userSessions).map(async ([socketId, info]) => {
119
+ positions[socketId] = await fetchGeo(info.ip);
120
+ }));
121
 
 
122
  for (const [socketId, info] of Object.entries(userSessions)) {
123
+ const tr = document.createElement("tr");
124
+
125
+ function createTd(text, elementKey) {
126
+ const td = document.createElement("td");
127
+ td.textContent = text;
128
+
129
+ const prevValue = previousSessions[socketId] ? previousSessions[socketId][elementKey] : undefined;
130
+ const currValue = info[elementKey];
131
+
132
+ let changed = prevValue !== currValue;
133
+
134
+ if (changed) {
135
+ td.classList.add("highlight");
136
+ setTimeout(() => td.classList.remove("highlight"), 1000);
137
+ }
138
+
139
+ return td;
140
+ }
141
+
142
+ tr.appendChild(createTd(socketId, "socketId"));
143
+ tr.appendChild(createTd(info.ip || "-", "ip"));
144
+ tr.appendChild(createTd(info.connectTime ? new Date(info.connectTime).toLocaleString() : "-", "connectTime"));
145
+ tr.appendChild(createTd(info.disconnectTime ? new Date(info.disconnectTime).toLocaleString() : "-", "disconnectTime"));
146
+
147
+ tbody.appendChild(tr);
148
+
149
+ // 位置情報が取得できた場合、マーカー表示
150
+ const pos = positions[socketId];
151
+ if(pos) {
152
+ // 前回位置と比較(緯度経度を比較)
153
+ const prevPos = previousSessions[socketId]?.geoPosition;
154
+ let changedPos = true;
155
+ if(prevPos && Math.abs(prevPos.lat - pos.lat) < 0.0001 && Math.abs(prevPos.lon - pos.lon) < 0.0001) {
156
+ changedPos = false;
157
+ }
158
+
159
+ // 色を決定:変化あれば赤丸、なければ黒丸
160
+ const color = changedPos ? 'red' : 'black';
161
+
162
+ const marker = createCircleMarker(pos.lat, pos.lon, color, `SocketID: ${socketId}<br>IP: ${info.ip || "-"}`);
163
+ marker.addTo(map);
164
+ markers[socketId] = marker;
165
+ }
166
  }
167
 
168
+ // 今回のデータにジオ位置も追加してlocalStorageに保存
169
+ const sessionsToStore = {};
170
+ for(const [socketId, info] of Object.entries(userSessions)) {
171
+ sessionsToStore[socketId] = {
172
+ ...info,
173
+ geoPosition: positions[socketId] || null
174
+ };
175
  }
176
 
177
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(sessionsToStore));
178
+ previousSessions = sessionsToStore;
 
179
  });
180
 
181
+ // 初回ロード時に前回のセッションを表示(あるなら)
182
  if (Object.keys(previousSessions).length > 0) {
183
  socket.emit("userSessionsUpdate", previousSessions);
184
  }
 
 
 
185
  </script>
186
  </body>
187
  </html>