@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,264 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tunnel d'accès distant via Cloudflare — app desktop (hôte) uniquement.
|
|
7
|
+
*
|
|
8
|
+
* Expose le serveur local en HTTPS (`{slug}.creez.io`) pour :
|
|
9
|
+
* desktop « Rejoindre un serveur », navigateur, mobile / PWA.
|
|
10
|
+
* PC éteint ou tunnel arrêté = URL inaccessible.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useEffect, useState } from "react";
|
|
14
|
+
import { toast } from "sonner";
|
|
15
|
+
import { Copy, Globe, Loader2, Wifi, WifiOff } from "lucide-react";
|
|
16
|
+
import { Button } from "../primitives/button";
|
|
17
|
+
import { Input } from "../primitives/input";
|
|
18
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
19
|
+
import { ServerTunnelCard } from "./server-mode-cards";
|
|
20
|
+
|
|
21
|
+
type TunnelPublicUrls = {
|
|
22
|
+
crm: string;
|
|
23
|
+
n8n: string;
|
|
24
|
+
hermes: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type TunnelStatus = {
|
|
28
|
+
configured: boolean;
|
|
29
|
+
online: boolean;
|
|
30
|
+
slug: string | null;
|
|
31
|
+
hostname: string | null;
|
|
32
|
+
publicUrl: string | null;
|
|
33
|
+
publicUrls?: TunnelPublicUrls | null;
|
|
34
|
+
error: string | null;
|
|
35
|
+
pcMustBeOn: true;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const SLUG_RE = /^[a-z0-9]([a-z0-9-]{0,46}[a-z0-9])$/;
|
|
39
|
+
|
|
40
|
+
export function DesktopTunnel() {
|
|
41
|
+
const [desktop, setDesktop] = useState(false);
|
|
42
|
+
const [status, setStatus] = useState<TunnelStatus | null>(null);
|
|
43
|
+
const [slug, setSlug] = useState("");
|
|
44
|
+
const [checking, setChecking] = useState(false);
|
|
45
|
+
const [reserving, setReserving] = useState(false);
|
|
46
|
+
const [avail, setAvail] = useState<{ available: boolean; reason?: string } | null>(null);
|
|
47
|
+
|
|
48
|
+
const refresh = useCallback(async () => {
|
|
49
|
+
const api = getShellDesktopApi();
|
|
50
|
+
if (!api?.getTunnelStatus) return;
|
|
51
|
+
setStatus(await api.getTunnelStatus());
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const api = getShellDesktopApi();
|
|
56
|
+
if (!api?.getTunnelStatus) return;
|
|
57
|
+
setDesktop(true);
|
|
58
|
+
void refresh();
|
|
59
|
+
const t = setInterval(() => void refresh(), 5000);
|
|
60
|
+
return () => clearInterval(t);
|
|
61
|
+
}, [refresh]);
|
|
62
|
+
|
|
63
|
+
// Web (serveur headless) : URLs publiques via /api/v1/os/tunnel/status.
|
|
64
|
+
if (!desktop) return <ServerTunnelCard />;
|
|
65
|
+
|
|
66
|
+
async function checkAvailability() {
|
|
67
|
+
const api = getShellDesktopApi();
|
|
68
|
+
if (!api?.checkTunnelSlug) return;
|
|
69
|
+
const s = slug.trim().toLowerCase();
|
|
70
|
+
if (!SLUG_RE.test(s)) {
|
|
71
|
+
setAvail({ available: false, reason: "Slug invalide (a-z, 0-9, tirets, 2–48 car.)" });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setChecking(true);
|
|
75
|
+
setAvail(null);
|
|
76
|
+
try {
|
|
77
|
+
const r = await api.checkTunnelSlug(s);
|
|
78
|
+
setAvail(r);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
setAvail({ available: false, reason: e instanceof Error ? e.message : "Erreur réseau" });
|
|
81
|
+
} finally {
|
|
82
|
+
setChecking(false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function reserve() {
|
|
87
|
+
const api = getShellDesktopApi();
|
|
88
|
+
if (!api?.reserveTunnel) return;
|
|
89
|
+
const s = slug.trim().toLowerCase();
|
|
90
|
+
if (!SLUG_RE.test(s)) {
|
|
91
|
+
toast.error("Slug invalide");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setReserving(true);
|
|
95
|
+
try {
|
|
96
|
+
const r = await api.reserveTunnel(s);
|
|
97
|
+
if (!r.ok) {
|
|
98
|
+
toast.error(r.error || "Réservation impossible");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
toast.success(`Réservé : ${r.hostname}`);
|
|
102
|
+
setSlug("");
|
|
103
|
+
setAvail(null);
|
|
104
|
+
await refresh();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
toast.error(e instanceof Error ? e.message : "Erreur de réservation");
|
|
107
|
+
} finally {
|
|
108
|
+
setReserving(false);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function stopTunnel() {
|
|
113
|
+
const api = getShellDesktopApi();
|
|
114
|
+
if (!api?.stopTunnel) return;
|
|
115
|
+
try {
|
|
116
|
+
await api.stopTunnel();
|
|
117
|
+
toast.message("Tunnel arrêté (URL d’accès distant inaccessible)");
|
|
118
|
+
await refresh();
|
|
119
|
+
} catch (e) {
|
|
120
|
+
toast.error(e instanceof Error ? e.message : "Erreur");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function startTunnel() {
|
|
125
|
+
const api = getShellDesktopApi();
|
|
126
|
+
if (!api?.startTunnel) return;
|
|
127
|
+
try {
|
|
128
|
+
await api.startTunnel();
|
|
129
|
+
toast.success("Tunnel d’accès distant démarré");
|
|
130
|
+
await refresh();
|
|
131
|
+
} catch (e) {
|
|
132
|
+
toast.error(e instanceof Error ? e.message : "Erreur de démarrage");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function copyPublicUrl() {
|
|
137
|
+
const url = status?.publicUrl;
|
|
138
|
+
if (!url) return;
|
|
139
|
+
try {
|
|
140
|
+
await navigator.clipboard.writeText(url);
|
|
141
|
+
toast.success("URL copiée — à coller dans « Rejoindre un serveur » sur les autres PC");
|
|
142
|
+
} catch {
|
|
143
|
+
toast.error("Impossible de copier l’URL");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const online = status?.online ?? false;
|
|
148
|
+
const configured = status?.configured ?? false;
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Card className="mt-4">
|
|
152
|
+
<CardHeader>
|
|
153
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
154
|
+
<Globe className="h-4 w-4" /> Tunnel d'accès distant
|
|
155
|
+
</CardTitle>
|
|
156
|
+
<CardDescription>
|
|
157
|
+
Expose ce serveur en HTTPS :{" "}
|
|
158
|
+
<code className="text-xs">{"{slug}.creez.io"}</code> (CRM) et{" "}
|
|
159
|
+
<code className="text-xs">{"n8n|hermes.{slug}.creez.io"}</code>{" "}
|
|
160
|
+
(embeds). Même URL CRM pour « Rejoindre un serveur », navigateur et mobile /
|
|
161
|
+
PWA. Ce PC doit rester allumé avec le tunnel en ligne.
|
|
162
|
+
</CardDescription>
|
|
163
|
+
</CardHeader>
|
|
164
|
+
<CardContent className="space-y-3">
|
|
165
|
+
<p
|
|
166
|
+
className={
|
|
167
|
+
online
|
|
168
|
+
? "flex items-center gap-2 rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-800"
|
|
169
|
+
: "flex items-center gap-2 rounded-md border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-700"
|
|
170
|
+
}
|
|
171
|
+
>
|
|
172
|
+
{online ? <Wifi className="h-4 w-4" /> : <WifiOff className="h-4 w-4" />}
|
|
173
|
+
{configured
|
|
174
|
+
? online
|
|
175
|
+
? `En ligne — ${status?.publicUrl}`
|
|
176
|
+
: `Hors ligne — ${status?.hostname || "tunnel configuré"} (PC éteint ou tunnel arrêté)`
|
|
177
|
+
: "Aucun sous-domaine réservé"}
|
|
178
|
+
</p>
|
|
179
|
+
{configured && status?.publicUrl ? (
|
|
180
|
+
<div className="space-y-1.5 text-xs text-muted-foreground">
|
|
181
|
+
<p>
|
|
182
|
+
Sur les autres postes : Configuration → Connexion serveur →{" "}
|
|
183
|
+
<strong className="font-medium text-foreground">Rejoindre un serveur</strong>,
|
|
184
|
+
coller l'URL CRM (ou au premier lancement choisir « Rejoindre »). Les
|
|
185
|
+
embeds s'ouvrent automatiquement via leurs sous-domaines.
|
|
186
|
+
</p>
|
|
187
|
+
{status.publicUrls ? (
|
|
188
|
+
<ul className="space-y-0.5 font-mono text-[11px] text-foreground/80">
|
|
189
|
+
<li>CRM · {status.publicUrls.crm}</li>
|
|
190
|
+
<li>n8n · {status.publicUrls.n8n}</li>
|
|
191
|
+
<li>Hermes · {status.publicUrls.hermes}</li>
|
|
192
|
+
</ul>
|
|
193
|
+
) : null}
|
|
194
|
+
</div>
|
|
195
|
+
) : null}
|
|
196
|
+
{status?.error ? (
|
|
197
|
+
<p className="text-sm text-destructive">{status.error}</p>
|
|
198
|
+
) : null}
|
|
199
|
+
|
|
200
|
+
{!configured ? (
|
|
201
|
+
<div className="space-y-2">
|
|
202
|
+
<div className="flex items-center gap-2">
|
|
203
|
+
<Input
|
|
204
|
+
value={slug}
|
|
205
|
+
onChange={(e) => {
|
|
206
|
+
setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""));
|
|
207
|
+
setAvail(null);
|
|
208
|
+
}}
|
|
209
|
+
placeholder="mon-cabinet"
|
|
210
|
+
className="flex-1"
|
|
211
|
+
maxLength={48}
|
|
212
|
+
/>
|
|
213
|
+
<span className="shrink-0 text-sm text-muted-foreground">.creez.io</span>
|
|
214
|
+
</div>
|
|
215
|
+
<div className="flex flex-wrap gap-2">
|
|
216
|
+
<Button size="sm" variant="outline" disabled={checking || !slug} onClick={() => void checkAvailability()}>
|
|
217
|
+
{checking ? <Loader2 className="h-4 w-4 animate-spin" /> : "Vérifier dispo"}
|
|
218
|
+
</Button>
|
|
219
|
+
<Button
|
|
220
|
+
size="sm"
|
|
221
|
+
disabled={reserving || !avail?.available}
|
|
222
|
+
onClick={() => void reserve()}
|
|
223
|
+
>
|
|
224
|
+
{reserving ? <Loader2 className="h-4 w-4 animate-spin" /> : "Réserver & activer"}
|
|
225
|
+
</Button>
|
|
226
|
+
</div>
|
|
227
|
+
{avail ? (
|
|
228
|
+
<p className={avail.available ? "text-sm text-emerald-700" : "text-sm text-amber-800"}>
|
|
229
|
+
{avail.available
|
|
230
|
+
? `Disponible : ${slug}.creez.io`
|
|
231
|
+
: avail.reason || "Indisponible"}
|
|
232
|
+
</p>
|
|
233
|
+
) : null}
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<div className="flex flex-wrap gap-2">
|
|
237
|
+
{online ? (
|
|
238
|
+
<Button size="sm" variant="outline" onClick={() => void stopTunnel()}>
|
|
239
|
+
Arrêter le tunnel
|
|
240
|
+
</Button>
|
|
241
|
+
) : (
|
|
242
|
+
<Button size="sm" onClick={() => void startTunnel()}>
|
|
243
|
+
Démarrer le tunnel
|
|
244
|
+
</Button>
|
|
245
|
+
)}
|
|
246
|
+
{status?.publicUrl ? (
|
|
247
|
+
<>
|
|
248
|
+
<Button size="sm" variant="outline" onClick={() => void copyPublicUrl()}>
|
|
249
|
+
<Copy className="mr-1.5 h-3.5 w-3.5" />
|
|
250
|
+
Copier l'URL
|
|
251
|
+
</Button>
|
|
252
|
+
<Button size="sm" variant="outline" asChild>
|
|
253
|
+
<a href={status.publicUrl} target="_blank" rel="noreferrer">
|
|
254
|
+
Ouvrir l'URL
|
|
255
|
+
</a>
|
|
256
|
+
</Button>
|
|
257
|
+
</>
|
|
258
|
+
) : null}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</CardContent>
|
|
262
|
+
</Card>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mise à jour de l'app desktop — Configuration.
|
|
7
|
+
* Check auto côté main ; téléchargement uniquement après clic.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useEffect, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { ArrowUpCircle, Loader2, RefreshCw } from "lucide-react";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
15
|
+
import type { DesktopUpdateStatus } from "../desktop-types";
|
|
16
|
+
import { ServerVersionCard } from "./server-mode-cards";
|
|
17
|
+
|
|
18
|
+
function labelFor(status: DesktopUpdateStatus | null): string {
|
|
19
|
+
if (!status) return "…";
|
|
20
|
+
switch (status.state) {
|
|
21
|
+
case "disabled":
|
|
22
|
+
return status.error || "Auto-update désactivé";
|
|
23
|
+
case "checking":
|
|
24
|
+
return "Recherche d'une mise à jour…";
|
|
25
|
+
case "available":
|
|
26
|
+
return `Mise à jour disponible : ${status.availableVersion ?? "?"}`;
|
|
27
|
+
case "not-available":
|
|
28
|
+
return "Vous êtes à jour";
|
|
29
|
+
case "downloading":
|
|
30
|
+
return `Téléchargement… ${Math.floor(status.percent ?? 0)} %`;
|
|
31
|
+
case "ready":
|
|
32
|
+
return `Prêt à installer (${status.availableVersion ?? "?"}) — redémarrage…`;
|
|
33
|
+
case "error":
|
|
34
|
+
return status.error || "Erreur de mise à jour";
|
|
35
|
+
case "idle":
|
|
36
|
+
default:
|
|
37
|
+
return status.updateAvailable
|
|
38
|
+
? `Mise à jour disponible : ${status.availableVersion ?? "?"}`
|
|
39
|
+
: "Aucune vérification récente";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function DesktopUpdateSettings() {
|
|
44
|
+
const [desktop, setDesktop] = useState(false);
|
|
45
|
+
const [status, setStatus] = useState<DesktopUpdateStatus | null>(null);
|
|
46
|
+
const [busy, setBusy] = useState(false);
|
|
47
|
+
|
|
48
|
+
const refresh = useCallback(async () => {
|
|
49
|
+
const api = getShellDesktopApi();
|
|
50
|
+
if (!api?.getUpdateStatus) return;
|
|
51
|
+
setStatus(await api.getUpdateStatus());
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const api = getShellDesktopApi();
|
|
56
|
+
if (!api?.getUpdateStatus) return;
|
|
57
|
+
setDesktop(true);
|
|
58
|
+
void refresh();
|
|
59
|
+
const unsub = api.onUpdateChanged?.(setStatus);
|
|
60
|
+
return () => unsub?.();
|
|
61
|
+
}, [refresh]);
|
|
62
|
+
|
|
63
|
+
// Web (serveur headless) : version d'image — updates gérées par la flotte.
|
|
64
|
+
if (!desktop) return <ServerVersionCard />;
|
|
65
|
+
|
|
66
|
+
const canInstall =
|
|
67
|
+
status?.state === "available" ||
|
|
68
|
+
status?.state === "ready" ||
|
|
69
|
+
(status?.updateAvailable && status.state !== "downloading");
|
|
70
|
+
const downloading = status?.state === "downloading";
|
|
71
|
+
|
|
72
|
+
async function onCheck() {
|
|
73
|
+
const api = getShellDesktopApi();
|
|
74
|
+
if (!api?.checkForUpdates) return;
|
|
75
|
+
setBusy(true);
|
|
76
|
+
try {
|
|
77
|
+
const s = await api.checkForUpdates();
|
|
78
|
+
setStatus(s);
|
|
79
|
+
if (s.state === "not-available") toast.success("Vous êtes à jour");
|
|
80
|
+
else if (s.state === "available") {
|
|
81
|
+
toast.message(`Mise à jour ${s.availableVersion} disponible`, {
|
|
82
|
+
description: "Cliquez sur « Mettre à jour » pour l'installer.",
|
|
83
|
+
});
|
|
84
|
+
} else if (s.state === "error") toast.error(s.error || "Erreur");
|
|
85
|
+
} catch (e) {
|
|
86
|
+
toast.error(e instanceof Error ? e.message : "Erreur de vérification");
|
|
87
|
+
} finally {
|
|
88
|
+
setBusy(false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function onInstall() {
|
|
93
|
+
const api = getShellDesktopApi();
|
|
94
|
+
if (!api?.downloadAndInstallUpdate) return;
|
|
95
|
+
setBusy(true);
|
|
96
|
+
try {
|
|
97
|
+
toast.message("Téléchargement de la mise à jour…", {
|
|
98
|
+
description: "L'application redémarrera pour terminer l'installation.",
|
|
99
|
+
});
|
|
100
|
+
const s = await api.downloadAndInstallUpdate();
|
|
101
|
+
setStatus(s);
|
|
102
|
+
if (s.state === "error") toast.error(s.error || "Échec du téléchargement");
|
|
103
|
+
} catch (e) {
|
|
104
|
+
toast.error(e instanceof Error ? e.message : "Échec de la mise à jour");
|
|
105
|
+
} finally {
|
|
106
|
+
setBusy(false);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Card>
|
|
112
|
+
<CardHeader className="pb-3">
|
|
113
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
114
|
+
<ArrowUpCircle className="h-4 w-4" />
|
|
115
|
+
Mises à jour
|
|
116
|
+
</CardTitle>
|
|
117
|
+
<CardDescription>
|
|
118
|
+
Version installée {status?.currentVersion ?? "…"}. Les mises à jour
|
|
119
|
+
ne se téléchargent qu'après votre accord.
|
|
120
|
+
</CardDescription>
|
|
121
|
+
</CardHeader>
|
|
122
|
+
<CardContent className="space-y-3">
|
|
123
|
+
<p
|
|
124
|
+
className={
|
|
125
|
+
status?.state === "error"
|
|
126
|
+
? "text-sm text-red-600"
|
|
127
|
+
: status?.updateAvailable || status?.state === "available"
|
|
128
|
+
? "text-sm font-medium text-amber-700"
|
|
129
|
+
: "text-sm text-muted-foreground"
|
|
130
|
+
}
|
|
131
|
+
>
|
|
132
|
+
{labelFor(status)}
|
|
133
|
+
</p>
|
|
134
|
+
{downloading ? (
|
|
135
|
+
<div className="h-2 w-full overflow-hidden rounded bg-muted">
|
|
136
|
+
<div
|
|
137
|
+
className="h-full bg-amber-500 transition-all"
|
|
138
|
+
style={{ width: `${Math.min(100, Math.max(0, status?.percent ?? 0))}%` }}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
) : null}
|
|
142
|
+
<div className="flex flex-wrap gap-2">
|
|
143
|
+
<Button
|
|
144
|
+
type="button"
|
|
145
|
+
variant="outline"
|
|
146
|
+
size="sm"
|
|
147
|
+
disabled={busy || downloading}
|
|
148
|
+
onClick={() => void onCheck()}
|
|
149
|
+
>
|
|
150
|
+
{busy && !downloading ? (
|
|
151
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
152
|
+
) : (
|
|
153
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
154
|
+
)}
|
|
155
|
+
Vérifier
|
|
156
|
+
</Button>
|
|
157
|
+
<Button
|
|
158
|
+
type="button"
|
|
159
|
+
size="sm"
|
|
160
|
+
disabled={!canInstall || busy || downloading || status?.state === "disabled"}
|
|
161
|
+
onClick={() => void onInstall()}
|
|
162
|
+
>
|
|
163
|
+
{downloading ? (
|
|
164
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
165
|
+
) : (
|
|
166
|
+
<ArrowUpCircle className="mr-2 h-4 w-4" />
|
|
167
|
+
)}
|
|
168
|
+
Mettre à jour
|
|
169
|
+
{status?.availableVersion ? ` (${status.availableVersion})` : ""}
|
|
170
|
+
</Button>
|
|
171
|
+
</div>
|
|
172
|
+
</CardContent>
|
|
173
|
+
</Card>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Remise à zéro usine — Configuration desktop.
|
|
7
|
+
* Efface compte, DB, Meili, uploads, tunnel token, cookies CRM + sites externes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FormEvent, useEffect, useState } from "react";
|
|
11
|
+
import { AlertTriangle, Loader2 } from "lucide-react";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import { Input } from "../primitives/input";
|
|
15
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
16
|
+
import { ServerOperatorNotice } from "./server-mode-cards";
|
|
17
|
+
|
|
18
|
+
export function FactoryResetSettings() {
|
|
19
|
+
const [confirm, setConfirm] = useState("");
|
|
20
|
+
const [busy, setBusy] = useState(false);
|
|
21
|
+
const [desktop, setDesktop] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setDesktop(Boolean(getShellDesktopApi()?.factoryReset));
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// Web (serveur headless) : wipe = opération opérateur (backup + recreate).
|
|
28
|
+
if (!desktop)
|
|
29
|
+
return <ServerOperatorNotice label="la remise à zéro usine" />;
|
|
30
|
+
|
|
31
|
+
async function onSubmit(e: FormEvent) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
if (confirm.trim().toUpperCase() !== "SUPPRIMER") {
|
|
34
|
+
toast.error('Tapez SUPPRIMER pour confirmer');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const api = getShellDesktopApi();
|
|
38
|
+
if (!api?.factoryReset) return;
|
|
39
|
+
setBusy(true);
|
|
40
|
+
try {
|
|
41
|
+
const r = await api.factoryReset();
|
|
42
|
+
if (!r.ok) {
|
|
43
|
+
toast.error(r.error || "Échec de la remise à zéro");
|
|
44
|
+
setBusy(false);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
toast.success("Remise à zéro en cours…");
|
|
48
|
+
} catch (err) {
|
|
49
|
+
toast.error(err instanceof Error ? err.message : "Erreur");
|
|
50
|
+
setBusy(false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Card className="border-rose-200">
|
|
56
|
+
<CardHeader>
|
|
57
|
+
<CardTitle className="flex items-center gap-2 text-base text-rose-700">
|
|
58
|
+
<AlertTriangle className="h-4 w-4" /> Remise à zéro usine
|
|
59
|
+
</CardTitle>
|
|
60
|
+
<CardDescription>
|
|
61
|
+
Efface toutes les données locales de cet ordinateur (compte, base catalogue/métier,
|
|
62
|
+
index Meili, uploads, token tunnel, cookies CRM et sessions sites externes), puis relance
|
|
63
|
+
le wizard de premier lancement. Le DNS Cloudflare distant peut rester — vous
|
|
64
|
+
re-réservez un slug au setup.
|
|
65
|
+
</CardDescription>
|
|
66
|
+
</CardHeader>
|
|
67
|
+
<CardContent>
|
|
68
|
+
<form onSubmit={(e) => void onSubmit(e)} className="space-y-3">
|
|
69
|
+
<Input
|
|
70
|
+
type="text"
|
|
71
|
+
placeholder='Tapez SUPPRIMER'
|
|
72
|
+
value={confirm}
|
|
73
|
+
onChange={(e) => setConfirm(e.target.value)}
|
|
74
|
+
disabled={busy}
|
|
75
|
+
/>
|
|
76
|
+
<Button type="submit" variant="destructive" disabled={busy}>
|
|
77
|
+
{busy ? (
|
|
78
|
+
<>
|
|
79
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Effacement…
|
|
80
|
+
</>
|
|
81
|
+
) : (
|
|
82
|
+
"Tout effacer et repartir de zéro"
|
|
83
|
+
)}
|
|
84
|
+
</Button>
|
|
85
|
+
</form>
|
|
86
|
+
</CardContent>
|
|
87
|
+
</Card>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Affiche les enfants uniquement en mode serveur local (hôte).
|
|
5
|
+
* En client distant, tunnel / BYOK / reindex / factory-reset sont gérés par
|
|
6
|
+
* l'app Serveur : on affiche un encart explicatif (ou rien) à la place.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
10
|
+
import { ServerCog } from "lucide-react";
|
|
11
|
+
import { getShellUiBrand } from "@creezio/shell-ui";
|
|
12
|
+
import { getDesktopHostInfo } from "../lib/desktop-host";
|
|
13
|
+
|
|
14
|
+
/** Encart standard « géré par l'app Serveur » (fallback par défaut). */
|
|
15
|
+
export function HostManagedNotice({
|
|
16
|
+
label = "cette section",
|
|
17
|
+
}: {
|
|
18
|
+
label?: string;
|
|
19
|
+
}) {
|
|
20
|
+
const productName = getShellUiBrand().productName;
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex items-start gap-3 rounded-lg border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">
|
|
23
|
+
<ServerCog className="mt-0.5 h-5 w-5 shrink-0 text-slate-400" />
|
|
24
|
+
<div>
|
|
25
|
+
<p className="font-medium text-slate-700">
|
|
26
|
+
Géré par l'app {productName} Server
|
|
27
|
+
</p>
|
|
28
|
+
<p className="mt-1">
|
|
29
|
+
Vous êtes connecté à un serveur distant : {label} se configure sur le
|
|
30
|
+
poste qui héberge le serveur, pas depuis ce client.
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function HostOnlySettings({
|
|
38
|
+
children,
|
|
39
|
+
fallback,
|
|
40
|
+
}: {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
/** Affiché à la place des enfants sur un client distant (défaut : encart). */
|
|
43
|
+
fallback?: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
// null = résolution en cours : ne rien rendre (pas de flash des contrôles
|
|
46
|
+
// host-only avant le masquage, cause de rejets IPC au montage).
|
|
47
|
+
const [host, setHost] = useState<boolean | null>(null);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
let alive = true;
|
|
51
|
+
void getDesktopHostInfo().then((info) => {
|
|
52
|
+
if (alive) setHost(info.isHost);
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
alive = false;
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
if (host === null) return null;
|
|
60
|
+
if (!host) return <>{fallback ?? <HostManagedNotice />}</>;
|
|
61
|
+
return <>{children}</>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Champ de configuration visible mais non modifiable (OS Creezio pré-configuré).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Lock } from "lucide-react";
|
|
8
|
+
|
|
9
|
+
export function LockedConfigField(props: {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
hint?: string;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="rounded-md border border-slate-200 bg-slate-50 px-3 py-2">
|
|
16
|
+
<div className="flex items-center gap-1.5 text-xs font-medium text-slate-600">
|
|
17
|
+
<Lock className="h-3 w-3 shrink-0" />
|
|
18
|
+
{props.label}
|
|
19
|
+
</div>
|
|
20
|
+
<div className="mt-1 break-all font-mono text-xs text-slate-900">
|
|
21
|
+
{props.value || "—"}
|
|
22
|
+
</div>
|
|
23
|
+
{props.hint ? (
|
|
24
|
+
<div className="mt-1 text-[11px] leading-snug text-slate-500">
|
|
25
|
+
{props.hint}
|
|
26
|
+
</div>
|
|
27
|
+
) : null}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|