diff --git a/Xboard/plugins/UserOnlineDevices/Controllers/UserOnlineDevicesController.php b/Xboard/plugins/UserOnlineDevices/Controllers/UserOnlineDevicesController.php index 38c2821..50582f8 100644 --- a/Xboard/plugins/UserOnlineDevices/Controllers/UserOnlineDevicesController.php +++ b/Xboard/plugins/UserOnlineDevices/Controllers/UserOnlineDevicesController.php @@ -46,6 +46,13 @@ class UserOnlineDevicesController extends PluginController ]); } + public function userStatus(): View + { + abort_unless($this->isPluginEnabled(), 404); + + return view('UserOnlineDevices::userstatus'); + } + public function panel(Request $request, int $user): View { abort_unless($this->isPluginEnabled(), 404); diff --git a/Xboard/plugins/UserOnlineDevices/README.md b/Xboard/plugins/UserOnlineDevices/README.md index 0d37c17..3584a4f 100644 --- a/Xboard/plugins/UserOnlineDevices/README.md +++ b/Xboard/plugins/UserOnlineDevices/README.md @@ -14,6 +14,7 @@ This plugin adds a user-facing online device dashboard for Xboard. - `GET /api/v1/user-online-devices/summary` - `GET /api/v1/user-online-devices/panel-url` +- `GET /user-online-devices/userstatus` - `GET /user-online-devices/panel/{user}` (temporary signed URL) ## Notes @@ -21,3 +22,4 @@ This plugin adds a user-facing online device dashboard for Xboard. - This plugin reuses Xboard's existing real-time device state data from Redis. - In current Xboard releases, plugin development is primarily backend-oriented, so this plugin ships a standalone user-side page instead of patching the compiled SPA bundle directly. - The "online device" count is effectively the number of unique online IPs reported by nodes. +- The easiest user entry is now `/user-online-devices/userstatus`, which reads the logged-in browser token and shows the current user's own status. diff --git a/Xboard/plugins/UserOnlineDevices/resources/views/userstatus.blade.php b/Xboard/plugins/UserOnlineDevices/resources/views/userstatus.blade.php new file mode 100644 index 0000000..1be079b --- /dev/null +++ b/Xboard/plugins/UserOnlineDevices/resources/views/userstatus.blade.php @@ -0,0 +1,458 @@ + + + + + + + User Status + + + + +
+
+
Xboard User Status
+

User Status

+
+ This page is provided by the User Online Devices plugin. After login, it reads your current token from browser storage and shows your own online IP list, online count and account status. +
+
+ + + +
+
+
User
+
-
+
Please log in first
+
+
+
Online IP Count
+
-
+
Unique real-time IP count reported by Xboard nodes
+
+
+
Last Online
+
-
+
Last device state update written by Xboard
+
+
+
Subscription
+
-
+
-
+
+
+ +
+
+
+
Online IP List
+
Auto Refresh
+
+
+
+ Current Xboard device state is counted by unique IP. If one device changes network, it may appear as a different online IP. +
+
+ +
+
+
Active Sessions
+
Checking
+
+
+
+ + +
+
+
+
+ + + + + diff --git a/Xboard/plugins/UserOnlineDevices/routes/web.php b/Xboard/plugins/UserOnlineDevices/routes/web.php index 093b1e9..e5e9c80 100644 --- a/Xboard/plugins/UserOnlineDevices/routes/web.php +++ b/Xboard/plugins/UserOnlineDevices/routes/web.php @@ -6,6 +6,9 @@ use Plugin\UserOnlineDevices\Controllers\UserOnlineDevicesController; Route::group([ 'prefix' => 'user-online-devices', ], function () { + Route::get('/userstatus', [UserOnlineDevicesController::class, 'userStatus']) + ->name('user-online-devices.userstatus'); + Route::get('/panel/{user}', [UserOnlineDevicesController::class, 'panel']) ->whereNumber('user') ->middleware('signed') diff --git a/Xboard/theme/Xboard/dashboard.blade.php b/Xboard/theme/Xboard/dashboard.blade.php index 7089e3f..7c976bd 100644 --- a/Xboard/theme/Xboard/dashboard.blade.php +++ b/Xboard/theme/Xboard/dashboard.blade.php @@ -1,4 +1,4 @@ - + @@ -124,6 +124,7 @@ async function loadSummary() { if (STATE.loading || STATE.loaded) return; + const authorization = getAuthHeader(); if (!authorization) return; @@ -160,8 +161,8 @@ } function buildTriggerHtml() { - const count = STATE.data?.user?.online_count ?? 0; - return '在线 IP' + count + ''; + const count = STATE.data && STATE.data.user ? STATE.data.user.online_count : 0; + return 'Online IP' + count + ''; } function buildMenuNode() { @@ -169,16 +170,18 @@ wrapper.className = 'xboard-online-devices-menu'; wrapper.setAttribute('data-user-online-devices-menu', '1'); - const devices = Array.isArray(STATE.data?.online_devices) ? STATE.data.online_devices : []; + const devices = STATE.data && Array.isArray(STATE.data.online_devices) ? STATE.data.online_devices : []; + const count = STATE.data && STATE.data.user ? STATE.data.user.online_count : 0; + const title = document.createElement('div'); title.className = 'xboard-online-devices-menu-title'; - title.innerHTML = '在线 IP / 设备' + (STATE.data?.user?.online_count ?? 0) + ''; + title.innerHTML = 'Online IP / Devices' + count + ''; wrapper.appendChild(title); if (!devices.length) { const empty = document.createElement('div'); empty.className = 'xboard-online-devices-menu-empty'; - empty.textContent = '当前没有检测到在线 IP。'; + empty.textContent = 'No online IP detected.'; wrapper.appendChild(empty); return wrapper; } @@ -199,16 +202,17 @@ function injectIntoAvatarArea() { if (!STATE.enabled || !STATE.data) return; - if (document.querySelector('[data-user-online-devices-trigger="1"]')) { - const current = document.querySelector('[data-user-online-devices-trigger="1"]'); - current.innerHTML = buildTriggerHtml(); + + const existing = document.querySelector('[data-user-online-devices-trigger="1"]'); + if (existing) { + existing.innerHTML = buildTriggerHtml(); return; } const avatar = document.querySelector('.n-avatar'); if (!avatar) return; - const anchor = avatar.closest('button, .n-button, .n-space, .n-flex, div'); + const anchor = avatar.closest('button, .n-button, .n-space, .n-flex, .n-popover-trigger, div'); if (!anchor || !anchor.parentElement) return; const badge = document.createElement('span'); @@ -221,14 +225,13 @@ function injectIntoDropdowns() { if (!STATE.enabled || !STATE.data) return; - const dropdowns = document.querySelectorAll('.n-dropdown-menu'); + const dropdowns = document.querySelectorAll('.n-dropdown-menu, .n-popover-shared .n-base-menu, .n-popover-shared .n-dropdown-menu'); dropdowns.forEach(function (dropdown) { if (dropdown.querySelector('[data-user-online-devices-menu="1"]')) return; - const menuNode = buildMenuNode(); const host = document.createElement('div'); host.className = 'n-dropdown-option'; - host.appendChild(menuNode); + host.appendChild(buildMenuNode()); dropdown.appendChild(host); }); } @@ -248,7 +251,7 @@ window.setTimeout(function () { injectIntoAvatarArea(); injectIntoDropdowns(); - }, 120); + }, 200); }, true); window.setTimeout(loadSummary, 1200); @@ -256,9 +259,18 @@ STATE.loaded = false; loadSummary(); }, 30000); + + window.__xboardUserOnlineDevicesDebug = { + reload: function () { + STATE.loaded = false; + return loadSummary(); + }, + state: STATE + }; })(); {!! $theme_config['custom_html'] !!} +