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
{liveTargets > 0 && <span className="pill">{liveTargets}</span>}
</button>
<button
className={route === 'logs' ? 'tab active' : 'tab'}
onClick={() => setRoute('logs')}
>
Journal
</button>
</nav>
<div className="topbar-actions">
@@ -220,7 +226,9 @@ export function App() {
</div>
)}
{route === 'streamers' ? (
{route === 'logs' ? (
<LogPanel logs={logs} />
) : route === 'streamers' ? (
<StreamersPage
targets={targets}
agents={agents}
@@ -253,8 +261,6 @@ export function App() {
</main>
)}
<LogPanel logs={logs} />
{openAgent && (
<AgentSettings
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 { 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[] }) {
const endRef = useRef<HTMLDivElement>(null);
useEffect(() => {
endRef.current?.scrollIntoView({ block: 'end' });
}, [logs.length]);
const newestFirst = useMemo(() => [...logs].reverse(), [logs]);
return (
<section className="logs">
<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">
{logs.length === 0 && <p className="muted small">Aucun évènement pour le moment.</p>}
{logs.map((entry) => (
{newestFirst.length === 0 && (
<p className="muted small">Aucun évènement pour le moment.</p>
)}
{newestFirst.map((entry) => (
<div key={entry.id} className={`log-line level-${entry.level}`}>
<span className="mono small muted">{formatTime(entry.ts)}</span>
<span className="log-agent">{entry.agentName ?? 'serveur'}</span>
<span className="log-message">{entry.message}</span>
</div>
))}
<div ref={endRef} />
</div>
</section>
);

View File

@@ -671,15 +671,22 @@ fieldset.group > legend {
/* --- 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 {
border-top: 1px solid var(--border);
background: var(--panel);
padding: 12px 18px;
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
padding: 18px;
}
.logs-head {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
}
.log-list {
max-height: 220px;
overflow-y: auto;
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 2px;

View File

@@ -1,6 +1,6 @@
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];
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
* à les rendre partageables et à survivre à un rechargement.
* Routage minimal par ancre — l'application n'a qu'une poignée de vues, et
* l'ancre suffit à les rendre partageables et à survivre à un rechargement.
*/
export function useHashRoute(): [Route, (route: Route) => void] {
const [route, setRoute] = useState<Route>(currentRoute);