@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,251 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Profil de connexion desktop : héberger un serveur local vs rejoindre un serveur.
|
|
7
|
+
* Changer de profil relance l'application Electron.
|
|
8
|
+
*
|
|
9
|
+
* Bind LAN (0.0.0.0) : option avancée hôte uniquement — pas au boot.
|
|
10
|
+
* Accès distant produit = tunnel Cloudflare (config serveur / onboarding).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useEffect, useState } from "react";
|
|
14
|
+
import { toast } from "sonner";
|
|
15
|
+
import { Loader2, Network, Server } 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 { ServerInstanceCard } from "./server-mode-cards";
|
|
20
|
+
|
|
21
|
+
type ConnProfile = {
|
|
22
|
+
mode: "local" | "remote";
|
|
23
|
+
remoteUrl: string | null;
|
|
24
|
+
localBind: "127.0.0.1" | "0.0.0.0";
|
|
25
|
+
chosen: boolean;
|
|
26
|
+
activeBaseUrl: string | null;
|
|
27
|
+
serverPort: number | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function DesktopConnectionSettings() {
|
|
31
|
+
const [desktop, setDesktop] = useState(false);
|
|
32
|
+
const [profile, setProfile] = useState<ConnProfile | null>(null);
|
|
33
|
+
const [mode, setMode] = useState<"local" | "remote">("local");
|
|
34
|
+
const [remoteUrl, setRemoteUrl] = useState("");
|
|
35
|
+
const [bindLan, setBindLan] = useState(false);
|
|
36
|
+
const [testing, setTesting] = useState(false);
|
|
37
|
+
const [saving, setSaving] = useState(false);
|
|
38
|
+
const [testMsg, setTestMsg] = useState<string | null>(null);
|
|
39
|
+
|
|
40
|
+
const refresh = useCallback(async () => {
|
|
41
|
+
const api = getShellDesktopApi();
|
|
42
|
+
if (!api?.getConnectionProfile) return;
|
|
43
|
+
const p = await api.getConnectionProfile();
|
|
44
|
+
setProfile(p);
|
|
45
|
+
setMode(p.mode);
|
|
46
|
+
setRemoteUrl(p.remoteUrl || "");
|
|
47
|
+
setBindLan(p.localBind === "0.0.0.0");
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const api = getShellDesktopApi();
|
|
52
|
+
if (!api?.getConnectionProfile) return;
|
|
53
|
+
setDesktop(true);
|
|
54
|
+
void refresh();
|
|
55
|
+
}, [refresh]);
|
|
56
|
+
|
|
57
|
+
// Web (serveur headless) : pas de profil Héberger/Rejoindre — carte serveur.
|
|
58
|
+
if (!desktop) return <ServerInstanceCard />;
|
|
59
|
+
|
|
60
|
+
async function onTest() {
|
|
61
|
+
const api = getShellDesktopApi();
|
|
62
|
+
if (!api?.testConnection) return;
|
|
63
|
+
setTesting(true);
|
|
64
|
+
setTestMsg(null);
|
|
65
|
+
try {
|
|
66
|
+
const r = await api.testConnection(remoteUrl);
|
|
67
|
+
if (r.ok) {
|
|
68
|
+
setTestMsg(`OK — ${r.baseUrl || remoteUrl}`);
|
|
69
|
+
if (r.baseUrl) setRemoteUrl(r.baseUrl);
|
|
70
|
+
toast.success("Serveur joignable");
|
|
71
|
+
} else {
|
|
72
|
+
setTestMsg(r.error || "Échec");
|
|
73
|
+
toast.error(r.error || "Serveur injoignable");
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
const msg = e instanceof Error ? e.message : "Erreur";
|
|
77
|
+
setTestMsg(msg);
|
|
78
|
+
toast.error(msg);
|
|
79
|
+
} finally {
|
|
80
|
+
setTesting(false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function onApply() {
|
|
85
|
+
const api = getShellDesktopApi();
|
|
86
|
+
if (!api?.applyConnection) return;
|
|
87
|
+
setSaving(true);
|
|
88
|
+
try {
|
|
89
|
+
const r = await api.applyConnection({
|
|
90
|
+
mode,
|
|
91
|
+
remoteUrl: mode === "remote" ? remoteUrl : undefined,
|
|
92
|
+
localBind: mode === "local" ? (bindLan ? "0.0.0.0" : "127.0.0.1") : "127.0.0.1",
|
|
93
|
+
chosen: true,
|
|
94
|
+
});
|
|
95
|
+
if (!r.ok) {
|
|
96
|
+
toast.error(r.error || "Impossible d'appliquer");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
toast.success("Profil enregistré — redémarrage…");
|
|
100
|
+
} catch (e) {
|
|
101
|
+
toast.error(e instanceof Error ? e.message : "Erreur");
|
|
102
|
+
} finally {
|
|
103
|
+
setSaving(false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isRemoteSession = profile?.mode === "remote";
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Card>
|
|
111
|
+
<CardHeader className="pb-2">
|
|
112
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
113
|
+
<Network className="h-4 w-4" />
|
|
114
|
+
Connexion serveur
|
|
115
|
+
</CardTitle>
|
|
116
|
+
<CardDescription>
|
|
117
|
+
Héberger le serveur sur ce PC (tunnel d'accès distant inclus dans la config
|
|
118
|
+
serveur), ou rejoindre un serveur du cabinet via son URL. Un changement relance
|
|
119
|
+
l'application.
|
|
120
|
+
</CardDescription>
|
|
121
|
+
</CardHeader>
|
|
122
|
+
<CardContent className="space-y-4 text-sm">
|
|
123
|
+
{profile?.activeBaseUrl && (
|
|
124
|
+
<p className="rounded-md border bg-muted/40 px-3 py-2 text-muted-foreground">
|
|
125
|
+
Session actuelle :{" "}
|
|
126
|
+
<span className="font-medium text-foreground">{profile.activeBaseUrl}</span>
|
|
127
|
+
{profile.mode === "local" && profile.serverPort != null && (
|
|
128
|
+
<> (port {profile.serverPort})</>
|
|
129
|
+
)}
|
|
130
|
+
</p>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
onClick={() => setMode("local")}
|
|
137
|
+
className={`rounded-lg border p-3 text-left transition-colors ${
|
|
138
|
+
mode === "local"
|
|
139
|
+
? "border-sky-500 bg-sky-500/5"
|
|
140
|
+
: "border-border hover:bg-muted/40"
|
|
141
|
+
}`}
|
|
142
|
+
>
|
|
143
|
+
<div className="flex items-center gap-2 font-medium">
|
|
144
|
+
<Server className="h-4 w-4" />
|
|
145
|
+
Héberger (serveur local)
|
|
146
|
+
</div>
|
|
147
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
148
|
+
Ce poste démarre la base et l'API. L'accès distant passe par le tunnel
|
|
149
|
+
configuré avec le serveur (carte Tunnel ci-dessous).
|
|
150
|
+
</p>
|
|
151
|
+
</button>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={() => setMode("remote")}
|
|
155
|
+
className={`rounded-lg border p-3 text-left transition-colors ${
|
|
156
|
+
mode === "remote"
|
|
157
|
+
? "border-sky-500 bg-sky-500/5"
|
|
158
|
+
: "border-border hover:bg-muted/40"
|
|
159
|
+
}`}
|
|
160
|
+
>
|
|
161
|
+
<div className="flex items-center gap-2 font-medium">
|
|
162
|
+
<Network className="h-4 w-4" />
|
|
163
|
+
Rejoindre un serveur
|
|
164
|
+
</div>
|
|
165
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
166
|
+
Client léger : pas de serveur local — URL tunnel{" "}
|
|
167
|
+
<code className="text-[11px]">https://….creez.io</code> (ou LAN si
|
|
168
|
+
activé côté hôte).
|
|
169
|
+
</p>
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{mode === "local" && (
|
|
174
|
+
<details className="rounded-md border px-3 py-2 text-xs text-muted-foreground">
|
|
175
|
+
<summary className="cursor-pointer font-medium text-foreground">
|
|
176
|
+
Configuration avancée (hôte)
|
|
177
|
+
</summary>
|
|
178
|
+
<label className="mt-3 flex items-start gap-2 leading-snug">
|
|
179
|
+
<input
|
|
180
|
+
type="checkbox"
|
|
181
|
+
className="mt-0.5"
|
|
182
|
+
checked={bindLan}
|
|
183
|
+
onChange={(e) => setBindLan(e.target.checked)}
|
|
184
|
+
/>
|
|
185
|
+
<span>
|
|
186
|
+
Écouter aussi sur le réseau local (<code className="text-[11px]">0.0.0.0</code>)
|
|
187
|
+
pour un accès HTTP LAN en plus du tunnel. Autoriser le port TCP dans le
|
|
188
|
+
pare-feu Windows (profil Réseau privé). Les clients utilisent{" "}
|
|
189
|
+
<code className="text-[11px]">http://<IP-LAN>:<port></code>.
|
|
190
|
+
Par défaut produit : accès distant = tunnel uniquement (pas de choix au
|
|
191
|
+
démarrage).
|
|
192
|
+
</span>
|
|
193
|
+
</label>
|
|
194
|
+
</details>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{mode === "remote" && (
|
|
198
|
+
<div className="space-y-2">
|
|
199
|
+
<label className="text-xs text-muted-foreground" htmlFor="remote-url">
|
|
200
|
+
URL du serveur (tunnel ou LAN)
|
|
201
|
+
</label>
|
|
202
|
+
<Input
|
|
203
|
+
id="remote-url"
|
|
204
|
+
value={remoteUrl}
|
|
205
|
+
onChange={(e) => setRemoteUrl(e.target.value)}
|
|
206
|
+
placeholder="https://cabinet.creez.io ou http://192.168.1.10:18790"
|
|
207
|
+
spellCheck={false}
|
|
208
|
+
autoComplete="off"
|
|
209
|
+
/>
|
|
210
|
+
{testMsg && (
|
|
211
|
+
<p className="text-xs text-muted-foreground">{testMsg}</p>
|
|
212
|
+
)}
|
|
213
|
+
<Button
|
|
214
|
+
type="button"
|
|
215
|
+
variant="outline"
|
|
216
|
+
size="sm"
|
|
217
|
+
disabled={testing || !remoteUrl.trim()}
|
|
218
|
+
onClick={() => void onTest()}
|
|
219
|
+
>
|
|
220
|
+
{testing ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : null}
|
|
221
|
+
Tester la connexion
|
|
222
|
+
</Button>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{isRemoteSession && (
|
|
227
|
+
<p className="text-xs text-amber-700 dark:text-amber-400">
|
|
228
|
+
Mode distant : tunnel, clés IA serveur, réindexation Meili et remise à zéro
|
|
229
|
+
usine concernent uniquement le PC hôte.
|
|
230
|
+
</p>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
<Button type="button" disabled={saving} onClick={() => void onApply()}>
|
|
234
|
+
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
|
|
235
|
+
Enregistrer et redémarrer
|
|
236
|
+
</Button>
|
|
237
|
+
</CardContent>
|
|
238
|
+
</Card>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Masque les réglages réservés à l'hôte local quand on est en client distant. */
|
|
243
|
+
export function useIsRemoteDesktopClient(): boolean {
|
|
244
|
+
const [remote, setRemote] = useState(false);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const api = getShellDesktopApi();
|
|
247
|
+
if (!api?.getConnectionProfile) return;
|
|
248
|
+
void api.getConnectionProfile().then((p: { mode: string }) => setRemote(p.mode === "remote"));
|
|
249
|
+
}, []);
|
|
250
|
+
return remote;
|
|
251
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Panneau variables d’environnement pour n8n / Hermes.
|
|
7
|
+
* - Locked : visibles, non éditables (OS Creezio)
|
|
8
|
+
* - Editables + custom : persistées via embed-env:set, puis redémarrage du service
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useEffect, useState } from "react";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import { Loader2, Plus, Save, Trash2 } from "lucide-react";
|
|
14
|
+
import { Button } from "../primitives/button";
|
|
15
|
+
import { Input } from "../primitives/input";
|
|
16
|
+
import { LockedConfigField } from "./locked-config-field";
|
|
17
|
+
|
|
18
|
+
type EmbedService = "n8n" | "hermes";
|
|
19
|
+
|
|
20
|
+
type EnvVar = {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
locked: boolean;
|
|
25
|
+
value: string;
|
|
26
|
+
kind?: "string" | "boolean" | "number";
|
|
27
|
+
custom?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function DesktopEmbedEnvPanel(props: { service: EmbedService }) {
|
|
31
|
+
const [desktop, setDesktop] = useState(false);
|
|
32
|
+
const [loading, setLoading] = useState(true);
|
|
33
|
+
const [saving, setSaving] = useState(false);
|
|
34
|
+
const [locked, setLocked] = useState<EnvVar[]>([]);
|
|
35
|
+
const [editable, setEditable] = useState<EnvVar[]>([]);
|
|
36
|
+
const [customKey, setCustomKey] = useState("");
|
|
37
|
+
const [customValue, setCustomValue] = useState("");
|
|
38
|
+
|
|
39
|
+
const applyPanel = useCallback((vars: EnvVar[]) => {
|
|
40
|
+
setLocked(vars.filter((v) => v.locked));
|
|
41
|
+
setEditable(
|
|
42
|
+
vars
|
|
43
|
+
.filter((v) => !v.locked)
|
|
44
|
+
.map((v) => ({ ...v, value: v.value ?? "" })),
|
|
45
|
+
);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const refresh = useCallback(async () => {
|
|
49
|
+
const api = getShellDesktopApi();
|
|
50
|
+
if (!api?.getEmbedEnv) return;
|
|
51
|
+
setLoading(true);
|
|
52
|
+
try {
|
|
53
|
+
const panel = await api.getEmbedEnv(props.service);
|
|
54
|
+
applyPanel(panel.vars as EnvVar[]);
|
|
55
|
+
} finally {
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [applyPanel, props.service]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!getShellDesktopApi()?.getEmbedEnv) return;
|
|
62
|
+
setDesktop(true);
|
|
63
|
+
void refresh();
|
|
64
|
+
}, [refresh]);
|
|
65
|
+
|
|
66
|
+
function setEditableValue(key: string, value: string) {
|
|
67
|
+
setEditable((prev) =>
|
|
68
|
+
prev.map((v) => (v.key === key ? { ...v, value } : v)),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function removeCustom(key: string) {
|
|
73
|
+
setEditable((prev) => prev.filter((v) => !(v.custom && v.key === key)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function addCustom() {
|
|
77
|
+
const key = customKey.trim();
|
|
78
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
79
|
+
toast.error("Nom de variable invalide (A-Z, 0-9, _)");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (locked.some((v) => v.key.toUpperCase() === key.toUpperCase())) {
|
|
83
|
+
toast.error("Cette clé est verrouillée par Creezio");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (editable.some((v) => v.key.toUpperCase() === key.toUpperCase())) {
|
|
87
|
+
toast.error("Variable déjà présente");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
setEditable((prev) => [
|
|
91
|
+
...prev,
|
|
92
|
+
{
|
|
93
|
+
key,
|
|
94
|
+
label: key,
|
|
95
|
+
value: customValue,
|
|
96
|
+
locked: false,
|
|
97
|
+
custom: true,
|
|
98
|
+
kind: "string",
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
setCustomKey("");
|
|
102
|
+
setCustomValue("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function onSave() {
|
|
106
|
+
const api = getShellDesktopApi();
|
|
107
|
+
if (!api?.setEmbedEnv) return;
|
|
108
|
+
setSaving(true);
|
|
109
|
+
try {
|
|
110
|
+
const values: Record<string, string> = {};
|
|
111
|
+
for (const v of editable) {
|
|
112
|
+
const val = String(v.value ?? "").trim();
|
|
113
|
+
if (val !== "") values[v.key] = val;
|
|
114
|
+
}
|
|
115
|
+
const r = await api.setEmbedEnv(props.service, values);
|
|
116
|
+
applyPanel(r.panel.vars as EnvVar[]);
|
|
117
|
+
toast.success(r.detail || "Enregistré");
|
|
118
|
+
} catch (e) {
|
|
119
|
+
toast.error(e instanceof Error ? e.message : "Erreur enregistrement");
|
|
120
|
+
} finally {
|
|
121
|
+
setSaving(false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!desktop) return null;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-4 border-t border-slate-200 pt-4">
|
|
129
|
+
<div>
|
|
130
|
+
<div className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
131
|
+
Variables d’environnement
|
|
132
|
+
</div>
|
|
133
|
+
<p className="text-[11px] leading-snug text-slate-500">
|
|
134
|
+
Les clés OS Creezio (tunnel, ports, sandbox) sont visibles mais
|
|
135
|
+
verrouillées. Les autres sont éditables et appliquées au prochain
|
|
136
|
+
démarrage du service (redémarrage automatique à l’enregistrement).
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{loading ? (
|
|
141
|
+
<div className="inline-flex items-center gap-2 text-sm text-slate-600">
|
|
142
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" /> Chargement des
|
|
143
|
+
variables…
|
|
144
|
+
</div>
|
|
145
|
+
) : (
|
|
146
|
+
<>
|
|
147
|
+
{locked.length > 0 ? (
|
|
148
|
+
<div>
|
|
149
|
+
<div className="mb-2 text-xs font-medium text-slate-500">
|
|
150
|
+
Verrouillées (OS)
|
|
151
|
+
</div>
|
|
152
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
153
|
+
{locked.map((v) => (
|
|
154
|
+
<LockedConfigField
|
|
155
|
+
key={v.key}
|
|
156
|
+
label={`${v.label} (${v.key})`}
|
|
157
|
+
value={v.value || "—"}
|
|
158
|
+
hint={v.hint}
|
|
159
|
+
/>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
) : null}
|
|
164
|
+
|
|
165
|
+
<div>
|
|
166
|
+
<div className="mb-2 text-xs font-medium text-slate-500">
|
|
167
|
+
Éditables
|
|
168
|
+
</div>
|
|
169
|
+
<div className="space-y-2">
|
|
170
|
+
{editable.map((v) => (
|
|
171
|
+
<div
|
|
172
|
+
key={v.key}
|
|
173
|
+
className="rounded-md border border-slate-200 bg-white px-3 py-2"
|
|
174
|
+
>
|
|
175
|
+
<div className="flex items-start justify-between gap-2">
|
|
176
|
+
<div className="min-w-0">
|
|
177
|
+
<div className="text-xs font-medium text-slate-700">
|
|
178
|
+
{v.label}
|
|
179
|
+
<span className="ml-1 font-mono text-[11px] text-slate-400">
|
|
180
|
+
{v.key}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
{v.hint ? (
|
|
184
|
+
<div className="mt-0.5 text-[11px] text-slate-500">
|
|
185
|
+
{v.hint}
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
188
|
+
</div>
|
|
189
|
+
{v.custom ? (
|
|
190
|
+
<Button
|
|
191
|
+
type="button"
|
|
192
|
+
size="sm"
|
|
193
|
+
variant="ghost"
|
|
194
|
+
className="h-7 shrink-0 px-2"
|
|
195
|
+
onClick={() => removeCustom(v.key)}
|
|
196
|
+
aria-label={`Supprimer ${v.key}`}
|
|
197
|
+
>
|
|
198
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
199
|
+
</Button>
|
|
200
|
+
) : null}
|
|
201
|
+
</div>
|
|
202
|
+
<Input
|
|
203
|
+
className="mt-1.5 font-mono text-xs"
|
|
204
|
+
value={v.value}
|
|
205
|
+
onChange={(e) => setEditableValue(v.key, e.target.value)}
|
|
206
|
+
placeholder={v.kind === "boolean" ? "true | false" : ""}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div className="rounded-md border border-dashed border-slate-300 px-3 py-2">
|
|
214
|
+
<div className="mb-2 text-xs font-medium text-slate-600">
|
|
215
|
+
Ajouter une variable
|
|
216
|
+
</div>
|
|
217
|
+
<div className="flex flex-col gap-2 sm:flex-row">
|
|
218
|
+
<Input
|
|
219
|
+
className="font-mono text-xs sm:max-w-[220px]"
|
|
220
|
+
placeholder="NOM_VARIABLE"
|
|
221
|
+
value={customKey}
|
|
222
|
+
onChange={(e) => setCustomKey(e.target.value)}
|
|
223
|
+
/>
|
|
224
|
+
<Input
|
|
225
|
+
className="font-mono text-xs"
|
|
226
|
+
placeholder="valeur"
|
|
227
|
+
value={customValue}
|
|
228
|
+
onChange={(e) => setCustomValue(e.target.value)}
|
|
229
|
+
/>
|
|
230
|
+
<Button
|
|
231
|
+
type="button"
|
|
232
|
+
size="sm"
|
|
233
|
+
variant="outline"
|
|
234
|
+
onClick={addCustom}
|
|
235
|
+
>
|
|
236
|
+
<Plus className="mr-1 h-3.5 w-3.5" /> Ajouter
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<Button
|
|
242
|
+
size="sm"
|
|
243
|
+
onClick={() => void onSave()}
|
|
244
|
+
disabled={saving}
|
|
245
|
+
>
|
|
246
|
+
{saving ? (
|
|
247
|
+
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
|
|
248
|
+
) : (
|
|
249
|
+
<Save className="mr-1 h-3.5 w-3.5" />
|
|
250
|
+
)}
|
|
251
|
+
Enregistrer et redémarrer
|
|
252
|
+
</Button>
|
|
253
|
+
</>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|