@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,349 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section « Clés API » des Paramètres — création / révocation des clés
|
|
5
|
+
* publiques product_live_... (Zapier / Make / n8n). La clé complète n'est
|
|
6
|
+
* montrée qu'une fois à la création ; ensuite seul le prefix est visible.
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import Link from "next/link";
|
|
10
|
+
import { toast } from "sonner";
|
|
11
|
+
import { BookOpen, Check, Copy, KeyRound, Plus, Trash2 } from "lucide-react";
|
|
12
|
+
import { Badge } from "../primitives/badge";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import {
|
|
15
|
+
Card,
|
|
16
|
+
CardContent,
|
|
17
|
+
CardDescription,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
} from "../primitives/card";
|
|
21
|
+
|
|
22
|
+
export type ApiKeyItem = {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
prefix: string;
|
|
26
|
+
scopes: string;
|
|
27
|
+
user_id?: string | null;
|
|
28
|
+
created_at: string;
|
|
29
|
+
last_used_at: string | null;
|
|
30
|
+
revoked_at: string | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type LinkableUser = { id: string; username: string; kind: "human" | "ai" };
|
|
34
|
+
|
|
35
|
+
function formatDate(value: string | null): string {
|
|
36
|
+
if (!value) return "jamais";
|
|
37
|
+
const d = new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`);
|
|
38
|
+
if (Number.isNaN(d.getTime())) return value;
|
|
39
|
+
return d.toLocaleString("fr-FR", { dateStyle: "medium", timeStyle: "short" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const inputCls =
|
|
43
|
+
"w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-100";
|
|
44
|
+
|
|
45
|
+
export function ApiKeysSettings({
|
|
46
|
+
initialKeys,
|
|
47
|
+
linkableUsers,
|
|
48
|
+
/** @deprecated → linkableUsers (compat Fidu/TF page props). */
|
|
49
|
+
users,
|
|
50
|
+
}: {
|
|
51
|
+
/** Si omis, charge GET /api/v1/api-keys au mount (page OS client). */
|
|
52
|
+
initialKeys?: ApiKeyItem[];
|
|
53
|
+
linkableUsers?: LinkableUser[];
|
|
54
|
+
users?: Array<{ id: string; username: string; role?: string; kind?: "human" | "ai" }>;
|
|
55
|
+
}) {
|
|
56
|
+
const [resolvedUsers, setResolvedUsers] = useState<LinkableUser[]>(
|
|
57
|
+
() =>
|
|
58
|
+
linkableUsers ??
|
|
59
|
+
(users || []).map((u) => ({
|
|
60
|
+
id: u.id,
|
|
61
|
+
username: u.username,
|
|
62
|
+
kind: u.kind ?? (u.role === "ai" || u.role === "agent" ? "ai" : "human"),
|
|
63
|
+
})),
|
|
64
|
+
);
|
|
65
|
+
const [keys, setKeys] = useState<ApiKeyItem[]>(initialKeys ?? []);
|
|
66
|
+
const [name, setName] = useState("");
|
|
67
|
+
const [scope, setScope] = useState("full");
|
|
68
|
+
const [linkedUser, setLinkedUser] = useState("");
|
|
69
|
+
const [busy, setBusy] = useState(false);
|
|
70
|
+
const [freshKey, setFreshKey] = useState<string | null>(null);
|
|
71
|
+
const [copied, setCopied] = useState(false);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (initialKeys) {
|
|
75
|
+
setKeys(initialKeys);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
void fetch("/api/v1/api-keys")
|
|
80
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
81
|
+
.then((data: { api_keys?: ApiKeyItem[] } | null) => {
|
|
82
|
+
if (!cancelled && Array.isArray(data?.api_keys)) setKeys(data.api_keys);
|
|
83
|
+
})
|
|
84
|
+
.catch(() => {
|
|
85
|
+
/* liste vide tant que l'API est indisponible */
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
cancelled = true;
|
|
89
|
+
};
|
|
90
|
+
}, [initialKeys]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (linkableUsers || users) {
|
|
94
|
+
setResolvedUsers(
|
|
95
|
+
linkableUsers ??
|
|
96
|
+
(users || []).map((u) => ({
|
|
97
|
+
id: u.id,
|
|
98
|
+
username: u.username,
|
|
99
|
+
kind: u.kind ?? (u.role === "ai" || u.role === "agent" ? "ai" : "human"),
|
|
100
|
+
})),
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
void fetch("/api/v1/users")
|
|
106
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
107
|
+
.then((data: { users?: Array<{ id: string; username: string; kind?: string; active?: boolean }> } | null) => {
|
|
108
|
+
if (cancelled || !Array.isArray(data?.users)) return;
|
|
109
|
+
setResolvedUsers(
|
|
110
|
+
data.users
|
|
111
|
+
.filter((u) => u.active !== false)
|
|
112
|
+
.map((u) => ({
|
|
113
|
+
id: u.id,
|
|
114
|
+
username: u.username,
|
|
115
|
+
kind: u.kind === "ai" ? "ai" : "human",
|
|
116
|
+
})),
|
|
117
|
+
);
|
|
118
|
+
})
|
|
119
|
+
.catch(() => {
|
|
120
|
+
/* owner-only endpoint ; hors droits → liste vide */
|
|
121
|
+
});
|
|
122
|
+
return () => {
|
|
123
|
+
cancelled = true;
|
|
124
|
+
};
|
|
125
|
+
}, [linkableUsers, users]);
|
|
126
|
+
|
|
127
|
+
const requiresUser = scope === "tasks:run";
|
|
128
|
+
|
|
129
|
+
async function createKey() {
|
|
130
|
+
const trimmed = name.trim();
|
|
131
|
+
if (!trimmed) {
|
|
132
|
+
toast.message("Donnez un nom à la clé (ex. « Zapier production »)");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (requiresUser && !linkedUser) {
|
|
136
|
+
toast.message("Le scope « Tâches IA » exige un utilisateur lié");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
setBusy(true);
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch("/api/v1/api-keys", {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
name: trimmed,
|
|
146
|
+
scopes: scope,
|
|
147
|
+
user_id: linkedUser || null,
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
const data = await res.json().catch(() => ({}));
|
|
151
|
+
if (!res.ok) throw new Error((data as { error?: string }).error || `Erreur ${res.status}`);
|
|
152
|
+
const created = data as { key: string; api_key: ApiKeyItem };
|
|
153
|
+
setKeys((prev) => [created.api_key, ...prev]);
|
|
154
|
+
setFreshKey(created.key);
|
|
155
|
+
setCopied(false);
|
|
156
|
+
setName("");
|
|
157
|
+
setScope("full");
|
|
158
|
+
setLinkedUser("");
|
|
159
|
+
toast.success("Clé API créée — copiez-la maintenant, elle ne sera plus affichée");
|
|
160
|
+
} catch (e) {
|
|
161
|
+
toast.error(e instanceof Error ? e.message : "Création impossible");
|
|
162
|
+
} finally {
|
|
163
|
+
setBusy(false);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function revokeKey(id: number) {
|
|
168
|
+
if (!window.confirm("Révoquer cette clé ? Les intégrations qui l'utilisent cesseront de fonctionner.")) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const res = await fetch(`/api/v1/api-keys/${id}`, { method: "DELETE" });
|
|
173
|
+
const data = await res.json().catch(() => ({}));
|
|
174
|
+
if (!res.ok) throw new Error((data as { error?: string }).error || `Erreur ${res.status}`);
|
|
175
|
+
setKeys((prev) =>
|
|
176
|
+
prev.map((k) =>
|
|
177
|
+
k.id === id ? { ...k, revoked_at: new Date().toISOString() } : k,
|
|
178
|
+
),
|
|
179
|
+
);
|
|
180
|
+
toast.success("Clé révoquée");
|
|
181
|
+
} catch (e) {
|
|
182
|
+
toast.error(e instanceof Error ? e.message : "Révocation impossible");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function copyFreshKey() {
|
|
187
|
+
if (!freshKey) return;
|
|
188
|
+
try {
|
|
189
|
+
await navigator.clipboard.writeText(freshKey);
|
|
190
|
+
setCopied(true);
|
|
191
|
+
toast.success("Clé copiée dans le presse-papiers");
|
|
192
|
+
} catch {
|
|
193
|
+
toast.error("Copie impossible — sélectionnez la clé manuellement");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Card>
|
|
199
|
+
<CardHeader className="pb-4">
|
|
200
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
201
|
+
<KeyRound className="h-4 w-4 text-sky-600" />
|
|
202
|
+
Clés API
|
|
203
|
+
</CardTitle>
|
|
204
|
+
<CardDescription>
|
|
205
|
+
Automatisez Creezio avec Zapier, Make ou n8n. Chaque clé donne un
|
|
206
|
+
accès complet à l'API (120 requêtes/minute).{" "}
|
|
207
|
+
<Link
|
|
208
|
+
href="/developers"
|
|
209
|
+
className="inline-flex items-center gap-1 font-medium text-sky-700 underline underline-offset-2 hover:text-sky-900"
|
|
210
|
+
>
|
|
211
|
+
<BookOpen className="h-3.5 w-3.5" />
|
|
212
|
+
Documentation API
|
|
213
|
+
</Link>
|
|
214
|
+
</CardDescription>
|
|
215
|
+
</CardHeader>
|
|
216
|
+
<CardContent className="space-y-5">
|
|
217
|
+
{freshKey ? (
|
|
218
|
+
<div className="rounded-xl border border-emerald-200 bg-emerald-50 p-4">
|
|
219
|
+
<p className="text-sm font-medium text-emerald-900">
|
|
220
|
+
Votre nouvelle clé — copiez-la maintenant, elle ne sera plus jamais affichée :
|
|
221
|
+
</p>
|
|
222
|
+
<div className="mt-2 flex items-center gap-2">
|
|
223
|
+
<code className="min-w-0 flex-1 truncate rounded-lg border border-emerald-200 bg-white px-3 py-2 font-mono text-[13px] text-emerald-950">
|
|
224
|
+
{freshKey}
|
|
225
|
+
</code>
|
|
226
|
+
<Button size="sm" variant="outline" onClick={copyFreshKey}>
|
|
227
|
+
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
228
|
+
{copied ? "Copiée" : "Copier"}
|
|
229
|
+
</Button>
|
|
230
|
+
</div>
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
className="mt-2 text-xs text-emerald-700 underline underline-offset-2"
|
|
234
|
+
onClick={() => setFreshKey(null)}
|
|
235
|
+
>
|
|
236
|
+
J'ai copié la clé, masquer
|
|
237
|
+
</button>
|
|
238
|
+
</div>
|
|
239
|
+
) : null}
|
|
240
|
+
|
|
241
|
+
<div className="space-y-2">
|
|
242
|
+
<div className="flex items-end gap-2">
|
|
243
|
+
<div className="min-w-0 flex-1">
|
|
244
|
+
<p className="mb-1.5 text-[13px] font-medium text-slate-700">Nouvelle clé</p>
|
|
245
|
+
<input
|
|
246
|
+
className={inputCls}
|
|
247
|
+
placeholder="Nom (ex. Zapier production)"
|
|
248
|
+
value={name}
|
|
249
|
+
maxLength={80}
|
|
250
|
+
onChange={(e) => setName(e.target.value)}
|
|
251
|
+
onKeyDown={(e) => {
|
|
252
|
+
if (e.key === "Enter") createKey();
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
<Button onClick={createKey} disabled={busy} className="shrink-0">
|
|
257
|
+
<Plus className="h-4 w-4" />
|
|
258
|
+
{busy ? "Création…" : "Créer la clé"}
|
|
259
|
+
</Button>
|
|
260
|
+
</div>
|
|
261
|
+
<div className="flex flex-wrap items-end gap-2">
|
|
262
|
+
<div className="min-w-0 flex-1">
|
|
263
|
+
<p className="mb-1.5 text-[13px] font-medium text-slate-700">Portée (scope)</p>
|
|
264
|
+
<select
|
|
265
|
+
className={inputCls}
|
|
266
|
+
value={scope}
|
|
267
|
+
onChange={(e) => setScope(e.target.value)}
|
|
268
|
+
>
|
|
269
|
+
<option value="full">Accès complet (full)</option>
|
|
270
|
+
<option value="crm:read">Lecture CRM (crm:read)</option>
|
|
271
|
+
<option value="crm:write">Lecture + écriture CRM (crm:write)</option>
|
|
272
|
+
<option value="tasks:run">Réveil des collaborateurs IA (tasks:run)</option>
|
|
273
|
+
</select>
|
|
274
|
+
</div>
|
|
275
|
+
{requiresUser ? (
|
|
276
|
+
<div className="min-w-0 flex-1">
|
|
277
|
+
<p className="mb-1.5 text-[13px] font-medium text-slate-700">
|
|
278
|
+
Agir au nom de
|
|
279
|
+
</p>
|
|
280
|
+
<select
|
|
281
|
+
className={inputCls}
|
|
282
|
+
value={linkedUser}
|
|
283
|
+
onChange={(e) => setLinkedUser(e.target.value)}
|
|
284
|
+
>
|
|
285
|
+
<option value="">— Choisir un utilisateur —</option>
|
|
286
|
+
{resolvedUsers.map((u) => (
|
|
287
|
+
<option key={u.id} value={u.id}>
|
|
288
|
+
{u.username}
|
|
289
|
+
{u.kind === "ai" ? " (IA)" : ""}
|
|
290
|
+
</option>
|
|
291
|
+
))}
|
|
292
|
+
</select>
|
|
293
|
+
</div>
|
|
294
|
+
) : null}
|
|
295
|
+
</div>
|
|
296
|
+
{requiresUser ? (
|
|
297
|
+
<p className="text-xs text-slate-500">
|
|
298
|
+
Cette clé pourra créer et suivre des tâches de collaborateurs IA au nom de
|
|
299
|
+
l'utilisateur choisi (ex. depuis ChatGPT via le MCP, ou n8n).
|
|
300
|
+
</p>
|
|
301
|
+
) : null}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{keys.length === 0 ? (
|
|
305
|
+
<p className="text-sm text-slate-500">
|
|
306
|
+
Aucune clé pour l'instant. Créez-en une pour connecter Zapier, Make ou n8n.
|
|
307
|
+
</p>
|
|
308
|
+
) : (
|
|
309
|
+
<ul className="divide-y divide-slate-100 rounded-xl border border-slate-200">
|
|
310
|
+
{keys.map((k) => (
|
|
311
|
+
<li key={k.id} className="flex items-center gap-3 px-4 py-3">
|
|
312
|
+
<div className="min-w-0 flex-1">
|
|
313
|
+
<p className="flex items-center gap-2 text-sm font-medium text-slate-900">
|
|
314
|
+
<span className="truncate">{k.name}</span>
|
|
315
|
+
{k.revoked_at ? (
|
|
316
|
+
<Badge variant="outline" className="border-red-200 bg-red-50 text-red-700">
|
|
317
|
+
Révoquée
|
|
318
|
+
</Badge>
|
|
319
|
+
) : (
|
|
320
|
+
<Badge variant="outline" className="border-emerald-200 bg-emerald-50 text-emerald-700">
|
|
321
|
+
Active
|
|
322
|
+
</Badge>
|
|
323
|
+
)}
|
|
324
|
+
</p>
|
|
325
|
+
<p className="mt-0.5 truncate text-xs text-slate-500">
|
|
326
|
+
<code className="font-mono">{k.prefix}…</code>
|
|
327
|
+
{" · "}créée le {formatDate(k.created_at)}
|
|
328
|
+
{" · "}dernière utilisation : {formatDate(k.last_used_at)}
|
|
329
|
+
</p>
|
|
330
|
+
</div>
|
|
331
|
+
{!k.revoked_at ? (
|
|
332
|
+
<Button
|
|
333
|
+
size="sm"
|
|
334
|
+
variant="ghost"
|
|
335
|
+
className="shrink-0 text-red-600 hover:bg-red-50 hover:text-red-700"
|
|
336
|
+
onClick={() => revokeKey(k.id)}
|
|
337
|
+
>
|
|
338
|
+
<Trash2 className="h-4 w-4" />
|
|
339
|
+
Révoquer
|
|
340
|
+
</Button>
|
|
341
|
+
) : null}
|
|
342
|
+
</li>
|
|
343
|
+
))}
|
|
344
|
+
</ul>
|
|
345
|
+
)}
|
|
346
|
+
</CardContent>
|
|
347
|
+
</Card>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fonctionnement en arrière-plan — desktop uniquement (IPC background:*).
|
|
7
|
+
*
|
|
8
|
+
* - « Fermer = réduire dans la zone de notification » : la fenêtre se masque,
|
|
9
|
+
* le serveur local + le bridge restent actifs → les collaborateurs IA
|
|
10
|
+
* restent joignables (MCP / API) même fenêtre fermée.
|
|
11
|
+
* - « Lancer au démarrage » : app.setLoginItemSettings (Windows / macOS).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useEffect, useState } from "react";
|
|
15
|
+
import { Power } from "lucide-react";
|
|
16
|
+
import { toast } from "sonner";
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardContent,
|
|
20
|
+
CardDescription,
|
|
21
|
+
CardHeader,
|
|
22
|
+
CardTitle,
|
|
23
|
+
} from "../primitives/card";
|
|
24
|
+
|
|
25
|
+
type BackgroundState = {
|
|
26
|
+
closeToTray: boolean;
|
|
27
|
+
launchAtStartup: boolean;
|
|
28
|
+
trayActive: boolean;
|
|
29
|
+
platform: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function DesktopBackgroundSettings() {
|
|
33
|
+
const [state, setState] = useState<BackgroundState | null>(null);
|
|
34
|
+
const [busy, setBusy] = useState(false);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const api = getShellDesktopApi();
|
|
38
|
+
if (!api?.getBackgroundSettings) return;
|
|
39
|
+
void api
|
|
40
|
+
.getBackgroundSettings()
|
|
41
|
+
.then((s: BackgroundState) => setState(s))
|
|
42
|
+
.catch(() => {});
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
// Navigateur classique ou vieux desktop sans l'IPC : section masquée.
|
|
46
|
+
if (!state) return null;
|
|
47
|
+
|
|
48
|
+
async function apply(patch: {
|
|
49
|
+
closeToTray?: boolean;
|
|
50
|
+
launchAtStartup?: boolean;
|
|
51
|
+
}) {
|
|
52
|
+
const api = getShellDesktopApi();
|
|
53
|
+
if (!api?.setBackgroundSettings) return;
|
|
54
|
+
setBusy(true);
|
|
55
|
+
try {
|
|
56
|
+
const r = await api.setBackgroundSettings(patch);
|
|
57
|
+
setState((prev) =>
|
|
58
|
+
prev
|
|
59
|
+
? { ...prev, ...r.settings, trayActive: r.trayActive }
|
|
60
|
+
: prev,
|
|
61
|
+
);
|
|
62
|
+
toast.success("Réglage enregistré");
|
|
63
|
+
} catch (err) {
|
|
64
|
+
toast.error(err instanceof Error ? err.message : "Erreur");
|
|
65
|
+
} finally {
|
|
66
|
+
setBusy(false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const launchSupported =
|
|
71
|
+
state.platform === "win32" || state.platform === "darwin";
|
|
72
|
+
const productName = getShellUiBrand().productName;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Card>
|
|
76
|
+
<CardHeader>
|
|
77
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
78
|
+
<Power className="h-4 w-4" /> Fonctionnement en arrière-plan
|
|
79
|
+
</CardTitle>
|
|
80
|
+
<CardDescription>
|
|
81
|
+
Pour que vos collaborateurs IA restent joignables (tâches, MCP, API)
|
|
82
|
+
même quand la fenêtre est fermée, {productName} peut continuer de
|
|
83
|
+
tourner dans la zone de notification.
|
|
84
|
+
</CardDescription>
|
|
85
|
+
</CardHeader>
|
|
86
|
+
<CardContent className="space-y-4 text-sm">
|
|
87
|
+
<label className="flex items-start justify-between gap-4 leading-snug">
|
|
88
|
+
<div>
|
|
89
|
+
<span className="font-medium">
|
|
90
|
+
Fermer la fenêtre = réduire dans la zone de notification
|
|
91
|
+
</span>
|
|
92
|
+
<p className="text-xs text-muted-foreground">
|
|
93
|
+
{state.trayActive
|
|
94
|
+
? `Le serveur local et les IA restent actifs ; quittez vraiment via le menu de l’icône ${productName}.`
|
|
95
|
+
: "Icône de notification indisponible sur ce système — fermer la fenêtre quittera l’application."}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
<input
|
|
99
|
+
type="checkbox"
|
|
100
|
+
className="mt-1 h-4 w-4"
|
|
101
|
+
checked={state.closeToTray}
|
|
102
|
+
disabled={busy || !state.trayActive}
|
|
103
|
+
onChange={(e) => void apply({ closeToTray: e.target.checked })}
|
|
104
|
+
/>
|
|
105
|
+
</label>
|
|
106
|
+
<label className="flex items-start justify-between gap-4 leading-snug">
|
|
107
|
+
<div>
|
|
108
|
+
<span className="font-medium">
|
|
109
|
+
Lancer {productName} au démarrage de l’ordinateur
|
|
110
|
+
</span>
|
|
111
|
+
<p className="text-xs text-muted-foreground">
|
|
112
|
+
{launchSupported
|
|
113
|
+
? "L’application démarre avec votre session — vos IA sont opérationnelles sans intervention."
|
|
114
|
+
: `Non géré automatiquement sur ce système (ajoutez ${productName} aux applications de démarrage de votre environnement).`}
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
<input
|
|
118
|
+
type="checkbox"
|
|
119
|
+
className="mt-1 h-4 w-4"
|
|
120
|
+
checked={state.launchAtStartup}
|
|
121
|
+
disabled={busy || !launchSupported}
|
|
122
|
+
onChange={(e) => void apply({ launchAtStartup: e.target.checked })}
|
|
123
|
+
/>
|
|
124
|
+
</label>
|
|
125
|
+
</CardContent>
|
|
126
|
+
</Card>
|
|
127
|
+
);
|
|
128
|
+
}
|