@creezio/shell-ui 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +336 -0
- package/dist/adapters/nav-shell.d.ts +54 -0
- package/dist/adapters/nav-shell.d.ts.map +1 -0
- package/dist/adapters/nav-shell.js +112 -0
- package/dist/adapters/nav-shell.js.map +1 -0
- package/dist/brand.d.ts +64 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +59 -0
- package/dist/brand.js.map +1 -0
- package/dist/core-nav.d.ts +17 -0
- package/dist/core-nav.d.ts.map +1 -0
- package/dist/core-nav.js +40 -0
- package/dist/core-nav.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-scopes.d.ts +24 -0
- package/dist/lib/api-scopes.d.ts.map +1 -0
- package/dist/lib/api-scopes.js +78 -0
- package/dist/lib/api-scopes.js.map +1 -0
- package/dist/lib/catalog-suspense-key.d.ts +3 -0
- package/dist/lib/catalog-suspense-key.d.ts.map +1 -0
- package/dist/lib/catalog-suspense-key.js +12 -0
- package/dist/lib/catalog-suspense-key.js.map +1 -0
- package/dist/lib/data-changed.d.ts +51 -0
- package/dist/lib/data-changed.d.ts.map +1 -0
- package/dist/lib/data-changed.js +227 -0
- package/dist/lib/data-changed.js.map +1 -0
- package/dist/lib/desktop-home-path.d.ts +14 -0
- package/dist/lib/desktop-home-path.d.ts.map +1 -0
- package/dist/lib/desktop-home-path.js +29 -0
- package/dist/lib/desktop-home-path.js.map +1 -0
- package/dist/lib/geo-distance.d.ts +6 -0
- package/dist/lib/geo-distance.d.ts.map +1 -0
- package/dist/lib/geo-distance.js +27 -0
- package/dist/lib/geo-distance.js.map +1 -0
- package/dist/lib/img.d.ts +2 -0
- package/dist/lib/img.d.ts.map +1 -0
- package/dist/lib/img.js +40 -0
- package/dist/lib/img.js.map +1 -0
- package/dist/lib/keepalive-eviction.d.ts +14 -0
- package/dist/lib/keepalive-eviction.d.ts.map +1 -0
- package/dist/lib/keepalive-eviction.js +44 -0
- package/dist/lib/keepalive-eviction.js.map +1 -0
- package/dist/lib/ops-track.d.ts +29 -0
- package/dist/lib/ops-track.d.ts.map +1 -0
- package/dist/lib/ops-track.js +37 -0
- package/dist/lib/ops-track.js.map +1 -0
- package/dist/lib/optimize-cover-url.d.ts +10 -0
- package/dist/lib/optimize-cover-url.d.ts.map +1 -0
- package/dist/lib/optimize-cover-url.js +80 -0
- package/dist/lib/optimize-cover-url.js.map +1 -0
- package/dist/lib/page-trails.d.ts +9 -0
- package/dist/lib/page-trails.d.ts.map +1 -0
- package/dist/lib/page-trails.js +20 -0
- package/dist/lib/page-trails.js.map +1 -0
- package/dist/lib/public-origin.d.ts +32 -0
- package/dist/lib/public-origin.d.ts.map +1 -0
- package/dist/lib/public-origin.js +125 -0
- package/dist/lib/public-origin.js.map +1 -0
- package/dist/lib/server-incident.d.ts +12 -0
- package/dist/lib/server-incident.d.ts.map +1 -0
- package/dist/lib/server-incident.js +72 -0
- package/dist/lib/server-incident.js.map +1 -0
- package/dist/lib/tab-document-url.d.ts +13 -0
- package/dist/lib/tab-document-url.d.ts.map +1 -0
- package/dist/lib/tab-document-url.js +60 -0
- package/dist/lib/tab-document-url.js.map +1 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +87 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/nav-catalog.d.ts +97 -0
- package/dist/nav-catalog.d.ts.map +1 -0
- package/dist/nav-catalog.js +282 -0
- package/dist/nav-catalog.js.map +1 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +60 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist-cjs/adapters/nav-shell.js +115 -0
- package/dist-cjs/adapters/nav-shell.js.map +1 -0
- package/dist-cjs/brand.js +66 -0
- package/dist-cjs/brand.js.map +1 -0
- package/dist-cjs/core-nav.js +43 -0
- package/dist-cjs/core-nav.js.map +1 -0
- package/dist-cjs/index.js +95 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/lib/api-scopes.js +86 -0
- package/dist-cjs/lib/api-scopes.js.map +1 -0
- package/dist-cjs/lib/catalog-suspense-key.js +15 -0
- package/dist-cjs/lib/catalog-suspense-key.js.map +1 -0
- package/dist-cjs/lib/data-changed.js +236 -0
- package/dist-cjs/lib/data-changed.js.map +1 -0
- package/dist-cjs/lib/desktop-home-path.js +34 -0
- package/dist-cjs/lib/desktop-home-path.js.map +1 -0
- package/dist-cjs/lib/geo-distance.js +30 -0
- package/dist-cjs/lib/geo-distance.js.map +1 -0
- package/dist-cjs/lib/img.js +43 -0
- package/dist-cjs/lib/img.js.map +1 -0
- package/dist-cjs/lib/keepalive-eviction.js +49 -0
- package/dist-cjs/lib/keepalive-eviction.js.map +1 -0
- package/dist-cjs/lib/ops-track.js +41 -0
- package/dist-cjs/lib/ops-track.js.map +1 -0
- package/dist-cjs/lib/optimize-cover-url.js +83 -0
- package/dist-cjs/lib/optimize-cover-url.js.map +1 -0
- package/dist-cjs/lib/page-trails.js +25 -0
- package/dist-cjs/lib/page-trails.js.map +1 -0
- package/dist-cjs/lib/public-origin.js +130 -0
- package/dist-cjs/lib/public-origin.js.map +1 -0
- package/dist-cjs/lib/server-incident.js +75 -0
- package/dist-cjs/lib/server-incident.js.map +1 -0
- package/dist-cjs/lib/tab-document-url.js +65 -0
- package/dist-cjs/lib/tab-document-url.js.map +1 -0
- package/dist-cjs/lib/utils.js +97 -0
- package/dist-cjs/lib/utils.js.map +1 -0
- package/dist-cjs/nav-catalog.js +293 -0
- package/dist-cjs/nav-catalog.js.map +1 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/registry.js +64 -0
- package/dist-cjs/registry.js.map +1 -0
- package/dist-cjs/types.js +3 -0
- package/dist-cjs/types.js.map +1 -0
- package/package.json +163 -0
- package/ui/app-error-boundary.tsx +59 -0
- package/ui/data-table.tsx +121 -0
- package/ui/desktop/auth-window-chrome.tsx +51 -0
- package/ui/desktop/desktop-bridge.tsx +87 -0
- package/ui/desktop/external-site-slot.tsx +313 -0
- package/ui/desktop/external-site-surface.ts +58 -0
- package/ui/desktop/site-link.tsx +105 -0
- package/ui/desktop/window-chrome-controls.tsx +5 -0
- package/ui/desktop-types.ts +30 -0
- package/ui/faceted-filters.tsx +119 -0
- package/ui/global-search-host.ts +20 -0
- package/ui/global-search.tsx +66 -0
- package/ui/index.ts +178 -0
- package/ui/kit.ts +30 -0
- package/ui/layout/app-shell.tsx +58 -0
- package/ui/layout/desktop-update-banner.tsx +100 -0
- package/ui/layout/entity-header.tsx +44 -0
- package/ui/layout/native-os-nav.ts +104 -0
- package/ui/layout/nav-catalog-loader.tsx +98 -0
- package/ui/layout/nav-icons.ts +67 -0
- package/ui/layout/page-chrome.tsx +90 -0
- package/ui/layout/page-toolbar-context.tsx +82 -0
- package/ui/layout/sandbox-banner.tsx +38 -0
- package/ui/layout/section-view-shell.tsx +73 -0
- package/ui/layout/sidebar-host.ts +144 -0
- package/ui/layout/sidebar.tsx +1150 -0
- package/ui/lib/ai-screencast-hub.ts +127 -0
- package/ui/lib/ai-workspace-client.ts +46 -0
- package/ui/lib/aid.ts +22 -0
- package/ui/lib/desktop-host.ts +114 -0
- package/ui/lib/fleet-tracker-client.ts +237 -0
- package/ui/lib/hermes-ui.ts +114 -0
- package/ui/lib/n8n-ui.ts +115 -0
- package/ui/lib/use-creezio-resource.tsx +121 -0
- package/ui/lib/use-shell-ui-brand.ts +22 -0
- package/ui/list-toolbar.tsx +309 -0
- package/ui/os-pages/desktop-settings-page.tsx +158 -0
- package/ui/os-pages/index.ts +8 -0
- package/ui/page-loading/entity-page-loading.tsx +59 -0
- package/ui/page-loading/list-page-loading.tsx +61 -0
- package/ui/pagination.tsx +56 -0
- package/ui/primitives/avatar.tsx +33 -0
- package/ui/primitives/badge.tsx +36 -0
- package/ui/primitives/breadcrumb.tsx +93 -0
- package/ui/primitives/button.tsx +44 -0
- package/ui/primitives/card.tsx +44 -0
- package/ui/primitives/chart.tsx +317 -0
- package/ui/primitives/command.tsx +111 -0
- package/ui/primitives/dialog.tsx +100 -0
- package/ui/primitives/dropdown-menu.tsx +187 -0
- package/ui/primitives/input.tsx +19 -0
- package/ui/primitives/label.tsx +17 -0
- package/ui/primitives/resizable.tsx +45 -0
- package/ui/primitives/scroll-area.tsx +41 -0
- package/ui/primitives/select.tsx +73 -0
- package/ui/primitives/separator.tsx +26 -0
- package/ui/primitives/sheet.tsx +90 -0
- package/ui/primitives/skeleton.tsx +7 -0
- package/ui/primitives/sonner.tsx +22 -0
- package/ui/primitives/tabs.tsx +51 -0
- package/ui/primitives/textarea.tsx +22 -0
- package/ui/primitives/tooltip.tsx +31 -0
- package/ui/pwa/client-error-reporter.tsx +62 -0
- package/ui/pwa/register-sw.tsx +50 -0
- package/ui/range-filters.tsx +174 -0
- package/ui/search/global-search-config.ts +32 -0
- package/ui/search/global-search-provider.tsx +528 -0
- package/ui/search/search-history.ts +74 -0
- package/ui/search-input.tsx +146 -0
- package/ui/settings/account-settings.tsx +140 -0
- package/ui/settings/agent-profile-settings.tsx +184 -0
- package/ui/settings/api-keys-settings.tsx +349 -0
- package/ui/settings/desktop-background-settings.tsx +128 -0
- package/ui/settings/desktop-connection-settings.tsx +251 -0
- package/ui/settings/desktop-embed-env-panel.tsx +257 -0
- package/ui/settings/desktop-fleet-telemetry-settings.tsx +269 -0
- package/ui/settings/desktop-hermes-settings.tsx +341 -0
- package/ui/settings/desktop-llm-keys.tsx +192 -0
- package/ui/settings/desktop-n8n-settings.tsx +306 -0
- package/ui/settings/desktop-tunnel.tsx +264 -0
- package/ui/settings/desktop-update-settings.tsx +175 -0
- package/ui/settings/factory-reset-settings.tsx +89 -0
- package/ui/settings/host-only-settings.tsx +62 -0
- package/ui/settings/locked-config-field.tsx +30 -0
- package/ui/settings/ops-diagnostic-settings.tsx +242 -0
- package/ui/settings/search-reindex-settings.tsx +158 -0
- package/ui/settings/server-mode-cards.tsx +801 -0
- package/ui/theme/tailwind-preset.cjs +82 -0
- package/ui/theme/theme.css +328 -0
- package/ui/workspace/ai-activity-panel-host.tsx +19 -0
- package/ui/workspace/ai-workspace-agent-host.tsx +54 -0
- package/ui/workspace/ai-workspace-banner.tsx +168 -0
- package/ui/workspace/impersonation-banner.tsx +39 -0
- package/ui/workspace/keep-alive.tsx +220 -0
- package/ui/workspace/tab-workspace-context.tsx +1172 -0
- package/ui/workspace/tab-workspace-host.ts +89 -0
- package/ui/workspace/types.ts +500 -0
- package/ui/workspace/use-location-search.tsx +50 -0
- package/ui/workspace/window-chrome-controls.tsx +116 -0
- package/ui/workspace/workspace-config.ts +47 -0
- package/ui/workspace/workspace-root.tsx +147 -0
- package/ui/workspace/workspace-shell.tsx +292 -0
- package/ui/workspace/workspace-tab-bar.tsx +572 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Diagnostic — boîte noire locale (Configuration → Système).
|
|
5
|
+
*
|
|
6
|
+
* Affiche le journal structuré du poste (décisions de boot, durées,
|
|
7
|
+
* anomalies, erreurs) lu via /api/v1/ops/events. C'est la réponse locale à
|
|
8
|
+
* « pourquoi l'app a réindexé / mis 3 minutes à démarrer ? » — sans SSH ni
|
|
9
|
+
* fichier de log à envoyer.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useCallback, useEffect, useState } from "react";
|
|
13
|
+
import { Activity, Loader2, RefreshCw } from "lucide-react";
|
|
14
|
+
import { Badge } from "../primitives/badge";
|
|
15
|
+
import { Button } from "../primitives/button";
|
|
16
|
+
import {
|
|
17
|
+
Card,
|
|
18
|
+
CardContent,
|
|
19
|
+
CardDescription,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
} from "../primitives/card";
|
|
23
|
+
|
|
24
|
+
type OpsEvent = {
|
|
25
|
+
ts: string;
|
|
26
|
+
bootId: string;
|
|
27
|
+
seq: number;
|
|
28
|
+
source: string;
|
|
29
|
+
level: "decision" | "event" | "anomaly" | "error" | "crash";
|
|
30
|
+
kind: string;
|
|
31
|
+
outcome?: string;
|
|
32
|
+
reason?: string;
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
ctx?: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type OpsBoot = {
|
|
38
|
+
bootId: string;
|
|
39
|
+
startedAt?: string;
|
|
40
|
+
durationMs?: number;
|
|
41
|
+
appVersion?: string;
|
|
42
|
+
decisions?: Record<string, { outcome?: string; reason?: string }>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type OpsPayload = {
|
|
46
|
+
available: boolean;
|
|
47
|
+
events: OpsEvent[];
|
|
48
|
+
boots: OpsBoot[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const LEVEL_STYLE: Record<OpsEvent["level"], string> = {
|
|
52
|
+
decision: "bg-indigo-50 text-indigo-700 border-indigo-200",
|
|
53
|
+
event: "bg-emerald-50 text-emerald-700 border-emerald-200",
|
|
54
|
+
anomaly: "bg-amber-50 text-amber-700 border-amber-200",
|
|
55
|
+
error: "bg-red-50 text-red-700 border-red-200",
|
|
56
|
+
crash: "bg-red-100 text-red-900 border-red-300",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const FILTERS = [
|
|
60
|
+
{ id: "", label: "Tout" },
|
|
61
|
+
{ id: "decision", label: "Décisions" },
|
|
62
|
+
{ id: "anomaly", label: "Anomalies" },
|
|
63
|
+
{ id: "error", label: "Erreurs" },
|
|
64
|
+
] as const;
|
|
65
|
+
|
|
66
|
+
function fmtDuration(ms?: number): string | null {
|
|
67
|
+
if (ms == null) return null;
|
|
68
|
+
return ms >= 10_000 ? `${Math.round(ms / 1000)}s` : `${ms}ms`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function fmtTs(ts: string): string {
|
|
72
|
+
try {
|
|
73
|
+
return new Date(ts).toLocaleString("fr-FR", {
|
|
74
|
+
day: "2-digit",
|
|
75
|
+
month: "2-digit",
|
|
76
|
+
hour: "2-digit",
|
|
77
|
+
minute: "2-digit",
|
|
78
|
+
second: "2-digit",
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return ts;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function OpsDiagnosticSettings() {
|
|
86
|
+
const [data, setData] = useState<OpsPayload | null>(null);
|
|
87
|
+
const [busy, setBusy] = useState(false);
|
|
88
|
+
const [level, setLevel] = useState<string>("");
|
|
89
|
+
|
|
90
|
+
const load = useCallback(async (lvl: string) => {
|
|
91
|
+
setBusy(true);
|
|
92
|
+
try {
|
|
93
|
+
const qs = lvl ? `&level=${lvl}` : "";
|
|
94
|
+
// Route kit canonique (owner, journal {dataDir}/ops du harness) puis
|
|
95
|
+
// route marque historique (TF2_OPS_DIR desktop).
|
|
96
|
+
let emptyKit: OpsPayload | null = null;
|
|
97
|
+
for (const base of ["/api/v1/platform/ops/events", "/api/v1/ops/events"]) {
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${base}?limit=120${qs}`, {
|
|
100
|
+
cache: "no-store",
|
|
101
|
+
});
|
|
102
|
+
if (res.ok) {
|
|
103
|
+
const payload = (await res.json()) as OpsPayload;
|
|
104
|
+
// Kit OK mais journal vide : la route marque (TF2_OPS_DIR) peut
|
|
105
|
+
// encore avoir les événements en desktop — on la tente avant de
|
|
106
|
+
// conclure.
|
|
107
|
+
if (!payload.available && base.startsWith("/api/v1/platform/")) {
|
|
108
|
+
emptyKit = payload;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
setData(payload);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
/* route absente */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (emptyKit) setData(emptyKit);
|
|
119
|
+
} finally {
|
|
120
|
+
setBusy(false);
|
|
121
|
+
}
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
void load(level);
|
|
126
|
+
}, [load, level]);
|
|
127
|
+
|
|
128
|
+
// Mode serveur pur (Docker) : pas de journal local — section masquée.
|
|
129
|
+
if (data && !data.available) return null;
|
|
130
|
+
|
|
131
|
+
const lastBoot = data?.boots?.[0];
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<Card>
|
|
135
|
+
<CardHeader>
|
|
136
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
137
|
+
<Activity className="h-4 w-4" /> Diagnostic (boîte noire)
|
|
138
|
+
</CardTitle>
|
|
139
|
+
<CardDescription>
|
|
140
|
+
Journal des décisions et événements du poste : pourquoi la recherche a
|
|
141
|
+
été réindexée, durée de chaque étape du démarrage, anomalies
|
|
142
|
+
détectées. Conservé localement (30 derniers démarrages).
|
|
143
|
+
</CardDescription>
|
|
144
|
+
</CardHeader>
|
|
145
|
+
<CardContent className="space-y-3">
|
|
146
|
+
{lastBoot ? (
|
|
147
|
+
<p className="text-xs text-slate-500">
|
|
148
|
+
Dernier démarrage : {lastBoot.startedAt ? fmtTs(lastBoot.startedAt) : "?"}
|
|
149
|
+
{lastBoot.durationMs ? ` · ${fmtDuration(lastBoot.durationMs)}` : ""}
|
|
150
|
+
{lastBoot.appVersion ? ` · v${lastBoot.appVersion}` : ""}
|
|
151
|
+
{lastBoot.decisions?.["meili.ready"] ? (
|
|
152
|
+
<>
|
|
153
|
+
{" · recherche : "}
|
|
154
|
+
<span
|
|
155
|
+
className={
|
|
156
|
+
lastBoot.decisions["meili.ready"].outcome === "skip"
|
|
157
|
+
? "text-emerald-700"
|
|
158
|
+
: "font-medium text-amber-700"
|
|
159
|
+
}
|
|
160
|
+
>
|
|
161
|
+
{lastBoot.decisions["meili.ready"].outcome === "skip"
|
|
162
|
+
? "index conservé"
|
|
163
|
+
: `réindexation (${lastBoot.decisions["meili.ready"].reason || "?"})`}
|
|
164
|
+
</span>
|
|
165
|
+
</>
|
|
166
|
+
) : null}
|
|
167
|
+
</p>
|
|
168
|
+
) : null}
|
|
169
|
+
|
|
170
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
171
|
+
{FILTERS.map((f) => (
|
|
172
|
+
<Button
|
|
173
|
+
key={f.id}
|
|
174
|
+
type="button"
|
|
175
|
+
size="sm"
|
|
176
|
+
variant={level === f.id ? "default" : "outline"}
|
|
177
|
+
onClick={() => setLevel(f.id)}
|
|
178
|
+
>
|
|
179
|
+
{f.label}
|
|
180
|
+
</Button>
|
|
181
|
+
))}
|
|
182
|
+
<Button
|
|
183
|
+
type="button"
|
|
184
|
+
size="sm"
|
|
185
|
+
variant="ghost"
|
|
186
|
+
onClick={() => void load(level)}
|
|
187
|
+
disabled={busy}
|
|
188
|
+
>
|
|
189
|
+
{busy ? (
|
|
190
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
191
|
+
) : (
|
|
192
|
+
<RefreshCw className="h-4 w-4" />
|
|
193
|
+
)}
|
|
194
|
+
</Button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div className="max-h-80 space-y-1 overflow-y-auto rounded-md border border-slate-100 p-2">
|
|
198
|
+
{(data?.events || []).map((e) => (
|
|
199
|
+
<div
|
|
200
|
+
key={`${e.bootId}-${e.seq}`}
|
|
201
|
+
className="flex items-start gap-2 rounded px-2 py-1.5 text-xs hover:bg-slate-50"
|
|
202
|
+
>
|
|
203
|
+
<Badge
|
|
204
|
+
variant="outline"
|
|
205
|
+
className={`shrink-0 ${LEVEL_STYLE[e.level] || ""}`}
|
|
206
|
+
>
|
|
207
|
+
{e.level}
|
|
208
|
+
</Badge>
|
|
209
|
+
<div className="min-w-0 flex-1">
|
|
210
|
+
<span className="font-medium text-slate-800">{e.kind}</span>
|
|
211
|
+
{e.outcome ? (
|
|
212
|
+
<span className="text-slate-600"> → {e.outcome}</span>
|
|
213
|
+
) : null}
|
|
214
|
+
{fmtDuration(e.durationMs) ? (
|
|
215
|
+
<span className="text-slate-400"> · {fmtDuration(e.durationMs)}</span>
|
|
216
|
+
) : null}
|
|
217
|
+
{e.reason ? (
|
|
218
|
+
<div className="truncate text-slate-500" title={e.reason}>
|
|
219
|
+
{e.reason}
|
|
220
|
+
</div>
|
|
221
|
+
) : null}
|
|
222
|
+
</div>
|
|
223
|
+
<span className="shrink-0 text-slate-400">
|
|
224
|
+
{fmtTs(e.ts)}
|
|
225
|
+
<span className="ml-1 text-[10px] text-slate-300">{e.source}</span>
|
|
226
|
+
</span>
|
|
227
|
+
</div>
|
|
228
|
+
))}
|
|
229
|
+
{data && data.events.length === 0 ? (
|
|
230
|
+
<p className="p-2 text-xs text-slate-400">
|
|
231
|
+
Aucun événement {level ? `de type « ${level} »` : ""} sur les
|
|
232
|
+
derniers démarrages.
|
|
233
|
+
</p>
|
|
234
|
+
) : null}
|
|
235
|
+
{!data ? (
|
|
236
|
+
<p className="p-2 text-xs text-slate-400">Chargement…</p>
|
|
237
|
+
) : null}
|
|
238
|
+
</div>
|
|
239
|
+
</CardContent>
|
|
240
|
+
</Card>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Réindexation Meili GED — sans factory-reset.
|
|
7
|
+
* Desktop uniquement (IPC search:reindex).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { Loader2, RefreshCw, Search } from "lucide-react";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
15
|
+
|
|
16
|
+
type HealthPayload = {
|
|
17
|
+
health?: { ok?: boolean; status?: string; error?: string };
|
|
18
|
+
coherence?: {
|
|
19
|
+
stale?: boolean;
|
|
20
|
+
reason?: string;
|
|
21
|
+
sql?: Record<string, number>;
|
|
22
|
+
meili?: Record<string, number>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function SearchReindexSettings() {
|
|
27
|
+
const [desktop, setDesktop] = useState(false);
|
|
28
|
+
const [busy, setBusy] = useState(false);
|
|
29
|
+
const [health, setHealth] = useState<HealthPayload | null>(null);
|
|
30
|
+
// Web serveur : la réindexation passe par la route kit owner
|
|
31
|
+
// /api/v1/platform/search/reindex (miroir HTTP de l'IPC desktop).
|
|
32
|
+
const [httpReindex, setHttpReindex] = useState(false);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setDesktop(Boolean(getShellDesktopApi()?.reindexSearch));
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const refreshHealth = useCallback(async () => {
|
|
39
|
+
// Route kit canonique (owner) d'abord, route marque historique ensuite.
|
|
40
|
+
for (const url of [
|
|
41
|
+
"/api/v1/platform/search/health",
|
|
42
|
+
"/api/v1/search/health",
|
|
43
|
+
]) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(url, { cache: "no-store" });
|
|
46
|
+
if (res.ok) {
|
|
47
|
+
setHealth((await res.json()) as HealthPayload);
|
|
48
|
+
if (url.startsWith("/api/v1/platform/")) setHttpReindex(true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
/* route absente */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
void refreshHealth();
|
|
59
|
+
}, [refreshHealth]);
|
|
60
|
+
|
|
61
|
+
if (!desktop && !health) return null;
|
|
62
|
+
|
|
63
|
+
async function onReindex() {
|
|
64
|
+
const api = getShellDesktopApi();
|
|
65
|
+
setBusy(true);
|
|
66
|
+
try {
|
|
67
|
+
if (api?.reindexSearch) {
|
|
68
|
+
const r = await api.reindexSearch();
|
|
69
|
+
if (!r.ok) {
|
|
70
|
+
toast.error(r.error || "Échec de la réindexation");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
toast.success(
|
|
74
|
+
r.ready
|
|
75
|
+
? "Recherche réindexée"
|
|
76
|
+
: `Réindexation terminée (${r.reason || "vérifiez le health"})`,
|
|
77
|
+
);
|
|
78
|
+
} else if (httpReindex) {
|
|
79
|
+
const res = await fetch("/api/v1/platform/search/reindex", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
});
|
|
82
|
+
const body = (await res.json()) as { error?: string };
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
toast.error(body.error || "Échec de la réindexation");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
toast.success("Recherche réindexée");
|
|
88
|
+
} else {
|
|
89
|
+
toast.error("Réindexation réservée au compte propriétaire");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await refreshHealth();
|
|
93
|
+
} catch (err) {
|
|
94
|
+
toast.error(err instanceof Error ? err.message : "Erreur");
|
|
95
|
+
} finally {
|
|
96
|
+
setBusy(false);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const stale = Boolean(health?.coherence?.stale);
|
|
101
|
+
const reason = health?.coherence?.reason;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Card className={stale ? "border-amber-300" : undefined}>
|
|
105
|
+
<CardHeader>
|
|
106
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
107
|
+
<Search className="h-4 w-4" /> Recherche (Meilisearch)
|
|
108
|
+
</CardTitle>
|
|
109
|
+
<CardDescription>
|
|
110
|
+
Recalcule l'index de recherche sans effacer vos données. Utile
|
|
111
|
+
si Ctrl+K ne trouve plus un élément pourtant présent en base.
|
|
112
|
+
</CardDescription>
|
|
113
|
+
</CardHeader>
|
|
114
|
+
<CardContent className="space-y-3">
|
|
115
|
+
{health?.coherence ? (
|
|
116
|
+
<p className="text-xs text-slate-500">
|
|
117
|
+
État :{" "}
|
|
118
|
+
<span className={stale ? "font-medium text-amber-700" : "text-emerald-700"}>
|
|
119
|
+
{stale ? `désynchronisé (${reason})` : "cohérent"}
|
|
120
|
+
</span>
|
|
121
|
+
{health.coherence.sql ? (
|
|
122
|
+
<>
|
|
123
|
+
{" "}
|
|
124
|
+
· SQL{" "}
|
|
125
|
+
{Object.entries(health.coherence.sql)
|
|
126
|
+
.map(([k, v]) => `${k}=${v ?? "?"}`)
|
|
127
|
+
.join(" / ")}
|
|
128
|
+
</>
|
|
129
|
+
) : null}
|
|
130
|
+
</p>
|
|
131
|
+
) : null}
|
|
132
|
+
<div className="flex flex-wrap gap-2">
|
|
133
|
+
{desktop || httpReindex ? (
|
|
134
|
+
<Button type="button" onClick={() => void onReindex()} disabled={busy}>
|
|
135
|
+
{busy ? (
|
|
136
|
+
<>
|
|
137
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Indexation…
|
|
138
|
+
</>
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
<RefreshCw className="mr-2 h-4 w-4" /> Réindexer la recherche
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
</Button>
|
|
145
|
+
) : null}
|
|
146
|
+
<Button
|
|
147
|
+
type="button"
|
|
148
|
+
variant="outline"
|
|
149
|
+
onClick={() => void refreshHealth()}
|
|
150
|
+
disabled={busy}
|
|
151
|
+
>
|
|
152
|
+
Actualiser l'état
|
|
153
|
+
</Button>
|
|
154
|
+
</div>
|
|
155
|
+
</CardContent>
|
|
156
|
+
</Card>
|
|
157
|
+
);
|
|
158
|
+
}
|