Fix : No log in footer
All checks were successful
release / build (push) Successful in 25s
release / verify-windows (push) Successful in 1m11s

This commit is contained in:
jeanotx32
2026-08-11 23:08:59 +02:00
parent 6a45b3063f
commit a5777ad920
4 changed files with 46 additions and 22 deletions

View File

@@ -179,6 +179,12 @@ export function App() {
Streamers Streamers
{liveTargets > 0 && <span className="pill">{liveTargets}</span>} {liveTargets > 0 && <span className="pill">{liveTargets}</span>}
</button> </button>
<button
className={route === 'logs' ? 'tab active' : 'tab'}
onClick={() => setRoute('logs')}
>
Journal
</button>
</nav> </nav>
<div className="topbar-actions"> <div className="topbar-actions">
@@ -220,7 +226,9 @@ export function App() {
</div> </div>
)} )}
{route === 'streamers' ? ( {route === 'logs' ? (
<LogPanel logs={logs} />
) : route === 'streamers' ? (
<StreamersPage <StreamersPage
targets={targets} targets={targets}
agents={agents} agents={agents}
@@ -253,8 +261,6 @@ export function App() {
</main> </main>
)} )}
<LogPanel logs={logs} />
{openAgent && ( {openAgent && (
<AgentSettings <AgentSettings
agent={openAgent} agent={openAgent}

View File

@@ -1,27 +1,38 @@
import { useEffect, useRef } from 'react'; import { useMemo } from 'react';
import type { LogEntry } from '@stream-control/shared'; import type { LogEntry } from '@stream-control/shared';
import { formatTime } from '../format'; import { formatTime } from '../format';
/**
* Journal global, toutes machines confondues. Il reste la seule vue des
* évènements sans agent rattaché — veille, suppression d'un agent, tentative de
* connexion refusée — que l'historique d'une VM ne peut pas montrer.
*
* Plus récent en tête, comme l'historique par VM : en pleine page, ramener le
* défilement vers le bas à chaque nouvelle entrée arracherait la lecture.
*/
export function LogPanel({ logs }: { logs: LogEntry[] }) { export function LogPanel({ logs }: { logs: LogEntry[] }) {
const endRef = useRef<HTMLDivElement>(null); const newestFirst = useMemo(() => [...logs].reverse(), [logs]);
useEffect(() => {
endRef.current?.scrollIntoView({ block: 'end' });
}, [logs.length]);
return ( return (
<section className="logs"> <section className="logs">
<h3>Journal</h3> <header className="logs-head">
<h3>Journal</h3>
<span className="muted small">
{logs.length} entrée(s) l'historique d'une machine s'ouvre depuis sa fiche (🕘)
</span>
</header>
<div className="log-list"> <div className="log-list">
{logs.length === 0 && <p className="muted small">Aucun évènement pour le moment.</p>} {newestFirst.length === 0 && (
{logs.map((entry) => ( <p className="muted small">Aucun évènement pour le moment.</p>
)}
{newestFirst.map((entry) => (
<div key={entry.id} className={`log-line level-${entry.level}`}> <div key={entry.id} className={`log-line level-${entry.level}`}>
<span className="mono small muted">{formatTime(entry.ts)}</span> <span className="mono small muted">{formatTime(entry.ts)}</span>
<span className="log-agent">{entry.agentName ?? 'serveur'}</span> <span className="log-agent">{entry.agentName ?? 'serveur'}</span>
<span className="log-message">{entry.message}</span> <span className="log-message">{entry.message}</span>
</div> </div>
))} ))}
<div ref={endRef} />
</div> </div>
</section> </section>
); );

View File

@@ -671,15 +671,22 @@ fieldset.group > legend {
/* --- Journal --- */ /* --- Journal --- */
/* Vue pleine page depuis l'onglet Journal. Elle défile avec la page, comme la
grille d'agents et la liste de streamers — la barre du haut est collante. */
.logs { .logs {
border-top: 1px solid var(--border); flex: 1;
background: var(--panel); display: flex;
padding: 12px 18px; flex-direction: column;
gap: 8px;
padding: 18px;
}
.logs-head {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
} }
.log-list { .log-list {
max-height: 220px;
overflow-y: auto;
margin-top: 8px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export const ROUTES = ['agents', 'streamers'] as const; export const ROUTES = ['agents', 'streamers', 'logs'] as const;
export type Route = (typeof ROUTES)[number]; export type Route = (typeof ROUTES)[number];
function currentRoute(): Route { function currentRoute(): Route {
@@ -9,8 +9,8 @@ function currentRoute(): Route {
} }
/** /**
* Routage minimal par ancre — l'application n'a que deux vues, et l'ancre suffit * Routage minimal par ancre — l'application n'a qu'une poignée de vues, et
* à les rendre partageables et à survivre à un rechargement. * l'ancre suffit à les rendre partageables et à survivre à un rechargement.
*/ */
export function useHashRoute(): [Route, (route: Route) => void] { export function useHashRoute(): [Route, (route: Route) => void] {
const [route, setRoute] = useState<Route>(currentRoute); const [route, setRoute] = useState<Route>(currentRoute);