Feat : STRCHA Stream status

This commit is contained in:
jeanotx32
2026-08-11 00:52:33 +02:00
parent 6f11b72cbb
commit b529417940
13 changed files with 1038 additions and 14 deletions

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import type { AgentAction, AgentView } from '@stream-control/shared';
import type { AgentAction, AgentView, WatchState } from '@stream-control/shared';
import { formatBytes, formatPercent, formatRelative, formatTimecode } from '../format';
interface Props {
@@ -26,13 +26,16 @@ export function AgentCard({ agent, selected, onToggleSelect, onCommand, onOpenSe
const busy = pending !== null;
const obsReady = agent.online && status.obsConnected;
const watch = status.watch;
const pausedLabel = watch?.autoPaused ? 'Pause · show privé' : 'En pause';
const state = !agent.online
? { label: 'Hors-ligne', tone: 'offline' as const }
: !status.obsConnected
? { label: 'OBS déconnecté', tone: 'warn' as const }
: status.recording
? {
label: status.recordPaused ? 'En pause' : 'Enregistre',
label: status.recordPaused ? pausedLabel : 'Enregistre',
tone: status.recordPaused ? ('warn' as const) : ('rec' as const),
}
: { label: 'Prêt', tone: 'ok' as const };
@@ -65,6 +68,15 @@ export function AgentCard({ agent, selected, onToggleSelect, onCommand, onOpenSe
<p className="error small">{status.obsError}</p>
)}
{watch?.enabled && (
<WatchStrip
watch={watch}
busy={busy}
onCheck={() => void run('watch.check')}
onFullscreen={() => void run('hotkey.fullscreen')}
/>
)}
<div className="metrics">
<Metric label="Durée" value={formatTimecode(status.recordTimecode)} mono />
<Metric label="Fichier" value={formatBytes(status.recordBytes)} />
@@ -150,6 +162,56 @@ export function AgentCard({ agent, selected, onToggleSelect, onCommand, onOpenSe
);
}
const WATCH_LABELS: Record<WatchState['state'], { text: string; tone: string }> = {
public: { text: 'public', tone: 'ok' },
private: { text: 'show privé', tone: 'rec' },
offline: { text: 'hors-ligne', tone: 'offline' },
unknown: { text: 'inconnu', tone: 'offline' },
};
function WatchStrip({
watch,
busy,
onCheck,
onFullscreen,
}: {
watch: WatchState;
busy: boolean;
onCheck: () => void;
onFullscreen: () => void;
}) {
const info = WATCH_LABELS[watch.state];
const stale = watch.lastCheckedAt > 0 && Date.now() - watch.lastCheckedAt > 60_000;
return (
<div className="watch">
<div className="watch-head">
<span className={`badge ${info.tone}`}>{info.text}</span>
<span className="watch-name" title={`${watch.provider} · ${watch.username}`}>
{watch.username || '(aucun pseudo)'}
</span>
<div className="spacer" />
<button className="icon" disabled={busy} onClick={onCheck} title="Sonder maintenant">
</button>
<button className="icon" disabled={busy} onClick={onFullscreen} title="Remettre en plein écran">
</button>
</div>
{watch.lastError ? (
<span className="small error">Sonde en échec : {watch.lastError}</span>
) : (
<span className="small muted">
{watch.rawStatus ? `statut brut « ${watch.rawStatus} »` : 'pas encore sondé'}
{watch.autoPaused && ' · pause automatique active'}
{stale && ' · dernière sonde ancienne'}
</span>
)}
</div>
);
}
function Metric({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
return (
<div className="metric">