feat: now-playing liveness dot + admin-gated radio management (#1086)

* feat(now-playing): liveness indicator dot in the listening popover

Replace the raw "Nm ago" line in the "Who is listening?" popover with a
derived presence dot (green playing / amber paused / dim idle). The presence
is computed in one tested helper that unifies the playbackReport transport
state with the legacy getNowPlaying recency, instead of formatting a raw
timestamp inline. The dot carries the localized status as an aria-label and
tooltip so it is not conveyed by colour alone.

* feat(radio): gate station create/edit/delete behind Navidrome admin role

Navidrome >= 0.62 restricts internet-radio management to admins
(GHSA-jw24-qqrj-633c); non-admin requests fail. Hide Add Station, Search
Directory, the per-card edit chip and delete button for confirmed standard
Navidrome users via a canManageNavidromeRadio() helper on the existing
useNavidromeAdminRole framework. Admins, non-Navidrome servers and transient
states stay unrestricted; playback and favourites remain available to all.

* docs(changelog): now-playing status dot + admin-gated radio (#1086)
This commit is contained in:
Psychotoxical
2026-06-14 01:30:25 +02:00
committed by GitHub
parent 0b7d9eae2d
commit c1403f8bd6
19 changed files with 231 additions and 44 deletions
+13 -1
View File
@@ -8,7 +8,7 @@ vi.mock('@/api/navidromeAdmin', () => ({
}));
import { ndLogin } from '@/api/navidromeAdmin';
import { useNavidromeAdminRole } from './useNavidromeAdminRole';
import { useNavidromeAdminRole, canManageNavidromeRadio } from './useNavidromeAdminRole';
beforeEach(() => {
resetAuthStore();
@@ -112,3 +112,15 @@ describe('useNavidromeAdminRole', () => {
await waitFor(() => expect(result.current).toBe('error'));
});
});
describe('canManageNavidromeRadio', () => {
it('blocks only a confirmed standard Navidrome user', () => {
expect(canManageNavidromeRadio('user')).toBe(false);
});
it('allows admins, non-Navidrome servers, and transient/unknown states', () => {
for (const role of ['admin', 'na', 'idle', 'checking', 'error'] as const) {
expect(canManageNavidromeRadio(role)).toBe(true);
}
});
});
+11
View File
@@ -5,6 +5,17 @@ import { isNavidromeServer } from '../utils/server/subsonicServerIdentity';
export type NavidromeAdminRole = 'idle' | 'checking' | 'admin' | 'user' | 'na' | 'error';
/**
* Navidrome ≥ 0.62 restricts internet-radio management (create/update/delete) to
* admins (GHSA-jw24-qqrj-633c). Block those actions only for a *confirmed*
* standard Navidrome user; everything else — admin, non-Navidrome servers
* (`'na'`), and transient/unknown states — stays allowed, with the server as the
* final authority. Non-Navidrome servers never carried this restriction.
*/
export function canManageNavidromeRadio(role: NavidromeAdminRole): boolean {
return role !== 'user';
}
function normalizeServerUrl(url: string): string {
const withScheme = url.startsWith('http') ? url : `http://${url}`;
return withScheme.replace(/\/$/, '');