@imenam/self-register-proxy 1.0.37
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/LICENSE +15 -0
- package/README.md +510 -0
- package/config.js +31 -0
- package/logger.js +88 -0
- package/meta-config.js +336 -0
- package/package.json +71 -0
- package/proxy-client.js +203 -0
- package/proxy.js +169 -0
- package/routes/dashboard.js +152 -0
- package/routes/metaConfig.js +180 -0
- package/routes/registration.js +227 -0
- package/routes/terminal.js +68 -0
- package/services/auth.js +151 -0
- package/services/healthCheck.js +95 -0
- package/services/portManager.js +82 -0
- package/services/proxyRouter.js +99 -0
- package/services/registry.js +41 -0
- package/terminal-server.js +154 -0
- package/views/dashboard.js +688 -0
- package/views/login.js +130 -0
- package/views/metaConfigs.js +643 -0
- package/views/terminal.html +1029 -0
- package/views/terminal.js +30 -0
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{PROXY_NAME}} - Agent</title>
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css">
|
|
8
|
+
<style>
|
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
|
+
html, body { height: 100%; overflow: hidden; background: #1e1e1e; }
|
|
11
|
+
.terminal-header {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 1rem;
|
|
15
|
+
padding: 0.5rem 1rem;
|
|
16
|
+
background: #1a1a2e;
|
|
17
|
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
18
|
+
color: #e4e4e7;
|
|
19
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
20
|
+
font-size: 0.85rem;
|
|
21
|
+
}
|
|
22
|
+
.back-link {
|
|
23
|
+
color: #8b5cf6;
|
|
24
|
+
text-decoration: none;
|
|
25
|
+
font-weight: 500;
|
|
26
|
+
}
|
|
27
|
+
.back-link:hover {
|
|
28
|
+
text-decoration: underline;
|
|
29
|
+
}
|
|
30
|
+
.terminal-title {
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
color: #f4f4f5;
|
|
33
|
+
}
|
|
34
|
+
.terminal-command {
|
|
35
|
+
color: #10b981;
|
|
36
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
37
|
+
font-size: 0.75rem;
|
|
38
|
+
margin-left: auto;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
.layout-body {
|
|
44
|
+
display: flex;
|
|
45
|
+
height: calc(100vh - 41px);
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
position: relative;
|
|
48
|
+
}
|
|
49
|
+
#terminal-container {
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
/* --- Sidebar méta-config --- */
|
|
54
|
+
.meta-sidebar {
|
|
55
|
+
width: 240px;
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
background: #12121f;
|
|
58
|
+
border-right: 1px solid rgba(255,255,255,0.08);
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
transition: width 0.2s ease;
|
|
63
|
+
}
|
|
64
|
+
.meta-sidebar.collapsed { width: 0; }
|
|
65
|
+
.sidebar-header {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
padding: 0.5rem 0.75rem;
|
|
70
|
+
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
}
|
|
73
|
+
.sidebar-title { font-size: 0.8rem; font-weight: 600; color: #a1a1aa; white-space: nowrap; }
|
|
74
|
+
.sidebar-collapse-btn {
|
|
75
|
+
background: transparent; border: none; color: #71717a;
|
|
76
|
+
cursor: pointer; font-size: 1.1rem; line-height: 1; padding: 0.1rem 0.3rem;
|
|
77
|
+
}
|
|
78
|
+
.sidebar-collapse-btn:hover { color: #e4e4e7; }
|
|
79
|
+
.sidebar-expand-btn {
|
|
80
|
+
position: absolute; left: 0; top: 50%;
|
|
81
|
+
transform: translateY(-50%);
|
|
82
|
+
background: #12121f; border: 1px solid rgba(255,255,255,0.1);
|
|
83
|
+
border-left: none; border-radius: 0 6px 6px 0;
|
|
84
|
+
color: #71717a; cursor: pointer;
|
|
85
|
+
padding: 0.5rem 0.3rem; z-index: 10;
|
|
86
|
+
display: none; font-size: 1rem; line-height: 1;
|
|
87
|
+
}
|
|
88
|
+
.sidebar-expand-btn:hover { color: #e4e4e7; }
|
|
89
|
+
.sidebar-sync-warning {
|
|
90
|
+
font-size: 0.7rem; color: #fbbf24;
|
|
91
|
+
background: rgba(245,158,11,0.12);
|
|
92
|
+
padding: 0.3rem 0.75rem;
|
|
93
|
+
border-bottom: 1px solid rgba(245,158,11,0.3);
|
|
94
|
+
white-space: nowrap; overflow: hidden; flex-shrink: 0;
|
|
95
|
+
}
|
|
96
|
+
.sidebar-toolbar {
|
|
97
|
+
display: flex; gap: 0.4rem; padding: 0.5rem 0.6rem;
|
|
98
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
}
|
|
101
|
+
.sb-btn {
|
|
102
|
+
border: none; border-radius: 5px; padding: 0.3rem 0.6rem;
|
|
103
|
+
font-size: 0.75rem; font-weight: 600; cursor: pointer; color: white;
|
|
104
|
+
transition: opacity 0.2s; white-space: nowrap;
|
|
105
|
+
}
|
|
106
|
+
.sb-btn:hover { opacity: 0.85; }
|
|
107
|
+
.sb-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
108
|
+
.sb-btn-primary { background: linear-gradient(90deg, #6366f1, #8b5cf6); }
|
|
109
|
+
.sb-btn-amber { background: linear-gradient(90deg, #f59e0b, #d97706); }
|
|
110
|
+
.sb-btn-green { background: linear-gradient(90deg, #10b981, #059669); }
|
|
111
|
+
.sb-btn-red { background: rgba(239,68,68,0.8); }
|
|
112
|
+
.sb-btn-ghost { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); }
|
|
113
|
+
.sidebar-configs {
|
|
114
|
+
flex: 1; overflow-y: auto; padding: 0.5rem;
|
|
115
|
+
display: flex; flex-direction: column; gap: 0.4rem;
|
|
116
|
+
}
|
|
117
|
+
.sidebar-empty { font-size: 0.75rem; color: #52525b; text-align: center; padding: 1rem 0; }
|
|
118
|
+
.sb-config-card {
|
|
119
|
+
background: rgba(255,255,255,0.05);
|
|
120
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
121
|
+
border-left: 3px solid transparent;
|
|
122
|
+
border-radius: 6px; padding: 0.45rem 0.6rem;
|
|
123
|
+
}
|
|
124
|
+
.sb-config-card.active {
|
|
125
|
+
border-color: rgba(16,185,129,0.4);
|
|
126
|
+
border-left-color: #10b981;
|
|
127
|
+
background: rgba(16,185,129,0.08);
|
|
128
|
+
}
|
|
129
|
+
.sb-config-row {
|
|
130
|
+
display: flex; align-items: center; gap: 0.4rem;
|
|
131
|
+
}
|
|
132
|
+
.sb-config-name {
|
|
133
|
+
flex: 1; font-size: 0.8rem; font-weight: 600; color: #f4f4f5;
|
|
134
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
135
|
+
}
|
|
136
|
+
.sb-config-actions { display: flex; gap: 0.25rem; flex-shrink: 0; }
|
|
137
|
+
.sb-icon-btn {
|
|
138
|
+
border: none; border-radius: 4px; width: 24px; height: 24px;
|
|
139
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
140
|
+
font-size: 0.75rem; cursor: pointer; color: white; transition: opacity 0.2s;
|
|
141
|
+
flex-shrink: 0; padding: 0;
|
|
142
|
+
}
|
|
143
|
+
.sb-icon-btn:hover { opacity: 0.8; }
|
|
144
|
+
.sb-icon-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
|
145
|
+
.sb-icon-btn-green { background: linear-gradient(135deg, #10b981, #059669); }
|
|
146
|
+
.sb-icon-btn-ghost { background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.15); }
|
|
147
|
+
.sb-icon-btn-red { background: rgba(239,68,68,0.75); }
|
|
148
|
+
.sb-icon-btn-blue { background: rgba(99,102,241,0.75); }
|
|
149
|
+
.sb-icon-btn-amber { background: rgba(245,158,11,0.75); }
|
|
150
|
+
.sb-icon-btn-purple { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
|
|
151
|
+
.sb-modal-fullscreen {
|
|
152
|
+
width: calc(100vw - 6rem); height: calc(100vh - 2rem);
|
|
153
|
+
background: #0f0f1a;
|
|
154
|
+
border-radius: 10px; padding: 0;
|
|
155
|
+
border: 1px solid rgba(139,92,246,0.4);
|
|
156
|
+
box-shadow: 0 0 0 4px rgba(99,102,241,0.15), 0 24px 48px rgba(0,0,0,0.6);
|
|
157
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
158
|
+
}
|
|
159
|
+
.sb-modal-fullscreen-bar {
|
|
160
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
161
|
+
padding: 0.5rem 1rem;
|
|
162
|
+
background: rgba(255,255,255,0.04);
|
|
163
|
+
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
164
|
+
flex-shrink: 0;
|
|
165
|
+
}
|
|
166
|
+
.sb-modal-fullscreen-bar span { font-size: 0.85rem; font-weight: 600; color: #d4d4d8; }
|
|
167
|
+
.sb-modal-fullscreen iframe { flex: 1; border: none; width: 100%; background: #fff; }
|
|
168
|
+
.sb-section-label {
|
|
169
|
+
font-size: 0.65rem; font-weight: 700; letter-spacing: 0.08em;
|
|
170
|
+
color: #52525b; text-transform: uppercase;
|
|
171
|
+
padding: 0.6rem 0.2rem 0.3rem; margin-top: 0.2rem;
|
|
172
|
+
border-top: 1px solid rgba(255,255,255,0.06);
|
|
173
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
174
|
+
}
|
|
175
|
+
.sb-refresh-btn {
|
|
176
|
+
background: transparent; border: none; color: #52525b;
|
|
177
|
+
cursor: pointer; font-size: 0.85rem; line-height: 1; padding: 0.1rem;
|
|
178
|
+
transition: color 0.2s, transform 0.3s;
|
|
179
|
+
}
|
|
180
|
+
.sb-refresh-btn:hover { color: #a1a1aa; }
|
|
181
|
+
.sb-refresh-btn.spinning { animation: spin 0.6s linear infinite; }
|
|
182
|
+
.sb-service-card {
|
|
183
|
+
display: flex; align-items: center; gap: 0.4rem;
|
|
184
|
+
padding: 0.35rem 0.5rem;
|
|
185
|
+
background: rgba(255,255,255,0.03);
|
|
186
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
187
|
+
border-radius: 5px;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
transition: background 0.15s, border-color 0.15s;
|
|
190
|
+
}
|
|
191
|
+
.sb-service-card:hover {
|
|
192
|
+
background: rgba(255,255,255,0.06);
|
|
193
|
+
border-color: rgba(139,92,246,0.3);
|
|
194
|
+
}
|
|
195
|
+
.sb-service-name {
|
|
196
|
+
flex: 1; font-size: 0.78rem; font-weight: 500; color: #d4d4d8;
|
|
197
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
198
|
+
}
|
|
199
|
+
.sb-service-actions { display: flex; gap: 0.25rem; flex-shrink: 0; }
|
|
200
|
+
/* Modal méta-config (partagée) */
|
|
201
|
+
.sb-modal-overlay {
|
|
202
|
+
display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
203
|
+
background: rgba(0,0,0,0.6);
|
|
204
|
+
z-index: 1000; align-items: center; justify-content: center;
|
|
205
|
+
}
|
|
206
|
+
.sb-modal-overlay.active { display: flex; }
|
|
207
|
+
.sb-modal {
|
|
208
|
+
background: linear-gradient(135deg, #1e1e3f 0%, #1a1a2e 100%);
|
|
209
|
+
border: 1px solid rgba(255,255,255,0.15); border-radius: 12px;
|
|
210
|
+
padding: 1.5rem; width: 90%; max-width: 480px;
|
|
211
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.4); max-height: 90vh; overflow-y: auto;
|
|
212
|
+
}
|
|
213
|
+
.sb-modal-header {
|
|
214
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
215
|
+
margin-bottom: 1rem;
|
|
216
|
+
}
|
|
217
|
+
.sb-modal-header h2 { font-size: 1.1rem; color: #f4f4f5; }
|
|
218
|
+
.sb-modal-close {
|
|
219
|
+
background: transparent; border: none; color: #71717a;
|
|
220
|
+
font-size: 1.25rem; cursor: pointer; padding: 0.25rem; line-height: 1;
|
|
221
|
+
}
|
|
222
|
+
.sb-modal-close:hover { color: #e4e4e7; }
|
|
223
|
+
.sb-form-group { margin-bottom: 0.75rem; }
|
|
224
|
+
.sb-form-group label { font-size: 0.75rem; color: #a1a1aa; margin-bottom: 0.25rem; display: block; }
|
|
225
|
+
.sb-form-group input, .sb-form-group textarea {
|
|
226
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1);
|
|
227
|
+
padding: 0.5rem 0.75rem; border-radius: 6px; color: white;
|
|
228
|
+
font-family: inherit; font-size: 0.9rem; width: 100%;
|
|
229
|
+
}
|
|
230
|
+
.sb-form-group textarea { resize: vertical; min-height: 60px; }
|
|
231
|
+
.sb-form-group input:focus, .sb-form-group textarea:focus { outline: none; border-color: #8b5cf6; }
|
|
232
|
+
.sb-modal-mappings { margin-top: 0.75rem; }
|
|
233
|
+
.sb-modal-mappings-title { font-size: 0.8rem; font-weight: 600; color: #a1a1aa; margin-bottom: 0.5rem; }
|
|
234
|
+
.sb-modal-mappings-list { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
235
|
+
.sb-modal-mapping-item {
|
|
236
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
237
|
+
background: rgba(0,0,0,0.2); padding: 0.4rem 0.6rem; border-radius: 6px;
|
|
238
|
+
}
|
|
239
|
+
.sb-modal-mapping-item .mcp-label { flex: 1; font-size: 0.8rem; color: #f4f4f5; font-weight: 500; }
|
|
240
|
+
.sb-modal-mapping-item .mcp-path-label { font-size: 0.7rem; color: #8b5cf6; font-family: 'SF Mono', 'Fira Code', monospace; }
|
|
241
|
+
.sb-modal-mapping-item select {
|
|
242
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
|
|
243
|
+
color: #e4e4e7; padding: 0.3rem 0.5rem; border-radius: 4px; font-size: 0.8rem; min-width: 130px;
|
|
244
|
+
}
|
|
245
|
+
.sb-modal-mapping-item select:focus { outline: none; border-color: #8b5cf6; }
|
|
246
|
+
.sb-spinner {
|
|
247
|
+
display: inline-block; width: 12px; height: 12px;
|
|
248
|
+
border: 2px solid rgba(255,255,255,0.2); border-top-color: #8b5cf6;
|
|
249
|
+
border-radius: 50%; animation: spin 0.6s linear infinite;
|
|
250
|
+
}
|
|
251
|
+
.prompt-btn {
|
|
252
|
+
background: linear-gradient(90deg, #6366f1, #8b5cf6);
|
|
253
|
+
color: white;
|
|
254
|
+
border: none;
|
|
255
|
+
padding: 0.4rem 0.8rem;
|
|
256
|
+
border-radius: 6px;
|
|
257
|
+
font-weight: 600;
|
|
258
|
+
font-size: 0.8rem;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
transition: opacity 0.2s;
|
|
261
|
+
}
|
|
262
|
+
.prompt-btn:hover { opacity: 0.9; }
|
|
263
|
+
.modal-overlay {
|
|
264
|
+
display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
265
|
+
background: rgba(0,0,0,0.6);
|
|
266
|
+
z-index: 1000; align-items: center; justify-content: center;
|
|
267
|
+
}
|
|
268
|
+
.modal-overlay.active { display: flex; }
|
|
269
|
+
.modal {
|
|
270
|
+
background: linear-gradient(135deg, #1e1e3f 0%, #1a1a2e 100%);
|
|
271
|
+
border: 1px solid rgba(255,255,255,0.15); border-radius: 12px;
|
|
272
|
+
padding: 1.5rem; width: 90%; max-width: 640px;
|
|
273
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
|
|
274
|
+
display: flex; flex-direction: column; gap: 1rem;
|
|
275
|
+
}
|
|
276
|
+
.modal-header {
|
|
277
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
278
|
+
}
|
|
279
|
+
.modal-header h2 { font-size: 1.1rem; color: #f4f4f5; }
|
|
280
|
+
.modal-close {
|
|
281
|
+
background: transparent; border: none; color: #71717a;
|
|
282
|
+
font-size: 1.25rem; cursor: pointer; padding: 0.25rem; line-height: 1;
|
|
283
|
+
}
|
|
284
|
+
.modal-close:hover { color: #e4e4e7; }
|
|
285
|
+
#promptTextarea {
|
|
286
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1);
|
|
287
|
+
border-radius: 6px; color: white; font-family: inherit; font-size: 0.9rem;
|
|
288
|
+
padding: 0.75rem; width: 100%; min-height: 200px; resize: vertical; line-height: 1.5;
|
|
289
|
+
}
|
|
290
|
+
#promptTextarea:focus { outline: none; border-color: #8b5cf6; }
|
|
291
|
+
.modal-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
|
|
292
|
+
.btn-cancel {
|
|
293
|
+
background: rgba(255,255,255,0.08); color: #e4e4e7; border: none;
|
|
294
|
+
padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; cursor: pointer;
|
|
295
|
+
}
|
|
296
|
+
.btn-send {
|
|
297
|
+
background: linear-gradient(90deg, #6366f1, #8b5cf6); color: white; border: none;
|
|
298
|
+
padding: 0.5rem 1rem; border-radius: 6px; font-weight: 600; font-size: 0.85rem; cursor: pointer;
|
|
299
|
+
}
|
|
300
|
+
.file-dropdown {
|
|
301
|
+
display: none; position: fixed; z-index: 1100;
|
|
302
|
+
background: #1a1a2e; border: 1px solid rgba(255,255,255,0.15);
|
|
303
|
+
border-radius: 6px; overflow-y: auto; max-height: 180px;
|
|
304
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
305
|
+
}
|
|
306
|
+
.file-dropdown-item {
|
|
307
|
+
padding: 0.4rem 0.75rem; cursor: pointer;
|
|
308
|
+
font-size: 0.8rem; color: #e4e4e7;
|
|
309
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
310
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
311
|
+
}
|
|
312
|
+
.file-dropdown-item.active { background: rgba(139,92,246,0.35); color: white; }
|
|
313
|
+
.file-dropdown-item:hover { background: rgba(255,255,255,0.08); }
|
|
314
|
+
.file-path-hint {
|
|
315
|
+
display: none; position: fixed; z-index: 1101;
|
|
316
|
+
background: #0f0f1a; border: 1px solid rgba(139,92,246,0.4);
|
|
317
|
+
border-radius: 4px; padding: 0.3rem 0.6rem;
|
|
318
|
+
font-size: 0.75rem; font-family: 'SF Mono', 'Fira Code', monospace;
|
|
319
|
+
color: #8b5cf6; white-space: nowrap; pointer-events: none;
|
|
320
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
321
|
+
}
|
|
322
|
+
</style>
|
|
323
|
+
</head>
|
|
324
|
+
<body>
|
|
325
|
+
<div class="terminal-header">
|
|
326
|
+
<a href="/" class="back-link">← Dashboard</a>
|
|
327
|
+
<span class="terminal-title">Agent</span>
|
|
328
|
+
<button class="prompt-btn" onclick="openPromptModal()">✎ Prompt</button>
|
|
329
|
+
{{TERMINAL_COMMAND}}
|
|
330
|
+
</div>
|
|
331
|
+
<div class="modal-overlay" id="promptModal" onclick="if(event.target===this)closePromptModal()">
|
|
332
|
+
<div class="modal">
|
|
333
|
+
<div class="modal-header">
|
|
334
|
+
<h2>Saisir un prompt</h2>
|
|
335
|
+
<button class="modal-close" onclick="closePromptModal()">×</button>
|
|
336
|
+
</div>
|
|
337
|
+
<textarea id="promptTextarea" placeholder="Saisissez votre prompt…"></textarea>
|
|
338
|
+
<div class="modal-actions">
|
|
339
|
+
<button class="btn-cancel" onclick="closePromptModal()">Annuler</button>
|
|
340
|
+
<button class="btn-send" onclick="sendPrompt()">Envoyer</button>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div id="fileDropdown" class="file-dropdown"></div>
|
|
345
|
+
<div id="filePathHint" class="file-path-hint"></div>
|
|
346
|
+
|
|
347
|
+
<div class="layout-body">
|
|
348
|
+
<aside class="meta-sidebar" id="metaSidebar">
|
|
349
|
+
<div class="sidebar-header">
|
|
350
|
+
<span class="sidebar-title">Méta-configs</span>
|
|
351
|
+
<button class="sidebar-collapse-btn" onclick="toggleSidebar()" title="Réduire">‹</button>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="sidebar-sync-warning" id="sidebarSyncWarning" style="display:none;">⚠ Désynchronisé</div>
|
|
354
|
+
<div class="sidebar-toolbar">
|
|
355
|
+
<button class="sb-btn sb-btn-amber" id="sbDiscoverBtn" onclick="sidebarDiscover()">🔍</button>
|
|
356
|
+
<button class="sb-btn sb-btn-primary" id="sbCreateBtn" onclick="openSbModal()" disabled>+</button>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="sidebar-configs">
|
|
359
|
+
<div id="sidebarConfigs"><p class="sidebar-empty">Chargement…</p></div>
|
|
360
|
+
<div class="sb-section-label" id="sbServicesLabel">
|
|
361
|
+
Services
|
|
362
|
+
<button class="sb-refresh-btn" id="sbRefreshRoutesBtn" onclick="refreshSidebarRoutes()" title="Rafraîchir">↻</button>
|
|
363
|
+
</div>
|
|
364
|
+
<div id="sidebarServices" style="display:flex;flex-direction:column;gap:0.3rem;"></div>
|
|
365
|
+
</div>
|
|
366
|
+
</aside>
|
|
367
|
+
|
|
368
|
+
<button class="sidebar-expand-btn" id="sidebarExpandBtn" onclick="toggleSidebar()" title="Méta-configs">⚙</button>
|
|
369
|
+
|
|
370
|
+
<div id="terminal-container"></div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<div class="sb-modal-overlay" id="mcpIframeModal" onclick="if(event.target===this)closeMcpIframeModal()">
|
|
374
|
+
<div class="sb-modal-fullscreen">
|
|
375
|
+
<div class="sb-modal-fullscreen-bar">
|
|
376
|
+
<span id="mcpIframeTitle"></span>
|
|
377
|
+
<button class="sb-icon-btn sb-icon-btn-ghost" onclick="closeMcpIframeModal()" title="Fermer">✕</button>
|
|
378
|
+
</div>
|
|
379
|
+
<iframe id="mcpIframeEl" src="about:blank" allowfullscreen></iframe>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<div class="sb-modal-overlay" id="sbModalOverlay" onclick="if(event.target===this)closeSbModal()">
|
|
384
|
+
<div class="sb-modal">
|
|
385
|
+
<div class="sb-modal-header">
|
|
386
|
+
<h2 id="sbModalTitle">Nouveau profil</h2>
|
|
387
|
+
<button class="sb-modal-close" onclick="closeSbModal()">×</button>
|
|
388
|
+
</div>
|
|
389
|
+
<form id="sbMetaForm" onsubmit="return sbHandleFormSubmit(event)">
|
|
390
|
+
<input type="hidden" id="sbEditId" value="">
|
|
391
|
+
<div class="sb-form-group">
|
|
392
|
+
<label for="sbCfgId">Identifiant (slug)</label>
|
|
393
|
+
<input type="text" id="sbCfgId" placeholder="mon-profil" required pattern="[a-z0-9\-]+" title="Lettres minuscules, chiffres et tirets uniquement">
|
|
394
|
+
</div>
|
|
395
|
+
<div class="sb-form-group">
|
|
396
|
+
<label for="sbCfgName">Nom</label>
|
|
397
|
+
<input type="text" id="sbCfgName" placeholder="Mon Profil" required>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="sb-form-group">
|
|
400
|
+
<label for="sbCfgDesc">Description</label>
|
|
401
|
+
<textarea id="sbCfgDesc" placeholder="Description optionnelle..."></textarea>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="sb-modal-mappings">
|
|
404
|
+
<div class="sb-modal-mappings-title">Mappings MCP → Configuration</div>
|
|
405
|
+
<div class="sb-modal-mappings-list" id="sbModalMappings">
|
|
406
|
+
<p style="font-size:0.75rem;color:#71717a;">Lancez d'abord la découverte des MCPs.</p>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
<button type="submit" class="sb-btn sb-btn-primary" style="width:100%;margin-top:1rem;" id="sbModalSubmitBtn">Créer</button>
|
|
410
|
+
</form>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.min.js"></script>
|
|
415
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.min.js"></script>
|
|
416
|
+
<script>
|
|
417
|
+
class BrowserTerminal {
|
|
418
|
+
constructor(container, options = {}) {
|
|
419
|
+
this.container =
|
|
420
|
+
typeof container === 'string' ? document.querySelector(container) : container;
|
|
421
|
+
this.options = {
|
|
422
|
+
fitOnResize: true,
|
|
423
|
+
...options,
|
|
424
|
+
terminalOptions: {
|
|
425
|
+
cursorBlink: true,
|
|
426
|
+
theme: {
|
|
427
|
+
background: '#1e1e1e',
|
|
428
|
+
foreground: '#d4d4d4',
|
|
429
|
+
},
|
|
430
|
+
...options.terminalOptions,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
this.terminal = new Terminal(this.options.terminalOptions);
|
|
434
|
+
this.fitAddon = new FitAddon.FitAddon();
|
|
435
|
+
this.ws = null;
|
|
436
|
+
this._resizeHandler = null;
|
|
437
|
+
this._init();
|
|
438
|
+
}
|
|
439
|
+
_init() {
|
|
440
|
+
this.terminal.loadAddon(this.fitAddon);
|
|
441
|
+
this.terminal.open(this.container);
|
|
442
|
+
this.fitAddon.fit();
|
|
443
|
+
this._bindClipboard();
|
|
444
|
+
this._connect();
|
|
445
|
+
this._bindResize();
|
|
446
|
+
}
|
|
447
|
+
_bindClipboard() {
|
|
448
|
+
this.terminal.attachCustomKeyEventHandler((e) => {
|
|
449
|
+
if (e.type !== 'keydown') return true;
|
|
450
|
+
if (e.ctrlKey && e.key === 'v') return false;
|
|
451
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
|
452
|
+
const sel = this.terminal.getSelection();
|
|
453
|
+
if (sel) navigator.clipboard.writeText(sel);
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'P') return false;
|
|
457
|
+
return true;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
this.terminal.onSelectionChange(() => {
|
|
461
|
+
const sel = this.terminal.getSelection();
|
|
462
|
+
if (sel) navigator.clipboard.writeText(sel);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
_handleOSC52(data) {
|
|
466
|
+
const regex = /\x1b\]52;([^;]*);([^\x07\x1b]*?)(?:\x07|\x1b\\)/g;
|
|
467
|
+
let match;
|
|
468
|
+
while ((match = regex.exec(data)) !== null) {
|
|
469
|
+
const payload = match[2];
|
|
470
|
+
if (payload && payload !== '?') {
|
|
471
|
+
try {
|
|
472
|
+
const bytes = Uint8Array.from(atob(payload), c => c.charCodeAt(0));
|
|
473
|
+
const text = new TextDecoder().decode(bytes);
|
|
474
|
+
navigator.clipboard.writeText(text);
|
|
475
|
+
console.log('[OSC52] Copied to clipboard:', text);
|
|
476
|
+
} catch (e) {}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
_connect() {
|
|
481
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
482
|
+
const path = this.options.wsPath || '/terminal';
|
|
483
|
+
const url = this.options.wsUrl || protocol + '//' + location.host + path;
|
|
484
|
+
this.ws = new WebSocket(url);
|
|
485
|
+
this.ws.onopen = () => { this._sendResize(); };
|
|
486
|
+
this.ws.onmessage = (event) => {
|
|
487
|
+
this._handleOSC52(event.data);
|
|
488
|
+
this.terminal.write(event.data);
|
|
489
|
+
};
|
|
490
|
+
this.terminal.onData((data) => {
|
|
491
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
492
|
+
this.ws.send(data);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
_bindResize() {
|
|
497
|
+
if (!this.options.fitOnResize) return;
|
|
498
|
+
this._resizeHandler = () => {
|
|
499
|
+
this.fitAddon.fit();
|
|
500
|
+
this._sendResize();
|
|
501
|
+
};
|
|
502
|
+
window.addEventListener('resize', this._resizeHandler);
|
|
503
|
+
}
|
|
504
|
+
_sendResize() {
|
|
505
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
506
|
+
this.ws.send(JSON.stringify({
|
|
507
|
+
type: 'resize',
|
|
508
|
+
cols: this.terminal.cols,
|
|
509
|
+
rows: this.terminal.rows,
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
fit() {
|
|
514
|
+
this.fitAddon.fit();
|
|
515
|
+
this._sendResize();
|
|
516
|
+
}
|
|
517
|
+
dispose() {
|
|
518
|
+
if (this._resizeHandler) window.removeEventListener('resize', this._resizeHandler);
|
|
519
|
+
if (this.ws) this.ws.close();
|
|
520
|
+
this.terminal.dispose();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const term = new BrowserTerminal('#terminal-container', {
|
|
525
|
+
wsPath: '/terminal-ws',
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// --- Autocomplete @ (buffer-based) ---
|
|
529
|
+
let _acBuffer = null;
|
|
530
|
+
let _acFiltered = [];
|
|
531
|
+
let _acAtPos = -1;
|
|
532
|
+
let _acIdx = -1;
|
|
533
|
+
let _acTimer = null;
|
|
534
|
+
|
|
535
|
+
function _acShowPathHint(itemEl, fullPath) {
|
|
536
|
+
const hint = document.getElementById('filePathHint');
|
|
537
|
+
const dropRect = document.getElementById('fileDropdown').getBoundingClientRect();
|
|
538
|
+
const itemRect = itemEl.getBoundingClientRect();
|
|
539
|
+
hint.textContent = fullPath;
|
|
540
|
+
hint.style.left = (dropRect.right + 6) + 'px';
|
|
541
|
+
hint.style.top = itemRect.top + 'px';
|
|
542
|
+
hint.style.display = 'block';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function _acHidePathHint() {
|
|
546
|
+
document.getElementById('filePathHint').style.display = 'none';
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function _acShow(files) {
|
|
550
|
+
_acFiltered = files;
|
|
551
|
+
_acIdx = -1;
|
|
552
|
+
const el = document.getElementById('fileDropdown');
|
|
553
|
+
const ta = document.getElementById('promptTextarea');
|
|
554
|
+
el.innerHTML = '';
|
|
555
|
+
if (!files.length) { el.style.display = 'none'; _acHidePathHint(); return; }
|
|
556
|
+
files.forEach((file, i) => {
|
|
557
|
+
const d = document.createElement('div');
|
|
558
|
+
d.className = 'file-dropdown-item';
|
|
559
|
+
d.textContent = file.split('/').pop();
|
|
560
|
+
d.addEventListener('mouseenter', () => _acShowPathHint(d, file));
|
|
561
|
+
d.addEventListener('mouseleave', () => _acHidePathHint());
|
|
562
|
+
d.addEventListener('mousedown', ev => { ev.preventDefault(); _acInsert(file); });
|
|
563
|
+
el.appendChild(d);
|
|
564
|
+
});
|
|
565
|
+
const rect = ta.getBoundingClientRect();
|
|
566
|
+
el.style.left = rect.left + 'px';
|
|
567
|
+
el.style.top = (rect.bottom + 4) + 'px';
|
|
568
|
+
el.style.width = Math.min(rect.width, 300) + 'px';
|
|
569
|
+
el.style.display = 'block';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function _acHide() {
|
|
573
|
+
document.getElementById('fileDropdown').style.display = 'none';
|
|
574
|
+
_acHidePathHint();
|
|
575
|
+
_acBuffer = null;
|
|
576
|
+
_acAtPos = -1;
|
|
577
|
+
_acIdx = -1;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function _acSetActive(items) {
|
|
581
|
+
items.forEach((el, i) => el.classList.toggle('active', i === _acIdx));
|
|
582
|
+
if (_acIdx >= 0 && items[_acIdx]) {
|
|
583
|
+
items[_acIdx].scrollIntoView({ block: 'nearest' });
|
|
584
|
+
_acShowPathHint(items[_acIdx], _acFiltered[_acIdx]);
|
|
585
|
+
} else {
|
|
586
|
+
_acHidePathHint();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function _acFetch() {
|
|
591
|
+
clearTimeout(_acTimer);
|
|
592
|
+
_acTimer = setTimeout(() => {
|
|
593
|
+
fetch('/proxy/files?q=' + encodeURIComponent(_acBuffer))
|
|
594
|
+
.then(r => r.json())
|
|
595
|
+
.then(files => _acShow(files))
|
|
596
|
+
.catch(() => {});
|
|
597
|
+
}, 120);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function _acInsert(file) {
|
|
601
|
+
const ta = document.getElementById('promptTextarea');
|
|
602
|
+
const end = ta.selectionStart;
|
|
603
|
+
ta.value = ta.value.slice(0, _acAtPos) + '@' + file + ta.value.slice(end);
|
|
604
|
+
ta.setSelectionRange(_acAtPos + 1 + file.length, _acAtPos + 1 + file.length);
|
|
605
|
+
_acHide();
|
|
606
|
+
ta.focus();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function openPromptModal() {
|
|
610
|
+
document.getElementById('promptModal').classList.add('active');
|
|
611
|
+
document.getElementById('promptTextarea').focus();
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function closePromptModal() {
|
|
615
|
+
_acHide();
|
|
616
|
+
document.getElementById('promptModal').classList.remove('active');
|
|
617
|
+
document.getElementById('promptTextarea').value = '';
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function sendPrompt() {
|
|
621
|
+
const text = document.getElementById('promptTextarea').value;
|
|
622
|
+
if (!text) return;
|
|
623
|
+
term.ws.send(text);
|
|
624
|
+
closePromptModal();
|
|
625
|
+
term.terminal.focus();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
document.addEventListener('keydown', (e) => {
|
|
629
|
+
if (e.key === 'Escape') { closePromptModal(); closeSbModal(); }
|
|
630
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'P') { e.preventDefault(); openPromptModal(); }
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
const _ta = document.getElementById('promptTextarea');
|
|
634
|
+
|
|
635
|
+
_ta.addEventListener('keydown', e => {
|
|
636
|
+
if (e.ctrlKey && e.key === 'Enter') { sendPrompt(); return; }
|
|
637
|
+
|
|
638
|
+
const dropdownVisible = document.getElementById('fileDropdown').style.display !== 'none';
|
|
639
|
+
|
|
640
|
+
if (dropdownVisible) {
|
|
641
|
+
const items = [...document.getElementById('fileDropdown').querySelectorAll('.file-dropdown-item')];
|
|
642
|
+
if (e.key === 'ArrowDown') {
|
|
643
|
+
e.preventDefault();
|
|
644
|
+
_acIdx = (_acIdx + 1) % items.length;
|
|
645
|
+
_acSetActive(items);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (e.key === 'ArrowUp') {
|
|
649
|
+
e.preventDefault();
|
|
650
|
+
_acIdx = (_acIdx - 1 + items.length) % items.length;
|
|
651
|
+
_acSetActive(items);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (e.key === 'Enter' && _acIdx >= 0) {
|
|
655
|
+
e.preventDefault();
|
|
656
|
+
_acInsert(_acFiltered[_acIdx]);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (e.key === 'Escape') {
|
|
660
|
+
_acHide();
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (e.key === '@') {
|
|
666
|
+
_acBuffer = '';
|
|
667
|
+
_acAtPos = _ta.selectionStart;
|
|
668
|
+
_acFetch();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (_acBuffer !== null) {
|
|
673
|
+
if (e.key === ' ' || e.key === 'Enter') {
|
|
674
|
+
_acHide();
|
|
675
|
+
} else if (e.key === 'Backspace') {
|
|
676
|
+
if (_acBuffer.length === 0) {
|
|
677
|
+
_acHide();
|
|
678
|
+
} else {
|
|
679
|
+
_acBuffer = _acBuffer.slice(0, -1);
|
|
680
|
+
_acFetch();
|
|
681
|
+
}
|
|
682
|
+
} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
|
683
|
+
_acBuffer += e.key;
|
|
684
|
+
_acFetch();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
_ta.addEventListener('blur', () => {
|
|
690
|
+
setTimeout(() => _acHide(), 150);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// =============================================
|
|
694
|
+
// Sidebar méta-configurations
|
|
695
|
+
// =============================================
|
|
696
|
+
let sbDiscoveredMcps = [];
|
|
697
|
+
let sbOfflineMappings = {};
|
|
698
|
+
let sbActiveId = null;
|
|
699
|
+
let sbSyncInterval = null;
|
|
700
|
+
let sbSynced = true;
|
|
701
|
+
|
|
702
|
+
function sbEsc(str) {
|
|
703
|
+
const d = document.createElement('div');
|
|
704
|
+
d.textContent = String(str);
|
|
705
|
+
return d.innerHTML;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function toggleSidebar() {
|
|
709
|
+
const sidebar = document.getElementById('metaSidebar');
|
|
710
|
+
const expandBtn = document.getElementById('sidebarExpandBtn');
|
|
711
|
+
const collapsed = sidebar.classList.toggle('collapsed');
|
|
712
|
+
expandBtn.style.display = collapsed ? '' : 'none';
|
|
713
|
+
setTimeout(() => term.fit(), 210);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function loadSidebarConfigs() {
|
|
717
|
+
try {
|
|
718
|
+
const res = await fetch('/proxy/meta-configs');
|
|
719
|
+
const data = await res.json();
|
|
720
|
+
const configs = data.metaConfigs || {};
|
|
721
|
+
sbActiveId = data.activeMetaConfig || null;
|
|
722
|
+
renderSidebarConfigs(configs, sbActiveId);
|
|
723
|
+
if (sbActiveId && !sbSyncInterval) {
|
|
724
|
+
sbCheckSync();
|
|
725
|
+
sbSyncInterval = setInterval(sbCheckSync, 30000);
|
|
726
|
+
}
|
|
727
|
+
} catch (err) {
|
|
728
|
+
document.getElementById('sidebarConfigs').innerHTML = '<p class="sidebar-empty">Erreur de chargement</p>';
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function renderSidebarConfigs(configs, activeId) {
|
|
733
|
+
const el = document.getElementById('sidebarConfigs');
|
|
734
|
+
const entries = Object.entries(configs);
|
|
735
|
+
if (!entries.length) {
|
|
736
|
+
el.innerHTML = '<p class="sidebar-empty">Aucun profil</p>';
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
el.innerHTML = entries.map(([id, cfg]) => {
|
|
740
|
+
const isActive = id === activeId;
|
|
741
|
+
return '<div class="sb-config-card' + (isActive ? ' active' : '') + '" data-id="' + sbEsc(id) + '">'
|
|
742
|
+
+ '<div class="sb-config-row">'
|
|
743
|
+
+ '<span class="sb-config-name">' + sbEsc(cfg.name) + '</span>'
|
|
744
|
+
+ '<div class="sb-config-actions">'
|
|
745
|
+
+ '<button class="sb-icon-btn sb-icon-btn-green" data-id="' + sbEsc(id) + '" onclick="sidebarApply(this.dataset.id, this)" title="Appliquer">✓</button>'
|
|
746
|
+
+ '<button class="sb-icon-btn sb-icon-btn-ghost" data-id="' + sbEsc(id) + '" onclick="sidebarEdit(this.dataset.id)" title="Modifier">✎</button>'
|
|
747
|
+
+ '<button class="sb-icon-btn sb-icon-btn-red" data-id="' + sbEsc(id) + '" onclick="sidebarDelete(this.dataset.id)" title="Supprimer">✕</button>'
|
|
748
|
+
+ '</div></div></div>';
|
|
749
|
+
}).join('');
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async function sidebarApply(id, btn) {
|
|
753
|
+
if (btn.disabled) return;
|
|
754
|
+
btn.disabled = true;
|
|
755
|
+
btn.innerHTML = '<span class="sb-spinner"></span>';
|
|
756
|
+
try {
|
|
757
|
+
const res = await fetch('/proxy/meta-configs/' + encodeURIComponent(id) + '/apply', { method: 'POST' });
|
|
758
|
+
const data = await res.json();
|
|
759
|
+
if (data.success) {
|
|
760
|
+
sbActiveId = id;
|
|
761
|
+
sbSynced = true;
|
|
762
|
+
document.getElementById('sidebarSyncWarning').style.display = 'none';
|
|
763
|
+
if (!sbSyncInterval) {
|
|
764
|
+
sbCheckSync();
|
|
765
|
+
sbSyncInterval = setInterval(sbCheckSync, 30000);
|
|
766
|
+
}
|
|
767
|
+
await loadSidebarConfigs();
|
|
768
|
+
} else {
|
|
769
|
+
alert('Erreur lors de l\u2019application du profil.');
|
|
770
|
+
}
|
|
771
|
+
} catch (err) {
|
|
772
|
+
alert('Erreur r\u00e9seau: ' + err.message);
|
|
773
|
+
} finally {
|
|
774
|
+
btn.disabled = false;
|
|
775
|
+
btn.innerHTML = '✓';
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async function sidebarDelete(id) {
|
|
780
|
+
if (!confirm('Supprimer ce profil ?')) return;
|
|
781
|
+
try {
|
|
782
|
+
const res = await fetch('/proxy/meta-configs/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
783
|
+
if (res.ok) {
|
|
784
|
+
await loadSidebarConfigs();
|
|
785
|
+
} else {
|
|
786
|
+
const data = await res.json();
|
|
787
|
+
alert('Erreur: ' + (data.error || 'Inconnue'));
|
|
788
|
+
}
|
|
789
|
+
} catch (err) {
|
|
790
|
+
alert('Erreur r\u00e9seau: ' + err.message);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function sidebarDiscover() {
|
|
795
|
+
const btn = document.getElementById('sbDiscoverBtn');
|
|
796
|
+
btn.disabled = true;
|
|
797
|
+
btn.innerHTML = '<span class="sb-spinner"></span>';
|
|
798
|
+
try {
|
|
799
|
+
const res = await fetch('/proxy/mcp-configurations');
|
|
800
|
+
const data = await res.json();
|
|
801
|
+
sbDiscoveredMcps = data.results || [];
|
|
802
|
+
const compatible = sbDiscoveredMcps.filter(m => m.configurations !== null);
|
|
803
|
+
document.getElementById('sbCreateBtn').disabled = compatible.length === 0;
|
|
804
|
+
} catch (err) {
|
|
805
|
+
alert('Erreur lors de la d\u00e9couverte: ' + err.message);
|
|
806
|
+
} finally {
|
|
807
|
+
btn.disabled = false;
|
|
808
|
+
btn.innerHTML = '🔍';
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function openSbModal() {
|
|
813
|
+
document.getElementById('sbModalTitle').textContent = 'Nouveau profil';
|
|
814
|
+
document.getElementById('sbModalSubmitBtn').textContent = 'Cr\u00e9er';
|
|
815
|
+
document.getElementById('sbEditId').value = '';
|
|
816
|
+
document.getElementById('sbCfgId').value = '';
|
|
817
|
+
document.getElementById('sbCfgId').readOnly = false;
|
|
818
|
+
document.getElementById('sbCfgName').value = '';
|
|
819
|
+
document.getElementById('sbCfgDesc').value = '';
|
|
820
|
+
sbRenderModalMappings({});
|
|
821
|
+
document.getElementById('sbModalOverlay').classList.add('active');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function sidebarEdit(id) {
|
|
825
|
+
try {
|
|
826
|
+
const res = await fetch('/proxy/meta-configs');
|
|
827
|
+
const data = await res.json();
|
|
828
|
+
const cfg = data.metaConfigs[id];
|
|
829
|
+
if (!cfg) return alert('Profil introuvable');
|
|
830
|
+
|
|
831
|
+
document.getElementById('sbModalTitle').textContent = 'Modifier\u00a0: ' + cfg.name;
|
|
832
|
+
document.getElementById('sbModalSubmitBtn').textContent = 'Mettre \u00e0 jour';
|
|
833
|
+
document.getElementById('sbEditId').value = id;
|
|
834
|
+
document.getElementById('sbCfgId').value = id;
|
|
835
|
+
document.getElementById('sbCfgId').readOnly = true;
|
|
836
|
+
document.getElementById('sbCfgName').value = cfg.name;
|
|
837
|
+
document.getElementById('sbCfgDesc').value = cfg.description || '';
|
|
838
|
+
|
|
839
|
+
if (sbDiscoveredMcps.length === 0) {
|
|
840
|
+
await sidebarDiscover();
|
|
841
|
+
}
|
|
842
|
+
sbRenderModalMappings(cfg.mappings || {});
|
|
843
|
+
document.getElementById('sbModalOverlay').classList.add('active');
|
|
844
|
+
} catch (err) {
|
|
845
|
+
alert('Erreur: ' + err.message);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function sbRenderModalMappings(currentMappings) {
|
|
850
|
+
const container = document.getElementById('sbModalMappings');
|
|
851
|
+
const compatible = sbDiscoveredMcps.filter(m => m.configurations !== null);
|
|
852
|
+
const compatiblePaths = new Set(compatible.map(m => m.path));
|
|
853
|
+
|
|
854
|
+
sbOfflineMappings = {};
|
|
855
|
+
Object.entries(currentMappings).forEach(([path, configId]) => {
|
|
856
|
+
if (!compatiblePaths.has(path)) sbOfflineMappings[path] = configId;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
if (compatible.length === 0 && Object.keys(sbOfflineMappings).length === 0) {
|
|
860
|
+
container.innerHTML = '<p style="font-size:0.75rem;color:#71717a;">Lancez d\u2019abord la d\u00e9couverte des MCPs.</p>';
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const onlineHtml = compatible.map(mcp => {
|
|
865
|
+
const selected = currentMappings[mcp.path] || '';
|
|
866
|
+
const options = mcp.configurations.map(c =>
|
|
867
|
+
'<option value="' + sbEsc(c.id) + '"' + (c.id === selected ? ' selected' : '') + '>'
|
|
868
|
+
+ sbEsc(c.name || c.id) + (c.active ? ' (actif)' : '') + '</option>'
|
|
869
|
+
).join('');
|
|
870
|
+
return '<div class="sb-modal-mapping-item">'
|
|
871
|
+
+ '<div style="flex:1;"><div class="mcp-label">' + sbEsc(mcp.name) + '</div>'
|
|
872
|
+
+ '<div class="mcp-path-label">' + sbEsc(mcp.path) + '</div></div>'
|
|
873
|
+
+ '<select data-mcp-path="' + sbEsc(mcp.path) + '"><option value="">-- ignorer --</option>' + options + '</select>'
|
|
874
|
+
+ '</div>';
|
|
875
|
+
}).join('');
|
|
876
|
+
|
|
877
|
+
const offlineHtml = Object.entries(sbOfflineMappings).map(([path, configId]) =>
|
|
878
|
+
'<div class="sb-modal-mapping-item" style="opacity:0.5;">'
|
|
879
|
+
+ '<div style="flex:1;"><div class="mcp-label">⚠ ' + sbEsc(path) + '</div>'
|
|
880
|
+
+ '<div class="mcp-path-label">MCP hors ligne — mapping conserv\u00e9</div></div>'
|
|
881
|
+
+ '<span style="font-size:0.8rem;color:#a1a1aa;">' + sbEsc(configId) + '</span>'
|
|
882
|
+
+ '</div>'
|
|
883
|
+
).join('');
|
|
884
|
+
|
|
885
|
+
container.innerHTML = onlineHtml + offlineHtml;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
async function sbHandleFormSubmit(e) {
|
|
889
|
+
e.preventDefault();
|
|
890
|
+
const editId = document.getElementById('sbEditId').value;
|
|
891
|
+
const id = document.getElementById('sbCfgId').value.trim();
|
|
892
|
+
const name = document.getElementById('sbCfgName').value.trim();
|
|
893
|
+
const description = document.getElementById('sbCfgDesc').value.trim();
|
|
894
|
+
|
|
895
|
+
const mappings = { ...sbOfflineMappings };
|
|
896
|
+
document.querySelectorAll('#sbModalMappings select[data-mcp-path]').forEach(sel => {
|
|
897
|
+
if (sel.value) mappings[sel.dataset.mcpPath] = sel.value;
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
const btn = document.getElementById('sbModalSubmitBtn');
|
|
901
|
+
btn.disabled = true;
|
|
902
|
+
try {
|
|
903
|
+
let res;
|
|
904
|
+
if (editId) {
|
|
905
|
+
res = await fetch('/proxy/meta-configs/' + encodeURIComponent(editId), {
|
|
906
|
+
method: 'PUT',
|
|
907
|
+
headers: { 'Content-Type': 'application/json' },
|
|
908
|
+
body: JSON.stringify({ name, description, mappings })
|
|
909
|
+
});
|
|
910
|
+
} else {
|
|
911
|
+
res = await fetch('/proxy/meta-configs', {
|
|
912
|
+
method: 'POST',
|
|
913
|
+
headers: { 'Content-Type': 'application/json' },
|
|
914
|
+
body: JSON.stringify({ id, name, description, mappings })
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
const data = await res.json();
|
|
918
|
+
if (res.ok) {
|
|
919
|
+
closeSbModal();
|
|
920
|
+
await loadSidebarConfigs();
|
|
921
|
+
} else {
|
|
922
|
+
alert('Erreur: ' + (data.error || 'Inconnue'));
|
|
923
|
+
}
|
|
924
|
+
} catch (err) {
|
|
925
|
+
alert('Erreur r\u00e9seau: ' + err.message);
|
|
926
|
+
} finally {
|
|
927
|
+
btn.disabled = false;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function closeSbModal() {
|
|
932
|
+
document.getElementById('sbModalOverlay').classList.remove('active');
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function sbCheckSync() {
|
|
936
|
+
try {
|
|
937
|
+
const res = await fetch('/proxy/meta-configs/active/sync');
|
|
938
|
+
const data = await res.json();
|
|
939
|
+
const wasSynced = sbSynced;
|
|
940
|
+
sbSynced = data.synced !== false;
|
|
941
|
+
const warning = document.getElementById('sidebarSyncWarning');
|
|
942
|
+
if (!sbSynced) {
|
|
943
|
+
warning.style.display = '';
|
|
944
|
+
const activeCard = document.querySelector('#sidebarConfigs .sb-config-card.active');
|
|
945
|
+
if (activeCard) activeCard.classList.remove('active');
|
|
946
|
+
} else if (!wasSynced && sbSynced) {
|
|
947
|
+
warning.style.display = 'none';
|
|
948
|
+
await loadSidebarConfigs();
|
|
949
|
+
}
|
|
950
|
+
} catch (_) {}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function loadSidebarRoutes() {
|
|
954
|
+
try {
|
|
955
|
+
const res = await fetch('/proxy/routes');
|
|
956
|
+
const data = await res.json();
|
|
957
|
+
renderSidebarRoutes(data.routes || []);
|
|
958
|
+
} catch (_) {}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function renderSidebarRoutes(routes) {
|
|
962
|
+
const el = document.getElementById('sidebarServices');
|
|
963
|
+
if (!routes.length) {
|
|
964
|
+
el.innerHTML = '<p class="sidebar-empty">Aucun MCP connect\u00e9</p>';
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
el.innerHTML = routes.map(r =>
|
|
968
|
+
'<div class="sb-service-card" data-path="' + sbEsc(r.path) + '" data-name="' + sbEsc(r.name || r.path) + '" onclick="openMcpIframeModal(this.dataset.path, this.dataset.name)">'
|
|
969
|
+
+ '<span class="sb-service-name" title="' + sbEsc(r.name || r.path) + '">' + sbEsc(r.name || r.path) + '</span>'
|
|
970
|
+
+ '<div class="sb-service-actions">'
|
|
971
|
+
+ '<button class="sb-icon-btn sb-icon-btn-purple" data-path="' + sbEsc(r.path) + '" data-name="' + sbEsc(r.name || r.path) + '" onclick="event.stopPropagation(); openMcpIframeModal(this.dataset.path, this.dataset.name)" title="Ouvrir dans une fenêtre">⛶</button>'
|
|
972
|
+
+ '<button class="sb-icon-btn sb-icon-btn-blue" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarOpenRoute(this.dataset.path)" title="Ouvrir">↗</button>'
|
|
973
|
+
+ '<button class="sb-icon-btn sb-icon-btn-amber" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarCheckHealth(this.dataset.path, this)" title="Santé">❤</button>'
|
|
974
|
+
+ '</div></div>'
|
|
975
|
+
).join('');
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function sidebarOpenRoute(path) {
|
|
979
|
+
window.open(path, '_blank');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function openMcpIframeModal(path, name) {
|
|
983
|
+
document.getElementById('mcpIframeEl').src = path;
|
|
984
|
+
document.getElementById('mcpIframeTitle').textContent = name || path;
|
|
985
|
+
document.getElementById('mcpIframeModal').classList.add('active');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function closeMcpIframeModal() {
|
|
989
|
+
document.getElementById('mcpIframeModal').classList.remove('active');
|
|
990
|
+
document.getElementById('mcpIframeEl').src = 'about:blank';
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function sidebarCheckHealth(path, btn) {
|
|
994
|
+
btn.disabled = true;
|
|
995
|
+
btn.innerHTML = '<span class="sb-spinner"></span>';
|
|
996
|
+
try {
|
|
997
|
+
const res = await fetch('/proxy/check', {
|
|
998
|
+
method: 'POST',
|
|
999
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1000
|
+
body: JSON.stringify({ path })
|
|
1001
|
+
});
|
|
1002
|
+
const data = await res.json();
|
|
1003
|
+
btn.innerHTML = data.healthy ? '✓' : '✕';
|
|
1004
|
+
btn.className = 'sb-icon-btn ' + (data.healthy ? 'sb-icon-btn-green' : 'sb-icon-btn-red');
|
|
1005
|
+
if (!data.healthy) await loadSidebarRoutes();
|
|
1006
|
+
else setTimeout(() => { btn.innerHTML = '❤'; btn.className = 'sb-icon-btn sb-icon-btn-amber'; btn.disabled = false; }, 2000);
|
|
1007
|
+
} catch (_) {
|
|
1008
|
+
btn.innerHTML = '❤';
|
|
1009
|
+
btn.disabled = false;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
async function refreshSidebarRoutes() {
|
|
1014
|
+
const btn = document.getElementById('sbRefreshRoutesBtn');
|
|
1015
|
+
btn.disabled = true;
|
|
1016
|
+
btn.classList.add('spinning');
|
|
1017
|
+
try {
|
|
1018
|
+
await loadSidebarRoutes();
|
|
1019
|
+
} finally {
|
|
1020
|
+
btn.disabled = false;
|
|
1021
|
+
btn.classList.remove('spinning');
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
loadSidebarConfigs();
|
|
1026
|
+
loadSidebarRoutes();
|
|
1027
|
+
</script>
|
|
1028
|
+
</body>
|
|
1029
|
+
</html>
|