Feat : STRCHA Stream status
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user