mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
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:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(/\/$/, '');
|
||||
|
||||
Reference in New Issue
Block a user