0.6.6
This commit is contained in:
109
kb_app/frontend/demo_ws_manager.html
Normal file
109
kb_app/frontend/demo_ws_manager.html
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- file: kb_app/frontend/demo_ws_manager.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Khadhroony-BoBoBot — Demo Ws Manager</title>
|
||||
<link rel="stylesheet" href="sass/main.scss" />
|
||||
</head>
|
||||
|
||||
<body class="bg-body-tertiary">
|
||||
<header class="app-header">
|
||||
<nav class="navbar navbar-expand-lg h-100 py-0 bg-light text-dark">
|
||||
<div class="container my-0">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Logo" src="imgs/logo.png" class="app-logo" />
|
||||
<span class="ps-2 fs-4 fw-bold text-primary font-logo">Demo Ws Manager</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<div class="osb-scrollable pt-1 pb-4" data-simplebar>
|
||||
<div class="container vcentered sketchy-translucid py-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xxl-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h1 class="h4 mb-3">Pilotage</h1>
|
||||
<p class="text-body-secondary mb-3">
|
||||
Démo légère du <code>WsManager</code> : démarrage/arrêt groupé, pilotage par rôle et bus unifié d’événements.
|
||||
</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||
<button id="demoWsManagerStartAllButton" type="button" class="btn btn-primary">Start all</button>
|
||||
<button id="demoWsManagerStopAllButton" type="button" class="btn btn-outline-primary">Stop all</button>
|
||||
<button id="demoWsManagerRefreshButton" type="button" class="btn btn-outline-secondary">Refresh snapshot</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="demoWsManagerRoleSelect" class="form-label">Rôle</label>
|
||||
<select id="demoWsManagerRoleSelect" class="form-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||
<button id="demoWsManagerStartRoleButton" type="button" class="btn btn-primary">Start role</button>
|
||||
<button id="demoWsManagerStopRoleButton" type="button" class="btn btn-outline-primary">Stop role</button>
|
||||
</div>
|
||||
|
||||
<div class="small text-body-secondary">
|
||||
<div><strong>Managed endpoints:</strong> <span id="demoWsManagerEndpointCountText">0</span></div>
|
||||
<div><strong>Started endpoints:</strong> <span id="demoWsManagerStartedCountText">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xxl-8">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3">Snapshot</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
<th>Provider</th>
|
||||
<th>Roles</th>
|
||||
<th>State</th>
|
||||
<th>Subs.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="demoWsManagerTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h5 mb-0">Unified event log</h2>
|
||||
<button id="demoWsManagerClearLogButton" type="button" class="btn btn-outline-secondary btn-sm">Clear log</button>
|
||||
</div>
|
||||
<textarea id="demoWsManagerLogTextarea" class="form-control font-monospace" rows="18" readonly spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer bg-dark text-light">
|
||||
<div class="container h-100 d-flex align-items-center">
|
||||
<div class="row flex-grow-1 align-items-center">
|
||||
<div class="col-12 col-md-6 text-center text-small my-1 my-md-0">
|
||||
© 2026 SASEDEV — Demo Ws Manager
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="ts/demo_ws_manager.ts" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -25,6 +25,9 @@
|
||||
<button id="openDemoHttpButton" class="btn btn-outline-primary">
|
||||
Demo Http
|
||||
</button>
|
||||
<button id="openDemoWsManagerButton" type="button" class="btn btn-outline-primary">
|
||||
Demo Ws Manager
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -54,6 +57,10 @@
|
||||
Les tests PRC Http manuels sont disponibles dans la fenêtre dédiée
|
||||
<strong>Demo Http</strong>.
|
||||
</p>
|
||||
<p class="text-body-secondary mb-3">
|
||||
La démonstration légère de pilotage multi-clients est disponible dans la fenêtre
|
||||
<strong>Demo Ws Manager</strong>.
|
||||
</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button id="openDemoWsButtonSecondary" type="button" class="btn btn-primary">
|
||||
@@ -62,6 +69,9 @@
|
||||
<button id="openDemoHttpButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo Http
|
||||
</button>
|
||||
<button id="openDemoWsManagerButtonSecondary" type="button" class="btn btn-primary">
|
||||
Ouvrir Demo Ws Manager
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -268,6 +268,8 @@ async function refreshPoolSnapshot(
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
void takeoverConsole();
|
||||
debug("demo_http window loaded");
|
||||
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// restaurer l’état depuis localStorage
|
||||
@@ -432,6 +434,4 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
appendLogLine(logTextarea, "[ui] demo_http window loaded");
|
||||
await refreshPoolSnapshot(poolTableBody, logTextarea, true);
|
||||
|
||||
debug("demo_http window loaded");
|
||||
});
|
||||
@@ -240,6 +240,9 @@ function applyStatusToUi(
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
void takeoverConsole();
|
||||
|
||||
debug("demo_ws window loaded");
|
||||
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// restaurer l’état depuis localStorage
|
||||
@@ -514,6 +517,4 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
unlistenStatusEvent();
|
||||
}
|
||||
});
|
||||
|
||||
debug("demo_ws window loaded");
|
||||
});
|
||||
237
kb_app/frontend/ts/demo_ws_manager.ts
Normal file
237
kb_app/frontend/ts/demo_ws_manager.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
// file: kb_app/frontend/ts/demo_ws_manager.ts
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
type DemoWsManagerEndpointSummary = {
|
||||
name: string;
|
||||
resolvedUrl: string;
|
||||
provider: string;
|
||||
roles: string[];
|
||||
connectionState: string;
|
||||
activeSubscriptionCount: number;
|
||||
};
|
||||
|
||||
type DemoWsManagerSnapshotPayload = {
|
||||
endpointCount: number;
|
||||
startedCount: number;
|
||||
endpoints: DemoWsManagerEndpointSummary[];
|
||||
};
|
||||
|
||||
const endpointCountText = document.querySelector<HTMLSpanElement>("#demoWsManagerEndpointCountText");
|
||||
const startedCountText = document.querySelector<HTMLSpanElement>("#demoWsManagerStartedCountText");
|
||||
const roleSelect = document.querySelector<HTMLSelectElement>("#demoWsManagerRoleSelect");
|
||||
const tableBody = document.querySelector<HTMLTableSectionElement>("#demoWsManagerTableBody");
|
||||
const logTextarea = document.querySelector<HTMLTextAreaElement>("#demoWsManagerLogTextarea");
|
||||
const startAllButton = document.querySelector<HTMLButtonElement>("#demoWsManagerStartAllButton");
|
||||
const stopAllButton = document.querySelector<HTMLButtonElement>("#demoWsManagerStopAllButton");
|
||||
const refreshButton = document.querySelector<HTMLButtonElement>("#demoWsManagerRefreshButton");
|
||||
const startRoleButton = document.querySelector<HTMLButtonElement>("#demoWsManagerStartRoleButton");
|
||||
const stopRoleButton = document.querySelector<HTMLButtonElement>("#demoWsManagerStopRoleButton");
|
||||
const clearLogButton = document.querySelector<HTMLButtonElement>("#demoWsManagerClearLogButton");
|
||||
|
||||
function appendLogLine(line: string): void {
|
||||
if (!logTextarea) {
|
||||
return;
|
||||
}
|
||||
const prefix = logTextarea.value.length > 0 ? "\n" : "";
|
||||
logTextarea.value += `${prefix}${line}`;
|
||||
logTextarea.scrollTop = logTextarea.scrollHeight;
|
||||
}
|
||||
|
||||
function renderSnapshot(snapshot: DemoWsManagerSnapshotPayload): void {
|
||||
if (endpointCountText) {
|
||||
endpointCountText.textContent = String(snapshot.endpointCount);
|
||||
}
|
||||
if (startedCountText) {
|
||||
startedCountText.textContent = String(snapshot.startedCount);
|
||||
}
|
||||
if (!tableBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = "";
|
||||
|
||||
for (const endpoint of snapshot.endpoints) {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<div class="fw-semibold">${endpoint.name}</div>
|
||||
<div class="small text-body-secondary">${endpoint.resolvedUrl}</div>
|
||||
</td>
|
||||
<td>${endpoint.provider}</td>
|
||||
<td>${endpoint.roles.join(", ")}</td>
|
||||
<td>${endpoint.connectionState}</td>
|
||||
<td>${endpoint.activeSubscriptionCount}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSnapshot(): Promise<void> {
|
||||
try {
|
||||
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_get_snapshot");
|
||||
renderSnapshot(snapshot);
|
||||
appendLogLine("[ui] refreshed manager snapshot");
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] snapshot error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRoles(): Promise<void> {
|
||||
if (!roleSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
roleSelect.innerHTML = "";
|
||||
|
||||
try {
|
||||
const roles = await invoke<string[]>("demo_ws_manager_list_roles");
|
||||
for (const role of roles) {
|
||||
const option = document.createElement("option");
|
||||
option.value = role;
|
||||
option.textContent = role;
|
||||
roleSelect.appendChild(option);
|
||||
}
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] list roles error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function startAll(): Promise<void> {
|
||||
try {
|
||||
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_all");
|
||||
renderSnapshot(snapshot);
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] start all error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopAll(): Promise<void> {
|
||||
try {
|
||||
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_all");
|
||||
renderSnapshot(snapshot);
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] stop all error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function startRole(): Promise<void> {
|
||||
if (!roleSelect || roleSelect.value.trim().length === 0) {
|
||||
appendLogLine("[ui] no role selected");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_role", {
|
||||
role: roleSelect.value,
|
||||
});
|
||||
renderSnapshot(snapshot);
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] start role error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopRole(): Promise<void> {
|
||||
if (!roleSelect || roleSelect.value.trim().length === 0) {
|
||||
appendLogLine("[ui] no role selected");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_role", {
|
||||
role: roleSelect.value,
|
||||
});
|
||||
renderSnapshot(snapshot);
|
||||
} catch (error) {
|
||||
appendLogLine(`[ui] stop role error: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
void takeoverConsole();
|
||||
debug("demo_ws_manager window loaded");
|
||||
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
if (sidebarToggle) {
|
||||
// restaurer l’état depuis localStorage
|
||||
if (localStorage.getItem('sidebar-toggle') === 'true') {
|
||||
document.body.classList.add('sidenav-toggled');
|
||||
}
|
||||
|
||||
sidebarToggle.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
document.body.classList.toggle('sidenav-toggled');
|
||||
localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
const toastElList = document.querySelectorAll('.toast');
|
||||
Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
|
||||
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
|
||||
Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
|
||||
|
||||
const gobackto = location.pathname + location.search;
|
||||
|
||||
document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
|
||||
const href = a.getAttribute("href");
|
||||
if (!href) return; // pas de href => on ignore
|
||||
|
||||
const url = new URL(href, location.origin);
|
||||
url.searchParams.set("gobackto", gobackto);
|
||||
|
||||
// conserve une URL relative (path + query)
|
||||
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
|
||||
});
|
||||
|
||||
if (startAllButton) {
|
||||
startAllButton.addEventListener("click", () => {
|
||||
void startAll();
|
||||
});
|
||||
}
|
||||
if (stopAllButton) {
|
||||
stopAllButton.addEventListener("click", () => {
|
||||
void stopAll();
|
||||
});
|
||||
}
|
||||
if (refreshButton) {
|
||||
refreshButton.addEventListener("click", () => {
|
||||
void refreshSnapshot();
|
||||
});
|
||||
}
|
||||
if (startRoleButton) {
|
||||
startRoleButton.addEventListener("click", () => {
|
||||
void startRole();
|
||||
});
|
||||
}
|
||||
if (stopRoleButton) {
|
||||
stopRoleButton.addEventListener("click", () => {
|
||||
void stopRole();
|
||||
});
|
||||
}
|
||||
if (clearLogButton && logTextarea) {
|
||||
clearLogButton.addEventListener("click", () => {
|
||||
logTextarea.value = "";
|
||||
});
|
||||
}
|
||||
|
||||
await listen<string>("kb-demo-ws-manager-log", (event) => {
|
||||
appendLogLine(event.payload);
|
||||
});
|
||||
|
||||
await listen<DemoWsManagerSnapshotPayload>("kb-demo-ws-manager-snapshot", (event) => {
|
||||
renderSnapshot(event.payload);
|
||||
});
|
||||
|
||||
await loadRoles();
|
||||
await refreshSnapshot();
|
||||
});
|
||||
@@ -23,6 +23,14 @@ async function openDemoHttpWindow(): Promise<void> {
|
||||
console.error("open_demo_http_window failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function openDemoWsManagerWindow(): Promise<void> {
|
||||
try {
|
||||
await invoke("open_demo_ws_manager_window");
|
||||
} catch (error) {
|
||||
console.error("open_demo_ws_manager_window failed:", error);
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
void takeoverConsole();
|
||||
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
|
||||
@@ -64,6 +72,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
const openDemoHttpButton = document.querySelector<HTMLButtonElement>("#openDemoHttpButton");
|
||||
const openDemoHttpButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoHttpButtonSecondary");
|
||||
const openDemoWsManagerButton = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButton");
|
||||
const openDemoWsManagerButtonSecondary = document.querySelector<HTMLButtonElement>("#openDemoWsManagerButtonSecondary");
|
||||
|
||||
if (openDemoWsButton) {
|
||||
openDemoWsButton.addEventListener("click", () => {
|
||||
@@ -88,6 +98,18 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
void openDemoHttpWindow();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemoWsManagerButton) {
|
||||
openDemoWsManagerButton.addEventListener("click", () => {
|
||||
void openDemoWsManagerWindow();
|
||||
});
|
||||
}
|
||||
|
||||
if (openDemoWsManagerButtonSecondary) {
|
||||
openDemoWsManagerButtonSecondary.addEventListener("click", () => {
|
||||
void openDemoWsManagerWindow();
|
||||
});
|
||||
}
|
||||
|
||||
debug("window loaded");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user