soiz1 commited on
Commit
b31641d
·
1 Parent(s): 47f5399

Update web-socket.html

Browse files
Files changed (1) hide show
  1. web-socket.html +138 -53
web-socket.html CHANGED
@@ -2,21 +2,27 @@
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
  <style>
8
- table { border-collapse: collapse; width: 100%; }
9
  th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
10
  th { background-color: #eee; }
11
  .highlight {
12
  background-color: #f99;
13
  transition: background-color 1s ease;
14
  }
 
 
 
 
15
  </style>
16
  </head>
17
  <body>
18
  <h1>現在の接続ユーザー数: <span id="userCount">0</span></h1>
19
- <div>更新まで: <span id="countdown">5</span>秒</div>
20
  <table>
21
  <thead>
22
  <tr>
@@ -29,83 +35,162 @@
29
  <tbody id="userTableBody"></tbody>
30
  </table>
31
 
 
 
32
  <script>
33
  const socket = io("https://web-socket-server-14ap.onrender.com/");
34
-
35
- // ローカルストレージのキー
36
  const STORAGE_KEY = "userSessions";
37
-
38
- // localStorageから前回セッション情報を取得(無ければ空オブジェクト)
39
  let previousSessions = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
40
- let countdown = 5;
41
 
42
  const userCountElem = document.getElementById("userCount");
43
- const countdownElem = document.getElementById("countdown");
44
  const tbody = document.getElementById("userTableBody");
45
 
46
- // カウントダウンタイマー
47
- setInterval(() => {
48
- countdown--;
49
- if (countdown < 0) countdown = 5;
50
- countdownElem.textContent = countdown;
51
- }, 1000);
52
-
53
- // 5秒ごとに更新要求(サーバーが自動送信なら不要)
54
- setInterval(() => {
55
- socket.emit("requestUserSessions");
56
- }, 5000);
57
-
58
- socket.on("updateUserCount", (count) => {
59
- userCountElem.textContent = count;
60
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- socket.on("userSessionsUpdate", (userSessions) => {
63
- tbody.innerHTML = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- for (const [socketId, info] of Object.entries(userSessions)) {
66
- const tr = document.createElement("tr");
 
 
 
 
67
 
68
- function createTd(text, elementKey) {
69
- const td = document.createElement("td");
70
- td.textContent = text;
 
71
 
72
- const prevValue = previousSessions[socketId] ? previousSessions[socketId][elementKey] : undefined;
73
- const currValue = info[elementKey];
 
74
 
75
- let changed = false;
76
- if (elementKey === "connectTime" || elementKey === "disconnectTime") {
77
- changed = prevValue !== currValue;
78
- } else {
79
- changed = prevValue !== currValue;
 
 
 
 
 
 
 
 
 
80
  }
81
-
82
- if (changed) {
83
- td.classList.add("highlight");
84
- setTimeout(() => td.classList.remove("highlight"), 1000);
 
 
 
 
 
 
 
 
 
85
  }
86
-
87
- return td;
88
  }
 
 
 
 
 
 
89
 
90
- tr.appendChild(createTd(socketId, "socketId"));
91
- tr.appendChild(createTd(info.ip || "-", "ip"));
92
- tr.appendChild(createTd(info.connectTime ? new Date(info.connectTime).toLocaleString() : "-", "connectTime"));
93
- tr.appendChild(createTd(info.disconnectTime ? new Date(info.disconnectTime).toLocaleString() : "-", "disconnectTime"));
 
 
 
 
 
 
 
94
 
95
- tbody.appendChild(tr);
 
 
 
 
 
 
 
 
 
 
 
96
  }
97
 
98
- // 今回のデータをlocalStorageに保存(ディープコピー推奨)
99
  localStorage.setItem(STORAGE_KEY, JSON.stringify(userSessions));
100
- // previousSessionsも更新
101
  previousSessions = JSON.parse(JSON.stringify(userSessions));
102
  });
103
 
104
- // 初回ロード時に前回のセッションを表示(あるなら)
105
  if (Object.keys(previousSessions).length > 0) {
106
- // 自己発火用の擬似的なイベントで描画のみ実施
107
  socket.emit("userSessionsUpdate", previousSessions);
108
  }
 
 
 
109
  </script>
110
  </body>
111
  </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 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>
 
35
  <tbody id="userTableBody"></tbody>
36
  </table>
37
 
38
+ <div id="map"></div>
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) => {
157
+ userCountElem.textContent = count;
158
+ });
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>