refactor(ui): Update legacy ui to be compatible with new monitoring structure

This commit is contained in:
FoxxMD
2026-07-23 14:50:34 +00:00
parent 86ebd3d673
commit 956054f387
8 changed files with 104 additions and 34 deletions
+27 -10
View File
@@ -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<string,SourcePlayerJson> : {},
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 Should Scrobble status ${listening === undefined ? 'system' : listening}`);
source.logger.verbose(`User requested Monitoring status ${listening === undefined ? 'system' : listening}`);
source.manualListening = 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)
}
client.logger.verbose(`User requested Monitoring status ${listening === undefined ? 'system' : listening}`);
client.monitoringActivity = listening;
res.status(200).json({listening});
});
+3 -5
View File
@@ -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() {
+3 -4
View File
@@ -37,7 +37,6 @@ export class AzuracastSource extends MemorySource {
this.requiresAuth = false;
this.canPoll = true;
this.supportsManualListening = true;
}
protected async doBuildInitData(): Promise<true | string | undefined> {
@@ -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) {
-1
View File
@@ -44,7 +44,6 @@ export class IcecastSource extends MemorySource {
this.requiresAuth = false;
this.canPoll = true;
this.supportsManualListening = true;
}
protected async doBuildInitData(): Promise<true | string | undefined> {
@@ -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 = <SkeletonParagraph/>;
@@ -50,10 +67,13 @@ const ClientStatusCard = (props: ClientStatusCardData) => {
<div onClick={() => tryStart(name)} className="capitalize underline cursor-pointer inline mr-1">{status === 'Running' ? 'Restart' : 'Start'}</div>
(<div onClick={() => tryStart(name, true)} className="capitalize underline cursor-pointer inline">Force</div>)
</Fragment>)
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)) ? <Link to={`/scrobbled?type=${type}&name=${name}`}>Tracks Scrobbled</Link> : <span>Tracks Scrobbled</span>;
const ml = useMemo(() => {
if(listenResult.status !== 'fulfilled' || listenResult.data === undefined) {
return manualListening;
}
return (listenResult.data as any).listening;
}, [manualListening, listenResult]);
// TODO links
const manualListenElement = (<Fragment>
<span>Monitoring:</span>
<div onClick={() => tryListen(name, type, ml)}
className="capitalize underline cursor-pointer inline mr-1 ml-1">
{ml !== undefined ? (ml ? 'Yes' : 'No') : null}
{ml === undefined ? <span>System {systemListeningBehavior ? '(Yes)' : '(No)'}</span> : null}
</div>
{/* {ml !== undefined ? <div onClick={() => tryListen(name, type, undefined)}
className="capitalize underline cursor-pointer inline">Clear
</div> : null} */}
</Fragment>);
subtitleElement = <Fragment>{startClientElement}</Fragment>;//<Fragment>{manualListenElement} | {startClientElement}</Fragment>;
const scrobbled = initialized && (!hasAuth || (hasAuth && authed)) ? <Link to={`/scrobbled?type=${type}&name=${name}`}>Tracks Scrobbled</Link> : <span>Tracks Scrobbled</span>;
body = (<Fragment>
<div>{scrobbled}: {scrobbledCount}</div>
<div>Queued Scrobbles: {queued}</div>
@@ -77,7 +115,7 @@ const ClientStatusCard = (props: ClientStatusCardData) => {
title={header}
subtitle={name}
status={status}
subtitleRight={startClientElement}
subtitleRight={subtitleElement}
statusType={statusToStatusType(status)}>
{body}
</StatusCardSkeleton>
@@ -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 = (<Fragment>
<span>Should Scrobble:</span>
const manualListenElement = type !== 'azuracast' && type !== 'icecast' ? null : (<Fragment>
<span>Monitoring <Tooltip message="test"
style={{
display: 'inline-flex'
}}>
<FontAwesomeIcon color="white" icon={faQuestionCircle}/></Tooltip> :
</span>
<div onClick={() => 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
</div> : null} */}
</Fragment>);
}
startSourceElement = (<Fragment>
const startSourceElement = (<Fragment>
<div onClick={() => tryStart(name, type)}
className="capitalize underline cursor-pointer inline mr-1">{startText}
</div>
@@ -16,8 +16,23 @@ export const scrobblerApi = createApi({
force: params.force
}
})
}),
listenClient: builder.mutation<undefined, {
name: string,
type: string,
listening?: boolean
}>({
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;
+2 -1
View File
@@ -28,7 +28,6 @@ export interface SourceStatusData {
players: Record<string, SourcePlayerJson>
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';