From 956054f387429033872afbc7d03d0b910d3cbe6b Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 23 Jul 2026 14:50:34 +0000 Subject: [PATCH] refactor(ui): Update legacy ui to be compatible with new monitoring structure --- src/backend/server/api.ts | 35 ++++++++++---- src/backend/sources/AbstractSource.ts | 8 ++-- src/backend/sources/AzuracastSource.ts | 7 ++- src/backend/sources/IcecastSource.ts | 1 - .../statusCard/ClientStatusCard.tsx | 46 +++++++++++++++++-- .../statusCard/SourceStatusCard.tsx | 19 ++++---- .../components/statusCard/clientDucks.ts | 17 ++++++- src/core/Atomic.ts | 3 +- 8 files changed, 103 insertions(+), 33 deletions(-) diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 921a2802..e957a628 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -380,7 +380,6 @@ export const setupApi = (app: Express, logger: Logger, appLoggerStream: PassThro players: 'players' in x ? (x as MemorySource).playersToObject() as unknown as Record : {}, sot: ('playerSourceOfTruth' in x) ? x.playerSourceOfTruth as SOURCE_SOT_TYPES : SOURCE_SOT.HISTORY, supportsUpstreamRecentlyPlayed: x.supportsUpstreamRecentlyPlayed, - supportsManualListening: true, manualListening: x.monitoringActivity, systemListeningBehavior: x.getSystemMonitoring(), ...x.additionalApiData() @@ -427,7 +426,9 @@ export const setupApi = (app: Express, logger: Logger, appLoggerStream: PassThro initialized: x.isReady(), deadLetterScrobbles: x.deadLetterQueued, // x.deadLetterScrobbles.length, deadLetterScrobblesTotal: x.deadLetterLength, - queued: x.queuedLength // x.queuedScrobbles.length + queued: x.queuedLength, // x.queuedScrobbles.length + manualListening: x.monitoringActivity, + systemListeningBehavior: x.getSystemMonitoring(), }; if (!base.initialized) { if(x.buildOK === false) { @@ -671,19 +672,35 @@ export const setupApi = (app: Express, logger: Logger, appLoggerStream: PassThro } } = req; - if(!source.supportsManualListening) - { - source.logger.warn('This source does not support manual Should Scrobble state'); - res.status(400).send(); - return; + let listening: boolean | undefined; + if(listeningQ !== undefined) { + listening = parseBool(listeningQ) } + source.logger.verbose(`User requested Monitoring status ${listening === undefined ? 'system' : listening}`); + + source.monitoringActivity = listening; + + res.status(200).json({listening}); + }); + + app.use('/api/client/listen', clientRequiredMiddle); + app.post('/api/client/listen', async (req, res) => { + // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message + const client = req.scrobbleClient as AbstractScrobbleClient; + + const { + query: { + listening: listeningQ + } + } = req; + let listening: boolean | undefined; if(listeningQ !== undefined) { listening = parseBool(listeningQ) } - source.logger.verbose(`User requested Should Scrobble status ${listening === undefined ? 'system' : listening}`); + client.logger.verbose(`User requested Monitoring status ${listening === undefined ? 'system' : listening}`); - source.manualListening = listening; + client.monitoringActivity = listening; res.status(200).json({listening}); }); diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 683c3723..669c0ca0 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -87,8 +87,6 @@ export default abstract class AbstractSource extends AbstractComponent implement supportsUpstreamNowPlaying: boolean = false; supportsManualListening: boolean = false; - manualListening?: boolean - scheduler: ToadScheduler = new ToadScheduler(); protected SCROBBLE_BACKLOG_COUNT: number = 30; @@ -268,12 +266,12 @@ export default abstract class AbstractSource extends AbstractComponent implement } public getRunningState(): ComponentState { - const monitoring = (this.canPoll && this.polling) || !this.canPoll; + const running = (this.canPoll && this.polling) || !this.canPoll; - if(monitoring && this.supportsManualListening && this.manualListening === false) { + if(running && !this.isMonitoring()) { return COMPONENT_STATE.MUTED; } - return monitoring ? COMPONENT_STATE.RUNNING : COMPONENT_STATE.IDLE; + return running ? COMPONENT_STATE.RUNNING : COMPONENT_STATE.IDLE; } protected getComponentApiData() { diff --git a/src/backend/sources/AzuracastSource.ts b/src/backend/sources/AzuracastSource.ts index d4d3f393..607ba933 100644 --- a/src/backend/sources/AzuracastSource.ts +++ b/src/backend/sources/AzuracastSource.ts @@ -37,7 +37,6 @@ export class AzuracastSource extends MemorySource { this.requiresAuth = false; this.canPoll = true; - this.supportsManualListening = true; } protected async doBuildInitData(): Promise { @@ -175,9 +174,9 @@ export class AzuracastSource extends MemorySource { this.logger.debug({labels: `Station ${this.config.data.station}`}, `Currently offline`); return false; } - if(this.manualListening === true) { - this.logger.debug({labels: `Station ${this.config.data.station}`}, `Using manual listening status ${this.manualListening}`); - return this.manualListening; + if(this.monitoringActivity !== undefined) { + this.logger.debug({labels: `Station ${this.config.data.station}`}, `Using manual listening status ${this.monitoringActivity}`); + return this.monitoringActivity; } if(this.config.data.monitorWhenListeners !== undefined) { if(this.config.data.monitorWhenListeners === true && this.wsNowPlaying.listeners.current === 0) { diff --git a/src/backend/sources/IcecastSource.ts b/src/backend/sources/IcecastSource.ts index 2b8b134d..7b78e35a 100644 --- a/src/backend/sources/IcecastSource.ts +++ b/src/backend/sources/IcecastSource.ts @@ -44,7 +44,6 @@ export class IcecastSource extends MemorySource { this.requiresAuth = false; this.canPoll = true; - this.supportsManualListening = true; } protected async doBuildInitData(): Promise { diff --git a/src/client/components/statusCard/ClientStatusCard.tsx b/src/client/components/statusCard/ClientStatusCard.tsx index 79605d93..f618fc91 100644 --- a/src/client/components/statusCard/ClientStatusCard.tsx +++ b/src/client/components/statusCard/ClientStatusCard.tsx @@ -5,7 +5,7 @@ import {clientAdapter} from "../../status/ducks"; import type {RootState} from "../../store"; import {connect, type ConnectedProps} from "react-redux"; import {Link} from "react-router-dom"; -import {useStartClientMutation} from "./clientDucks"; +import {useStartClientMutation, useListenClientMutation} from "./clientDucks"; export interface ClientStatusCardData extends StatusCardSkeletonData, PropsFromRedux { loading?: boolean @@ -40,8 +40,25 @@ const ClientStatusCard = (props: ClientStatusCardData) => { } = props; const [startClientPut, startResult] = useStartClientMutation(); + const [listenPut, listenResult] = useListenClientMutation(); const tryStart = useCallback((name: string, force?: boolean) => startClientPut({name, force}), [startClientPut]); + const tryListen = useCallback((name: string, type: string, currentListening?: boolean) => { + // cycle through states + let nextListen: boolean | undefined; + switch(currentListening) { + case true: + nextListen = false; + break; + case false: + nextListen = undefined; + break; + case undefined: + nextListen = true; + break; + } + listenPut({name, type, listening: nextListen}); + }, [listenPut]); let header: string | undefined = display; let body = ; @@ -50,10 +67,13 @@ const ClientStatusCard = (props: ClientStatusCardData) => {
tryStart(name)} className="capitalize underline cursor-pointer inline mr-1">{status === 'Running' ? 'Restart' : 'Start'}
(
tryStart(name, true)} className="capitalize underline cursor-pointer inline">Force
) ) + let subtitleElement = startClientElement; if(data !== undefined) { const { hasAuth, hasAuthInteraction, + manualListening, + systemListeningBehavior, name, type, authed, @@ -61,9 +81,27 @@ const ClientStatusCard = (props: ClientStatusCardData) => { } = data; header = `${display} (Client)`; - const scrobbled = initialized && (!hasAuth || (hasAuth && authed)) ? Tracks Scrobbled : Tracks Scrobbled; + const ml = useMemo(() => { + if(listenResult.status !== 'fulfilled' || listenResult.data === undefined) { + return manualListening; + } + return (listenResult.data as any).listening; + }, [manualListening, listenResult]); + + const manualListenElement = ( + Monitoring: +
tryListen(name, type, ml)} + className="capitalize underline cursor-pointer inline mr-1 ml-1"> + {ml !== undefined ? (ml ? 'Yes' : 'No') : null} + {ml === undefined ? System {systemListeningBehavior ? '(Yes)' : '(No)'} : null} +
+ {/* {ml !== undefined ?
tryListen(name, type, undefined)} + className="capitalize underline cursor-pointer inline">Clear +
: null} */} +
); + subtitleElement = {startClientElement};//{manualListenElement} | {startClientElement}; - // TODO links + const scrobbled = initialized && (!hasAuth || (hasAuth && authed)) ? Tracks Scrobbled : Tracks Scrobbled; body = (
{scrobbled}: {scrobbledCount}
Queued Scrobbles: {queued}
@@ -77,7 +115,7 @@ const ClientStatusCard = (props: ClientStatusCardData) => { title={header} subtitle={name} status={status} - subtitleRight={startClientElement} + subtitleRight={subtitleElement} statusType={statusToStatusType(status)}> {body} diff --git a/src/client/components/statusCard/SourceStatusCard.tsx b/src/client/components/statusCard/SourceStatusCard.tsx index 4b4937be..66a3eb29 100644 --- a/src/client/components/statusCard/SourceStatusCard.tsx +++ b/src/client/components/statusCard/SourceStatusCard.tsx @@ -8,6 +8,9 @@ import {connect, type ConnectedProps} from "react-redux"; import Player from "../player/Player"; import {useStartSourceMutation, useListenSourceMutation} from "./sourceDucks"; import './statusCard.scss'; +import Tooltip from '../Tooltip'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons' const ambiguousTypes = ['lastfm','listenbrainz','koito','librefm','maloja','rocksky']; export interface SourceStatusCardData extends StatusCardSkeletonData, PropsFromRedux { @@ -59,8 +62,6 @@ const SourceStatusCard = (props: SourceStatusCardData) => { } listenPut({name, type, listening: nextListen}); }, [listenPut]); - let startSourceElement = null; - let manualListenElement = null; let subtitleElement = null; if(data !== undefined) @@ -78,7 +79,6 @@ const SourceStatusCard = (props: SourceStatusCardData) => { players = {}, sot, supportsUpstreamRecentlyPlayed, - supportsManualListening, manualListening, systemListeningBehavior } = data; @@ -100,9 +100,13 @@ const SourceStatusCard = (props: SourceStatusCardData) => { return (listenResult.data as any).listening; }, [manualListening, listenResult]); - if(supportsManualListening) { - manualListenElement = ( - Should Scrobble: + const manualListenElement = type !== 'azuracast' && type !== 'icecast' ? null : ( + Monitoring + : +
tryListen(name, type, ml)} className="capitalize underline cursor-pointer inline mr-1 ml-1"> {ml !== undefined ? (ml ? 'Yes' : 'No') : null} @@ -112,9 +116,8 @@ const SourceStatusCard = (props: SourceStatusCardData) => { className="capitalize underline cursor-pointer inline">Clear
: null} */}
); - } - startSourceElement = ( + const startSourceElement = (
tryStart(name, type)} className="capitalize underline cursor-pointer inline mr-1">{startText}
diff --git a/src/client/components/statusCard/clientDucks.ts b/src/client/components/statusCard/clientDucks.ts index 1559213c..039641f8 100644 --- a/src/client/components/statusCard/clientDucks.ts +++ b/src/client/components/statusCard/clientDucks.ts @@ -16,8 +16,23 @@ export const scrobblerApi = createApi({ force: params.force } }) + }), + listenClient: builder.mutation({ + query: (params) => ({ + url: '/client/listen', + method: 'POST', + params: { + name: params.name, + type: params.type, + listening: params.listening + } + }) }) }) }); -export const {useStartClientMutation} = scrobblerApi; +export const {useStartClientMutation, useListenClientMutation} = scrobblerApi; diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index f426a003..fcbca122 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -28,7 +28,6 @@ export interface SourceStatusData { players: Record sot: SOURCE_SOT_TYPES supportsUpstreamRecentlyPlayed: boolean; - supportsManualListening: boolean; manualListening?: boolean systemListeningBehavior?: boolean } @@ -46,6 +45,8 @@ export interface ClientStatusData { hasAuthInteraction: boolean; authed: boolean; initialized: boolean; + manualListening?: boolean + systemListeningBehavior?: boolean } export type PlayObjectIncludeTypes = 'album' | 'time' | 'artist' | 'track' | 'timeFromNow' | 'trackId' | 'comment' | 'platform' | 'session'; -- 2.51.2