@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,643 @@
|
|
|
1
|
+
const { escapeHtml } = require('../services/proxyRouter');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface de gestion des méta-configurations
|
|
5
|
+
* @param {string} proxyName
|
|
6
|
+
* @param {Object} configs - metaConfigs object
|
|
7
|
+
* @param {string|null} activeId - ID de la méta-config active
|
|
8
|
+
*/
|
|
9
|
+
function renderMetaConfigs(proxyName, configs, activeId) {
|
|
10
|
+
const configsHtml = Object.keys(configs).length > 0
|
|
11
|
+
? `<div class="configs-list" id="configsList">
|
|
12
|
+
${Object.entries(configs).map(([id, cfg]) => `
|
|
13
|
+
<div class="config-card ${id === activeId ? 'active' : ''}" data-id="${escapeHtml(id)}">
|
|
14
|
+
<div class="config-info">
|
|
15
|
+
<div class="config-name">
|
|
16
|
+
${escapeHtml(cfg.name)}
|
|
17
|
+
${id === activeId ? '<span class="badge">actif</span>' : ''}
|
|
18
|
+
</div>
|
|
19
|
+
${cfg.description ? `<div class="config-desc">${escapeHtml(cfg.description)}</div>` : ''}
|
|
20
|
+
<div class="config-mappings">
|
|
21
|
+
${Object.entries(cfg.mappings).map(([p, c]) => `${escapeHtml(p)} → ${escapeHtml(c)}`).join(' • ')}
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="config-actions">
|
|
25
|
+
<button class="btn btn-green" onclick="applyConfig('${escapeHtml(id)}', this)">Appliquer</button>
|
|
26
|
+
<button class="btn btn-ghost" onclick="editConfig('${escapeHtml(id)}')">Modifier</button>
|
|
27
|
+
<button class="btn btn-red" onclick="deleteConfig('${escapeHtml(id)}')">Suppr.</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`).join('')}
|
|
31
|
+
</div>`
|
|
32
|
+
: `<div class="empty-state" id="emptyState">
|
|
33
|
+
<h2>Aucun profil de méta-configuration</h2>
|
|
34
|
+
<p>Cliquez sur "Découvrir les MCPs" pour détecter les MCPs compatibles, puis créez un profil.</p>
|
|
35
|
+
</div>`;
|
|
36
|
+
|
|
37
|
+
return `<!DOCTYPE html>
|
|
38
|
+
<html lang="fr">
|
|
39
|
+
<head>
|
|
40
|
+
<meta charset="UTF-8">
|
|
41
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
42
|
+
<title>${escapeHtml(proxyName)} - Meta Configurations</title>
|
|
43
|
+
<style>
|
|
44
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
45
|
+
body {
|
|
46
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
47
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
48
|
+
min-height: 100vh;
|
|
49
|
+
color: #e4e4e7;
|
|
50
|
+
padding: 1.5rem;
|
|
51
|
+
}
|
|
52
|
+
.container { max-width: 1100px; margin: 0 auto; }
|
|
53
|
+
header { text-align: center; margin-bottom: 1.5rem; }
|
|
54
|
+
h1 {
|
|
55
|
+
font-size: 2rem;
|
|
56
|
+
background: linear-gradient(90deg, #6366f1, #8b5cf6);
|
|
57
|
+
-webkit-background-clip: text;
|
|
58
|
+
-webkit-text-fill-color: transparent;
|
|
59
|
+
background-clip: text;
|
|
60
|
+
margin-bottom: 0.25rem;
|
|
61
|
+
}
|
|
62
|
+
.subtitle { color: #a1a1aa; font-size: 0.95rem; }
|
|
63
|
+
.toolbar {
|
|
64
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
65
|
+
margin-bottom: 0.75rem; gap: 0.5rem;
|
|
66
|
+
}
|
|
67
|
+
.toolbar-right { display: flex; gap: 0.5rem; align-items: center; }
|
|
68
|
+
.btn {
|
|
69
|
+
border: none; padding: 0.5rem 1rem; border-radius: 6px;
|
|
70
|
+
font-weight: 600; font-size: 0.8rem; cursor: pointer;
|
|
71
|
+
transition: opacity 0.2s; display: inline-flex; align-items: center;
|
|
72
|
+
gap: 0.4rem; white-space: nowrap; text-decoration: none; color: white;
|
|
73
|
+
}
|
|
74
|
+
.btn-primary { background: linear-gradient(90deg, #6366f1, #8b5cf6); }
|
|
75
|
+
.btn-green { background: linear-gradient(90deg, #10b981, #059669); }
|
|
76
|
+
.btn-amber { background: linear-gradient(90deg, #f59e0b, #d97706); }
|
|
77
|
+
.btn-red { background: rgba(239, 68, 68, 0.8); }
|
|
78
|
+
.btn-ghost {
|
|
79
|
+
background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15);
|
|
80
|
+
color: #e4e4e7;
|
|
81
|
+
}
|
|
82
|
+
.btn:hover { opacity: 0.9; }
|
|
83
|
+
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
84
|
+
|
|
85
|
+
.section-title {
|
|
86
|
+
font-size: 1rem; font-weight: 600; color: #f4f4f5;
|
|
87
|
+
margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.configs-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
91
|
+
.config-card {
|
|
92
|
+
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
|
|
93
|
+
border-radius: 8px; padding: 0.75rem 1rem;
|
|
94
|
+
display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
|
|
95
|
+
transition: all 0.15s ease;
|
|
96
|
+
}
|
|
97
|
+
.config-card:hover {
|
|
98
|
+
background: rgba(255,255,255,0.08); border-color: rgba(139,92,246,0.4);
|
|
99
|
+
}
|
|
100
|
+
.config-card.active { border-color: rgba(16,185,129,0.5); background: rgba(16,185,129,0.08); }
|
|
101
|
+
.config-info { flex: 1; min-width: 0; }
|
|
102
|
+
.config-name {
|
|
103
|
+
font-size: 0.95rem; font-weight: 600; color: #f4f4f5;
|
|
104
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
105
|
+
}
|
|
106
|
+
.config-name .badge {
|
|
107
|
+
font-size: 0.65rem; padding: 0.1rem 0.4rem; border-radius: 4px;
|
|
108
|
+
background: rgba(16,185,129,0.2); color: #10b981; font-weight: 500;
|
|
109
|
+
}
|
|
110
|
+
.config-desc { font-size: 0.8rem; color: #a1a1aa; margin-top: 0.15rem; }
|
|
111
|
+
.config-mappings {
|
|
112
|
+
font-size: 0.7rem; color: #71717a; margin-top: 0.25rem;
|
|
113
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
114
|
+
}
|
|
115
|
+
.config-actions { display: flex; gap: 0.4rem; flex-shrink: 0; }
|
|
116
|
+
|
|
117
|
+
.empty-state {
|
|
118
|
+
text-align: center; padding: 2.5rem 1.5rem;
|
|
119
|
+
background: rgba(255,255,255,0.03); border-radius: 10px;
|
|
120
|
+
border: 1px dashed rgba(255,255,255,0.1); margin-bottom: 1.5rem;
|
|
121
|
+
}
|
|
122
|
+
.empty-state h2 { color: #a1a1aa; font-size: 1rem; margin-bottom: 0.5rem; }
|
|
123
|
+
.empty-state p { color: #71717a; font-size: 0.85rem; }
|
|
124
|
+
|
|
125
|
+
.discovery-section {
|
|
126
|
+
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08);
|
|
127
|
+
border-radius: 10px; padding: 1rem; margin-bottom: 1.5rem;
|
|
128
|
+
}
|
|
129
|
+
.discovery-section.hidden { display: none; }
|
|
130
|
+
.mcp-list { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.5rem; }
|
|
131
|
+
.mcp-item {
|
|
132
|
+
background: rgba(0,0,0,0.2); border-radius: 6px; padding: 0.5rem 0.75rem;
|
|
133
|
+
display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
|
134
|
+
}
|
|
135
|
+
.mcp-item-info { flex: 1; min-width: 0; }
|
|
136
|
+
.mcp-item-name { font-size: 0.85rem; font-weight: 500; color: #f4f4f5; }
|
|
137
|
+
.mcp-item-path {
|
|
138
|
+
font-size: 0.7rem; color: #8b5cf6;
|
|
139
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
140
|
+
}
|
|
141
|
+
.mcp-item-error { font-size: 0.7rem; color: #71717a; font-style: italic; }
|
|
142
|
+
.mcp-item select {
|
|
143
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
|
|
144
|
+
color: #e4e4e7; padding: 0.3rem 0.5rem; border-radius: 4px; font-size: 0.8rem;
|
|
145
|
+
}
|
|
146
|
+
.mcp-item select:focus { outline: none; border-color: #8b5cf6; }
|
|
147
|
+
|
|
148
|
+
.modal-overlay {
|
|
149
|
+
display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
150
|
+
background: rgba(0,0,0,0.6); backdrop-filter: blur(4px);
|
|
151
|
+
z-index: 1000; align-items: center; justify-content: center;
|
|
152
|
+
}
|
|
153
|
+
.modal-overlay.active { display: flex; }
|
|
154
|
+
.modal {
|
|
155
|
+
background: linear-gradient(135deg, #1e1e3f 0%, #1a1a2e 100%);
|
|
156
|
+
border: 1px solid rgba(255,255,255,0.15); border-radius: 12px;
|
|
157
|
+
padding: 1.5rem; width: 100%; max-width: 480px;
|
|
158
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
|
|
159
|
+
}
|
|
160
|
+
.modal-header {
|
|
161
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
162
|
+
margin-bottom: 1rem;
|
|
163
|
+
}
|
|
164
|
+
.modal-header h2 { font-size: 1.1rem; color: #f4f4f5; }
|
|
165
|
+
.modal-close {
|
|
166
|
+
background: transparent; border: none; color: #71717a;
|
|
167
|
+
font-size: 1.25rem; cursor: pointer; padding: 0.25rem; line-height: 1;
|
|
168
|
+
}
|
|
169
|
+
.modal-close:hover { color: #e4e4e7; }
|
|
170
|
+
.form-group { margin-bottom: 0.75rem; }
|
|
171
|
+
.form-group label {
|
|
172
|
+
font-size: 0.75rem; color: #a1a1aa; margin-bottom: 0.25rem; display: block;
|
|
173
|
+
}
|
|
174
|
+
.form-group input, .form-group textarea {
|
|
175
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1);
|
|
176
|
+
padding: 0.5rem 0.75rem; border-radius: 6px; color: white;
|
|
177
|
+
font-family: inherit; font-size: 0.9rem; width: 100%;
|
|
178
|
+
}
|
|
179
|
+
.form-group textarea { resize: vertical; min-height: 60px; }
|
|
180
|
+
.form-group input:focus, .form-group textarea:focus {
|
|
181
|
+
outline: none; border-color: #8b5cf6; background: rgba(0,0,0,0.4);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.modal-mappings { margin-top: 0.75rem; }
|
|
185
|
+
.modal-mappings-title {
|
|
186
|
+
font-size: 0.8rem; font-weight: 600; color: #a1a1aa; margin-bottom: 0.5rem;
|
|
187
|
+
}
|
|
188
|
+
.modal-mappings-list { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
189
|
+
.modal-mapping-item {
|
|
190
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
191
|
+
background: rgba(0,0,0,0.2); padding: 0.4rem 0.6rem; border-radius: 6px;
|
|
192
|
+
}
|
|
193
|
+
.modal-mapping-item .mcp-label {
|
|
194
|
+
flex: 1; font-size: 0.8rem; color: #f4f4f5; font-weight: 500;
|
|
195
|
+
}
|
|
196
|
+
.modal-mapping-item .mcp-path-label {
|
|
197
|
+
font-size: 0.7rem; color: #8b5cf6;
|
|
198
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
199
|
+
}
|
|
200
|
+
.modal-mapping-item select {
|
|
201
|
+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
|
|
202
|
+
color: #e4e4e7; padding: 0.3rem 0.5rem; border-radius: 4px; font-size: 0.8rem;
|
|
203
|
+
min-width: 140px;
|
|
204
|
+
}
|
|
205
|
+
.modal-mapping-item select:focus { outline: none; border-color: #8b5cf6; }
|
|
206
|
+
|
|
207
|
+
.apply-results { margin-top: 0.75rem; }
|
|
208
|
+
.apply-result {
|
|
209
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
210
|
+
font-size: 0.8rem; padding: 0.25rem 0;
|
|
211
|
+
}
|
|
212
|
+
.apply-result.success { color: #10b981; }
|
|
213
|
+
.apply-result.failure { color: #ef4444; }
|
|
214
|
+
|
|
215
|
+
.spinner {
|
|
216
|
+
display: inline-block; width: 14px; height: 14px;
|
|
217
|
+
border: 2px solid rgba(255,255,255,0.2); border-top-color: #8b5cf6;
|
|
218
|
+
border-radius: 50%; animation: spin 0.6s linear infinite;
|
|
219
|
+
}
|
|
220
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
221
|
+
|
|
222
|
+
footer {
|
|
223
|
+
margin-top: 1.5rem; text-align: center; color: #52525b; font-size: 0.75rem;
|
|
224
|
+
}
|
|
225
|
+
footer a { color: #8b5cf6; text-decoration: none; }
|
|
226
|
+
footer a:hover { text-decoration: underline; }
|
|
227
|
+
|
|
228
|
+
.sync-warning {
|
|
229
|
+
display: none;
|
|
230
|
+
background: rgba(245, 158, 11, 0.12);
|
|
231
|
+
border: 1px solid rgba(245, 158, 11, 0.4);
|
|
232
|
+
border-radius: 8px;
|
|
233
|
+
padding: 0.65rem 1rem;
|
|
234
|
+
margin-bottom: 1rem;
|
|
235
|
+
font-size: 0.85rem;
|
|
236
|
+
color: #fbbf24;
|
|
237
|
+
align-items: center;
|
|
238
|
+
gap: 0.5rem;
|
|
239
|
+
}
|
|
240
|
+
.sync-warning.visible { display: flex; }
|
|
241
|
+
|
|
242
|
+
@media (max-width: 600px) {
|
|
243
|
+
.toolbar { flex-direction: column; }
|
|
244
|
+
.toolbar-right { width: 100%; justify-content: flex-end; }
|
|
245
|
+
.config-card { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
|
|
246
|
+
.config-actions { width: 100%; justify-content: flex-end; }
|
|
247
|
+
.modal { margin: 1rem; max-width: none; }
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
250
|
+
</head>
|
|
251
|
+
<body>
|
|
252
|
+
<div class="container">
|
|
253
|
+
<header>
|
|
254
|
+
<h1>⚙ Meta Configurations</h1>
|
|
255
|
+
<p class="subtitle">${escapeHtml(proxyName)} — Profils de configuration groupés</p>
|
|
256
|
+
</header>
|
|
257
|
+
|
|
258
|
+
<div class="toolbar">
|
|
259
|
+
<a href="/" class="btn btn-ghost">← Dashboard</a>
|
|
260
|
+
<div class="toolbar-right">
|
|
261
|
+
<button class="btn btn-amber" id="discoverBtn" onclick="discover()">🔍 Découvrir les MCPs</button>
|
|
262
|
+
<button class="btn btn-ghost" id="syncBtn" onclick="checkSync()" style="display:none;">⟳ Vérifier sync</button>
|
|
263
|
+
<button class="btn btn-primary" id="createBtn" onclick="openCreateModal()" disabled>+ Créer</button>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="sync-warning" id="syncWarning">
|
|
268
|
+
⚠ Les configurations ne sont plus synchronisées avec le profil actif — une ou plusieurs configs ont été modifiées manuellement.
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="discovery-section hidden" id="discoverySection">
|
|
272
|
+
<div class="section-title">
|
|
273
|
+
MCPs compatibles
|
|
274
|
+
<span id="discoveryCount" style="font-size:0.75rem;color:#71717a;font-weight:400;"></span>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="mcp-list" id="mcpList"></div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<div class="section-title">Profils sauvegardés</div>
|
|
280
|
+
${configsHtml}
|
|
281
|
+
|
|
282
|
+
<div class="apply-results" id="applyResults"></div>
|
|
283
|
+
|
|
284
|
+
<div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
|
|
285
|
+
<div class="modal">
|
|
286
|
+
<div class="modal-header">
|
|
287
|
+
<h2 id="modalTitle">Nouveau profil</h2>
|
|
288
|
+
<button class="modal-close" onclick="closeModal()">×</button>
|
|
289
|
+
</div>
|
|
290
|
+
<form id="metaForm" onsubmit="return handleFormSubmit(event)">
|
|
291
|
+
<input type="hidden" id="editId" value="">
|
|
292
|
+
<div class="form-group">
|
|
293
|
+
<label for="cfgId">Identifiant (slug)</label>
|
|
294
|
+
<input type="text" id="cfgId" placeholder="mon-profil" required pattern="[a-z0-9\\-]+" title="Lettres minuscules, chiffres et tirets uniquement">
|
|
295
|
+
</div>
|
|
296
|
+
<div class="form-group">
|
|
297
|
+
<label for="cfgName">Nom</label>
|
|
298
|
+
<input type="text" id="cfgName" placeholder="Mon Profil" required>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="form-group">
|
|
301
|
+
<label for="cfgDesc">Description</label>
|
|
302
|
+
<textarea id="cfgDesc" placeholder="Description optionnelle..."></textarea>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="modal-mappings">
|
|
305
|
+
<div class="modal-mappings-title">Mappings MCP → Configuration</div>
|
|
306
|
+
<div class="modal-mappings-list" id="modalMappings">
|
|
307
|
+
<p style="font-size:0.75rem;color:#71717a;">Lancez d'abord la découverte des MCPs.</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:1rem;" id="modalSubmitBtn">Créer</button>
|
|
311
|
+
</form>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<footer>
|
|
316
|
+
<p><a href="/">← Dashboard</a> | API: <a href="/proxy/meta-configs">/proxy/meta-configs</a> | <a href="/proxy/mcp-configurations">/proxy/mcp-configurations</a></p>
|
|
317
|
+
</footer>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<script>
|
|
321
|
+
let ACTIVE_META_CONFIG_ID = ${JSON.stringify(activeId)};
|
|
322
|
+
let discoveredMcps = [];
|
|
323
|
+
let offlineMappings = {};
|
|
324
|
+
|
|
325
|
+
let syncPollInterval = null;
|
|
326
|
+
let currentlySynced = true;
|
|
327
|
+
|
|
328
|
+
async function checkSync() {
|
|
329
|
+
console.log('[sync-poll] V\u00e9rification de la synchronisation...');
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetch('/proxy/meta-configs/active/sync');
|
|
332
|
+
const data = await res.json();
|
|
333
|
+
console.log('[sync-poll] R\u00e9ponse:', data);
|
|
334
|
+
|
|
335
|
+
const wasSynced = currentlySynced;
|
|
336
|
+
currentlySynced = data.synced !== false;
|
|
337
|
+
|
|
338
|
+
const warning = document.getElementById('syncWarning');
|
|
339
|
+
const activeCard = document.querySelector('.config-card.active');
|
|
340
|
+
|
|
341
|
+
if (!currentlySynced) {
|
|
342
|
+
console.log('[sync-poll] D\u00e9synchronisation d\u00e9tect\u00e9e, conflits:', data.conflicts);
|
|
343
|
+
warning.classList.add('visible');
|
|
344
|
+
if (activeCard) {
|
|
345
|
+
activeCard.classList.remove('active');
|
|
346
|
+
const badge = activeCard.querySelector('.badge');
|
|
347
|
+
if (badge) badge.remove();
|
|
348
|
+
}
|
|
349
|
+
} else if (!wasSynced && currentlySynced) {
|
|
350
|
+
console.log('[sync-poll] Retour en sync, rechargement...');
|
|
351
|
+
warning.classList.remove('visible');
|
|
352
|
+
window.location.reload();
|
|
353
|
+
} else {
|
|
354
|
+
console.log('[sync-poll] OK \u2014 tout est synchronis\u00e9.');
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.warn('[sync-poll] Erreur r\u00e9seau:', err.message);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (ACTIVE_META_CONFIG_ID) {
|
|
362
|
+
console.log('[sync-poll] D\u00e9marrage du polling (30s) pour la m\u00e9ta-config active:', ACTIVE_META_CONFIG_ID);
|
|
363
|
+
document.getElementById('syncBtn').style.display = '';
|
|
364
|
+
checkSync();
|
|
365
|
+
syncPollInterval = setInterval(checkSync, 30000);
|
|
366
|
+
} else {
|
|
367
|
+
console.log('[sync-poll] Aucune m\u00e9ta-config active, polling d\u00e9sactiv\u00e9.');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
window.addEventListener('beforeunload', () => {
|
|
371
|
+
if (syncPollInterval) clearInterval(syncPollInterval);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
async function discover() {
|
|
375
|
+
const btn = document.getElementById('discoverBtn');
|
|
376
|
+
btn.disabled = true;
|
|
377
|
+
btn.innerHTML = '<span class="spinner"></span> D\u00e9couverte...';
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const res = await fetch('/proxy/mcp-configurations');
|
|
381
|
+
const data = await res.json();
|
|
382
|
+
|
|
383
|
+
discoveredMcps = data.results || [];
|
|
384
|
+
const section = document.getElementById('discoverySection');
|
|
385
|
+
const list = document.getElementById('mcpList');
|
|
386
|
+
const count = document.getElementById('discoveryCount');
|
|
387
|
+
|
|
388
|
+
const compatible = discoveredMcps.filter(m => m.configurations !== null);
|
|
389
|
+
count.textContent = '(' + compatible.length + '/' + discoveredMcps.length + ' compatibles)';
|
|
390
|
+
|
|
391
|
+
list.innerHTML = discoveredMcps.map(mcp => {
|
|
392
|
+
if (mcp.configurations) {
|
|
393
|
+
return '<div class="mcp-item">'
|
|
394
|
+
+ '<div class="mcp-item-info">'
|
|
395
|
+
+ '<div class="mcp-item-name">' + esc(mcp.name) + '</div>'
|
|
396
|
+
+ '<div class="mcp-item-path">' + esc(mcp.path) + '</div>'
|
|
397
|
+
+ '</div>'
|
|
398
|
+
+ '<span style="font-size:0.75rem;color:#10b981;">' + mcp.configurations.length + ' config(s)</span>'
|
|
399
|
+
+ '</div>';
|
|
400
|
+
}
|
|
401
|
+
return '<div class="mcp-item" style="opacity:0.5;">'
|
|
402
|
+
+ '<div class="mcp-item-info">'
|
|
403
|
+
+ '<div class="mcp-item-name">' + esc(mcp.name) + '</div>'
|
|
404
|
+
+ '<div class="mcp-item-path">' + esc(mcp.path) + '</div>'
|
|
405
|
+
+ '</div>'
|
|
406
|
+
+ '<span class="mcp-item-error">' + esc(mcp.error || 'non compatible') + '</span>'
|
|
407
|
+
+ '</div>';
|
|
408
|
+
}).join('');
|
|
409
|
+
|
|
410
|
+
section.classList.remove('hidden');
|
|
411
|
+
document.getElementById('createBtn').disabled = compatible.length === 0;
|
|
412
|
+
} catch (err) {
|
|
413
|
+
alert('Erreur lors de la d\u00e9couverte: ' + err.message);
|
|
414
|
+
} finally {
|
|
415
|
+
btn.disabled = false;
|
|
416
|
+
btn.innerHTML = '🔍 D\u00e9couvrir les MCPs';
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function openCreateModal() {
|
|
421
|
+
document.getElementById('modalTitle').textContent = 'Nouveau profil';
|
|
422
|
+
document.getElementById('modalSubmitBtn').textContent = 'Cr\u00e9er';
|
|
423
|
+
document.getElementById('editId').value = '';
|
|
424
|
+
document.getElementById('cfgId').value = '';
|
|
425
|
+
document.getElementById('cfgId').readOnly = false;
|
|
426
|
+
document.getElementById('cfgName').value = '';
|
|
427
|
+
document.getElementById('cfgDesc').value = '';
|
|
428
|
+
renderModalMappings({});
|
|
429
|
+
document.getElementById('modalOverlay').classList.add('active');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function editConfig(id) {
|
|
433
|
+
fetch('/proxy/meta-configs').then(r => r.json()).then(data => {
|
|
434
|
+
const cfg = data.metaConfigs[id];
|
|
435
|
+
if (!cfg) return alert('Profil introuvable');
|
|
436
|
+
|
|
437
|
+
document.getElementById('modalTitle').textContent = 'Modifier: ' + cfg.name;
|
|
438
|
+
document.getElementById('modalSubmitBtn').textContent = 'Mettre \u00e0 jour';
|
|
439
|
+
document.getElementById('editId').value = id;
|
|
440
|
+
document.getElementById('cfgId').value = id;
|
|
441
|
+
document.getElementById('cfgId').readOnly = true;
|
|
442
|
+
document.getElementById('cfgName').value = cfg.name;
|
|
443
|
+
document.getElementById('cfgDesc').value = cfg.description || '';
|
|
444
|
+
|
|
445
|
+
if (discoveredMcps.length === 0) {
|
|
446
|
+
discover().then(() => {
|
|
447
|
+
renderModalMappings(cfg.mappings || {});
|
|
448
|
+
document.getElementById('modalOverlay').classList.add('active');
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
renderModalMappings(cfg.mappings || {});
|
|
452
|
+
document.getElementById('modalOverlay').classList.add('active');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function renderModalMappings(currentMappings) {
|
|
458
|
+
const container = document.getElementById('modalMappings');
|
|
459
|
+
const compatible = discoveredMcps.filter(m => m.configurations !== null);
|
|
460
|
+
const compatiblePaths = new Set(compatible.map(m => m.path));
|
|
461
|
+
|
|
462
|
+
offlineMappings = {};
|
|
463
|
+
Object.entries(currentMappings).forEach(([path, configId]) => {
|
|
464
|
+
if (!compatiblePaths.has(path)) offlineMappings[path] = configId;
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (compatible.length === 0 && Object.keys(offlineMappings).length === 0) {
|
|
468
|
+
container.innerHTML = '<p style="font-size:0.75rem;color:#71717a;">Lancez d\\'abord la d\u00e9couverte des MCPs.</p>';
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const onlineHtml = compatible.map(mcp => {
|
|
473
|
+
const selected = currentMappings[mcp.path] || '';
|
|
474
|
+
const options = mcp.configurations.map(c =>
|
|
475
|
+
'<option value="' + esc(c.id) + '"' + (c.id === selected ? ' selected' : '') + '>'
|
|
476
|
+
+ esc(c.name || c.id)
|
|
477
|
+
+ (c.active ? ' (actif)' : '') + '</option>'
|
|
478
|
+
).join('');
|
|
479
|
+
|
|
480
|
+
return '<div class="modal-mapping-item">'
|
|
481
|
+
+ '<div style="flex:1;">'
|
|
482
|
+
+ '<div class="mcp-label">' + esc(mcp.name) + '</div>'
|
|
483
|
+
+ '<div class="mcp-path-label">' + esc(mcp.path) + '</div>'
|
|
484
|
+
+ '</div>'
|
|
485
|
+
+ '<select data-mcp-path="' + esc(mcp.path) + '">'
|
|
486
|
+
+ '<option value="">-- ignorer --</option>'
|
|
487
|
+
+ options
|
|
488
|
+
+ '</select>'
|
|
489
|
+
+ '</div>';
|
|
490
|
+
}).join('');
|
|
491
|
+
|
|
492
|
+
const offlineHtml = Object.entries(offlineMappings).map(([path, configId]) =>
|
|
493
|
+
'<div class="modal-mapping-item" style="opacity:0.5;">'
|
|
494
|
+
+ '<div style="flex:1;">'
|
|
495
|
+
+ '<div class="mcp-label">⚠ ' + esc(path) + '</div>'
|
|
496
|
+
+ '<div class="mcp-path-label">MCP hors ligne — mapping conserv\u00e9</div>'
|
|
497
|
+
+ '</div>'
|
|
498
|
+
+ '<span style="font-size:0.8rem;color:#a1a1aa;">' + esc(configId) + '</span>'
|
|
499
|
+
+ '</div>'
|
|
500
|
+
).join('');
|
|
501
|
+
|
|
502
|
+
container.innerHTML = onlineHtml + offlineHtml;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function handleFormSubmit(e) {
|
|
506
|
+
e.preventDefault();
|
|
507
|
+
const editId = document.getElementById('editId').value;
|
|
508
|
+
const id = document.getElementById('cfgId').value.trim();
|
|
509
|
+
const name = document.getElementById('cfgName').value.trim();
|
|
510
|
+
const description = document.getElementById('cfgDesc').value.trim();
|
|
511
|
+
|
|
512
|
+
const mappings = { ...offlineMappings };
|
|
513
|
+
document.querySelectorAll('#modalMappings select[data-mcp-path]').forEach(sel => {
|
|
514
|
+
if (sel.value) mappings[sel.dataset.mcpPath] = sel.value;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const btn = document.getElementById('modalSubmitBtn');
|
|
518
|
+
btn.disabled = true;
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
let res;
|
|
522
|
+
if (editId) {
|
|
523
|
+
res = await fetch('/proxy/meta-configs/' + encodeURIComponent(editId), {
|
|
524
|
+
method: 'PUT',
|
|
525
|
+
headers: { 'Content-Type': 'application/json' },
|
|
526
|
+
body: JSON.stringify({ name, description, mappings })
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
res = await fetch('/proxy/meta-configs', {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: { 'Content-Type': 'application/json' },
|
|
532
|
+
body: JSON.stringify({ id, name, description, mappings })
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const data = await res.json();
|
|
537
|
+
if (res.ok) {
|
|
538
|
+
window.location.reload();
|
|
539
|
+
} else {
|
|
540
|
+
alert('Erreur: ' + (data.error || 'Inconnue'));
|
|
541
|
+
}
|
|
542
|
+
} catch (err) {
|
|
543
|
+
alert('Erreur r\u00e9seau: ' + err.message);
|
|
544
|
+
} finally {
|
|
545
|
+
btn.disabled = false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function applyConfig(id, btn) {
|
|
550
|
+
if (btn.disabled) return;
|
|
551
|
+
const originalText = btn.textContent;
|
|
552
|
+
btn.disabled = true;
|
|
553
|
+
btn.innerHTML = '<span class="spinner"></span>';
|
|
554
|
+
|
|
555
|
+
const resultsDiv = document.getElementById('applyResults');
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
const res = await fetch('/proxy/meta-configs/' + encodeURIComponent(id) + '/apply', {
|
|
559
|
+
method: 'POST'
|
|
560
|
+
});
|
|
561
|
+
const data = await res.json();
|
|
562
|
+
|
|
563
|
+
const appliedResults = (data.results || []).filter(r => !(data.warnings || []).some(w => w.path === r.path));
|
|
564
|
+
resultsDiv.innerHTML = '<div class="section-title" style="margin-top:0.5rem;">R\u00e9sultat d\\'application</div>'
|
|
565
|
+
+ appliedResults.map(r =>
|
|
566
|
+
'<div class="apply-result ' + (r.success ? 'success' : 'failure') + '">'
|
|
567
|
+
+ (r.success ? '✓' : '✗') + ' '
|
|
568
|
+
+ esc(r.path)
|
|
569
|
+
+ (r.error ? ' — ' + esc(r.error) : '')
|
|
570
|
+
+ '</div>'
|
|
571
|
+
).join('')
|
|
572
|
+
+ (data.warnings || []).map(r =>
|
|
573
|
+
'<div class="apply-result" style="color:#f59e0b;">'
|
|
574
|
+
+ '⚠ ' + esc(r.path) + ' — MCP non enregistr\u00e9 (ignor\u00e9)'
|
|
575
|
+
+ '</div>'
|
|
576
|
+
).join('');
|
|
577
|
+
|
|
578
|
+
if (data.success) {
|
|
579
|
+
document.querySelectorAll('.config-card').forEach(card => {
|
|
580
|
+
card.classList.remove('active');
|
|
581
|
+
const badge = card.querySelector('.badge');
|
|
582
|
+
if (badge) badge.remove();
|
|
583
|
+
});
|
|
584
|
+
const activeCard = document.querySelector('.config-card[data-id="' + id + '"]');
|
|
585
|
+
if (activeCard) {
|
|
586
|
+
activeCard.classList.add('active');
|
|
587
|
+
const nameEl = activeCard.querySelector('.config-name');
|
|
588
|
+
if (nameEl) nameEl.insertAdjacentHTML('beforeend', '<span class="badge">actif</span>');
|
|
589
|
+
}
|
|
590
|
+
ACTIVE_META_CONFIG_ID = id;
|
|
591
|
+
document.getElementById('syncBtn').style.display = '';
|
|
592
|
+
currentlySynced = true;
|
|
593
|
+
document.getElementById('syncWarning').classList.remove('visible');
|
|
594
|
+
if (!syncPollInterval) {
|
|
595
|
+
checkSync();
|
|
596
|
+
syncPollInterval = setInterval(checkSync, 30000);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
} catch (err) {
|
|
600
|
+
resultsDiv.innerHTML = '<div class="apply-result failure">Erreur r\u00e9seau: ' + esc(err.message) + '</div>';
|
|
601
|
+
} finally {
|
|
602
|
+
btn.textContent = originalText;
|
|
603
|
+
btn.disabled = false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function deleteConfig(id) {
|
|
608
|
+
if (!confirm('Supprimer ce profil ?')) return;
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
const res = await fetch('/proxy/meta-configs/' + encodeURIComponent(id), {
|
|
612
|
+
method: 'DELETE'
|
|
613
|
+
});
|
|
614
|
+
if (res.ok) {
|
|
615
|
+
window.location.reload();
|
|
616
|
+
} else {
|
|
617
|
+
const data = await res.json();
|
|
618
|
+
alert('Erreur: ' + (data.error || 'Inconnue'));
|
|
619
|
+
}
|
|
620
|
+
} catch (err) {
|
|
621
|
+
alert('Erreur r\u00e9seau: ' + err.message);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function closeModal() {
|
|
626
|
+
document.getElementById('modalOverlay').classList.remove('active');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
document.addEventListener('keydown', (e) => {
|
|
630
|
+
if (e.key === 'Escape') closeModal();
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
function esc(str) {
|
|
634
|
+
const d = document.createElement('div');
|
|
635
|
+
d.textContent = String(str);
|
|
636
|
+
return d.innerHTML;
|
|
637
|
+
}
|
|
638
|
+
</script>
|
|
639
|
+
</body>
|
|
640
|
+
</html>`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
module.exports = { renderMetaConfigs };
|