|
@@ -0,0 +1,140 @@
|
|
|
|
|
+import { consoleFetchJSON } from '@openshift-console/dynamic-plugin-sdk';
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Alert,
|
|
|
|
|
+ Card,
|
|
|
|
|
+ CardBody,
|
|
|
|
|
+ CardTitle,
|
|
|
|
|
+ Spinner,
|
|
|
|
|
+} from '@patternfly/react-core';
|
|
|
|
|
+
|
|
|
|
|
+import type { FC } from 'react';
|
|
|
|
|
+import { useEffect, useState } from 'react';
|
|
|
|
|
+
|
|
|
|
|
+interface Song {
|
|
|
|
|
+ display: string;
|
|
|
|
|
+ artist: string | null;
|
|
|
|
|
+ title: string | null;
|
|
|
|
|
+ file: string | null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface MPDStatus {
|
|
|
|
|
+ available: boolean;
|
|
|
|
|
+ current: Song | null;
|
|
|
|
|
+ previous: Song[];
|
|
|
|
|
+ error: string | null;
|
|
|
|
|
+ updatedAt: string | null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const STATUS_URL =
|
|
|
|
|
+ '/api/proxy/plugin/homelab-dashboard/mpd-status/api/status';
|
|
|
|
|
+
|
|
|
|
|
+const NowPlayingCard: FC = () => {
|
|
|
|
|
+ const [status, setStatus] = useState<MPDStatus>();
|
|
|
|
|
+ const [fetchError, setFetchError] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ let stopped = false;
|
|
|
|
|
+ let timer: number | undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const poll = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = (await consoleFetchJSON(
|
|
|
|
|
+ STATUS_URL,
|
|
|
|
|
+ 'GET',
|
|
|
|
|
+ {},
|
|
|
|
|
+ 1000,
|
|
|
|
|
+ )) as MPDStatus;
|
|
|
|
|
+
|
|
|
|
|
+ if (!stopped) {
|
|
|
|
|
+ setStatus(result);
|
|
|
|
|
+ setFetchError(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ if (!stopped) {
|
|
|
|
|
+ setFetchError(true);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (!stopped) {
|
|
|
|
|
+ timer = window.setTimeout(poll, 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ void poll();
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ stopped = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (timer !== undefined) {
|
|
|
|
|
+ window.clearTimeout(timer);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardTitle>MPD Radio</CardTitle>
|
|
|
|
|
+
|
|
|
|
|
+ <CardBody>
|
|
|
|
|
+ {!status && !fetchError ? (
|
|
|
|
|
+ <Spinner size="md" />
|
|
|
|
|
+ ) : fetchError ? (
|
|
|
|
|
+ <Alert
|
|
|
|
|
+ isInline
|
|
|
|
|
+ variant="danger"
|
|
|
|
|
+ title="MPD status unavailable"
|
|
|
|
|
+ >
|
|
|
|
|
+ The MPD status service did not respond within one second.
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
|
|
+ {!status?.available && (
|
|
|
|
|
+ <Alert
|
|
|
|
|
+ isInline
|
|
|
|
|
+ variant="warning"
|
|
|
|
|
+ title="MPD unavailable"
|
|
|
|
|
+ >
|
|
|
|
|
+ {status?.error ?? 'Unable to contact MPD.'}
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {status?.available ? (
|
|
|
|
|
+ <p>
|
|
|
|
|
+ Current song:{' '}
|
|
|
|
|
+ <strong>
|
|
|
|
|
+ {status.current?.display ?? 'Nothing playing'}
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ ) : status?.current ? (
|
|
|
|
|
+ <p>
|
|
|
|
|
+ Last known song:{' '}
|
|
|
|
|
+ <strong>{status.current.display}</strong>
|
|
|
|
|
+ </p>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+
|
|
|
|
|
+ {status?.previous && status.previous.length > 0 && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <br />
|
|
|
|
|
+
|
|
|
|
|
+ <p>
|
|
|
|
|
+ <strong>Previous:</strong>
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ <ul>
|
|
|
|
|
+ {status.previous.map((song, index) => (
|
|
|
|
|
+ <li key={`${song.display}-${index}`}>
|
|
|
|
|
+ {song.display}
|
|
|
|
|
+ </li>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardBody>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default NowPlayingCard;
|