@imenam/mcp-github 1.1.47 → 1.1.49
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 +24 -10
- package/dist/__tests__/utils/config.test.js +5 -0
- package/dist/__tests__/utils/config.test.js.map +1 -1
- package/dist/config-server.d.ts.map +1 -1
- package/dist/config-server.js +5 -0
- package/dist/config-server.js.map +1 -1
- package/dist/handlers/deploy.d.ts.map +1 -1
- package/dist/handlers/deploy.js +11 -8
- package/dist/handlers/deploy.js.map +1 -1
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -9
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/exec.d.ts +14 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +58 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/ssh.js +3 -3
- package/dist/utils/ssh.js.map +1 -1
- package/package.json +1 -1
- package/public/index.html +507 -235
package/public/index.html
CHANGED
|
@@ -6,17 +6,37 @@
|
|
|
6
6
|
<title>GitHub MCP</title>
|
|
7
7
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
8
|
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0a0a0b;
|
|
11
|
+
--surface: #141416;
|
|
12
|
+
--surface-2: #1b1b1e;
|
|
13
|
+
--border: #2a2a2e;
|
|
14
|
+
--border-strong: #3a3a3f;
|
|
15
|
+
--text: #ededef;
|
|
16
|
+
--text-dim: #9a9aa2;
|
|
17
|
+
--text-muted: #6b6b73;
|
|
18
|
+
--accent: #4d7cfe;
|
|
19
|
+
--accent-hover: #6a92ff;
|
|
20
|
+
--danger: #e0635e;
|
|
21
|
+
--radius: 8px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
* {
|
|
25
|
+
-webkit-font-smoothing: antialiased;
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
body {
|
|
10
|
-
background-color:
|
|
11
|
-
color:
|
|
29
|
+
background-color: var(--bg);
|
|
30
|
+
color: var(--text);
|
|
31
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
/* Layout */
|
|
15
35
|
.app-container {
|
|
16
36
|
display: grid;
|
|
17
|
-
grid-template-columns:
|
|
18
|
-
gap:
|
|
19
|
-
max-width:
|
|
37
|
+
grid-template-columns: 300px 1fr;
|
|
38
|
+
gap: 1.5rem;
|
|
39
|
+
max-width: 1080px;
|
|
20
40
|
margin: 0 auto;
|
|
21
41
|
}
|
|
22
42
|
|
|
@@ -26,179 +46,337 @@
|
|
|
26
46
|
}
|
|
27
47
|
}
|
|
28
48
|
|
|
29
|
-
/*
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
/* Surfaces */
|
|
50
|
+
.card {
|
|
51
|
+
background-color: var(--surface);
|
|
52
|
+
border: 1px solid var(--border);
|
|
53
|
+
border-radius: var(--radius);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.section-label {
|
|
57
|
+
font-size: 0.6875rem;
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
letter-spacing: 0.08em;
|
|
60
|
+
text-transform: uppercase;
|
|
61
|
+
color: var(--text-muted);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.divider {
|
|
65
|
+
border-top: 1px solid var(--border);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Text helpers */
|
|
69
|
+
.text-main { color: var(--text); }
|
|
70
|
+
.text-dim { color: var(--text-dim); }
|
|
71
|
+
.text-muted { color: var(--text-muted); }
|
|
72
|
+
|
|
73
|
+
/* Buttons */
|
|
74
|
+
.btn {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
gap: 0.5rem;
|
|
79
|
+
width: 100%;
|
|
80
|
+
padding: 0.55rem 0.9rem;
|
|
81
|
+
font-size: 0.875rem;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
border-radius: var(--radius);
|
|
84
|
+
border: 1px solid transparent;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition: background-color .15s ease, border-color .15s ease, color .15s ease, opacity .15s ease;
|
|
87
|
+
}
|
|
88
|
+
.btn:disabled {
|
|
89
|
+
opacity: 0.4;
|
|
90
|
+
cursor: not-allowed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.btn-primary {
|
|
94
|
+
background-color: var(--accent);
|
|
95
|
+
color: #fff;
|
|
96
|
+
}
|
|
97
|
+
.btn-primary:not(:disabled):hover { background-color: var(--accent-hover); }
|
|
98
|
+
|
|
99
|
+
.btn-secondary {
|
|
100
|
+
background-color: transparent;
|
|
101
|
+
border-color: var(--border);
|
|
102
|
+
color: var(--text-dim);
|
|
103
|
+
}
|
|
104
|
+
.btn-secondary:not(:disabled):hover {
|
|
105
|
+
background-color: var(--surface-2);
|
|
106
|
+
border-color: var(--border-strong);
|
|
107
|
+
color: var(--text);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn-danger {
|
|
111
|
+
background-color: transparent;
|
|
112
|
+
border-color: var(--border);
|
|
113
|
+
color: var(--danger);
|
|
114
|
+
}
|
|
115
|
+
.btn-danger:not(:disabled):hover {
|
|
116
|
+
background-color: rgba(224, 99, 94, 0.08);
|
|
117
|
+
border-color: var(--danger);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.btn-icon {
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
width: 2.25rem;
|
|
125
|
+
padding: 0;
|
|
35
126
|
flex-shrink: 0;
|
|
36
127
|
}
|
|
37
128
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
129
|
+
/* Form fields */
|
|
130
|
+
.field-label {
|
|
131
|
+
display: block;
|
|
132
|
+
font-size: 0.75rem;
|
|
133
|
+
font-weight: 500;
|
|
134
|
+
color: var(--text-dim);
|
|
135
|
+
margin-bottom: 0.4rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.field-input,
|
|
139
|
+
.field-select {
|
|
140
|
+
width: 100%;
|
|
141
|
+
padding: 0.5rem 0.75rem;
|
|
142
|
+
font-size: 0.875rem;
|
|
143
|
+
background-color: var(--bg);
|
|
144
|
+
border: 1px solid var(--border);
|
|
145
|
+
border-radius: var(--radius);
|
|
146
|
+
color: var(--text);
|
|
147
|
+
outline: none;
|
|
148
|
+
transition: border-color .15s ease;
|
|
149
|
+
}
|
|
150
|
+
.field-input::placeholder { color: var(--text-muted); }
|
|
151
|
+
.field-input:focus,
|
|
152
|
+
.field-select:focus { border-color: var(--accent); }
|
|
153
|
+
.field-select { cursor: pointer; }
|
|
154
|
+
|
|
155
|
+
/* Toggle rows */
|
|
156
|
+
.toggle-row {
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: space-between;
|
|
160
|
+
gap: 1rem;
|
|
161
|
+
padding: 0.65rem 0;
|
|
162
|
+
}
|
|
163
|
+
.toggle-row + .toggle-row { border-top: 1px solid var(--border); }
|
|
164
|
+
.toggle-row label.text {
|
|
165
|
+
font-size: 0.875rem;
|
|
166
|
+
color: var(--text-dim);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.info-row {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: space-between;
|
|
173
|
+
gap: 1rem;
|
|
174
|
+
padding: 0.65rem 0.75rem;
|
|
175
|
+
background-color: var(--bg);
|
|
176
|
+
border: 1px solid var(--border);
|
|
177
|
+
border-radius: var(--radius);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Collapsible form sections */
|
|
181
|
+
.form-section {
|
|
182
|
+
border: 1px solid var(--border);
|
|
183
|
+
border-radius: var(--radius);
|
|
184
|
+
overflow: hidden;
|
|
42
185
|
}
|
|
186
|
+
.section-header {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: space-between;
|
|
190
|
+
width: 100%;
|
|
191
|
+
padding: 0.7rem 0.9rem;
|
|
192
|
+
background-color: var(--surface-2);
|
|
193
|
+
border: none;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
font-size: 0.8125rem;
|
|
196
|
+
font-weight: 600;
|
|
197
|
+
letter-spacing: 0.02em;
|
|
198
|
+
color: var(--text);
|
|
199
|
+
transition: background-color .15s ease;
|
|
200
|
+
}
|
|
201
|
+
.section-header:hover { background-color: var(--border); }
|
|
202
|
+
.section-header .chevron { transition: transform .2s ease; color: var(--text-muted); }
|
|
203
|
+
.section-header.open .chevron { transform: rotate(90deg); }
|
|
204
|
+
.section-static-header {
|
|
205
|
+
padding: 0.7rem 0.9rem;
|
|
206
|
+
background-color: var(--surface-2);
|
|
207
|
+
font-size: 0.8125rem;
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
letter-spacing: 0.02em;
|
|
210
|
+
color: var(--text);
|
|
211
|
+
}
|
|
212
|
+
.section-body { padding: 1rem 0.9rem; }
|
|
43
213
|
|
|
214
|
+
/* iPhone style slider (sober) */
|
|
215
|
+
.switch {
|
|
216
|
+
position: relative;
|
|
217
|
+
display: inline-block;
|
|
218
|
+
width: 40px;
|
|
219
|
+
height: 22px;
|
|
220
|
+
flex-shrink: 0;
|
|
221
|
+
}
|
|
222
|
+
.switch input { opacity: 0; width: 0; height: 0; }
|
|
44
223
|
.slider {
|
|
45
224
|
position: absolute;
|
|
46
225
|
cursor: pointer;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
background-color: #334155;
|
|
52
|
-
transition: .4s;
|
|
53
|
-
border-radius: 24px;
|
|
226
|
+
inset: 0;
|
|
227
|
+
background-color: var(--border-strong);
|
|
228
|
+
transition: .25s;
|
|
229
|
+
border-radius: 22px;
|
|
54
230
|
}
|
|
55
|
-
|
|
56
231
|
.slider:before {
|
|
57
232
|
position: absolute;
|
|
58
233
|
content: "";
|
|
59
|
-
height:
|
|
60
|
-
width:
|
|
234
|
+
height: 16px;
|
|
235
|
+
width: 16px;
|
|
61
236
|
left: 3px;
|
|
62
237
|
bottom: 3px;
|
|
63
|
-
background-color:
|
|
64
|
-
transition: .
|
|
238
|
+
background-color: #fff;
|
|
239
|
+
transition: .25s;
|
|
65
240
|
border-radius: 50%;
|
|
66
241
|
}
|
|
242
|
+
input:checked + .slider { background-color: var(--accent); }
|
|
243
|
+
input:checked + .slider:before { transform: translateX(18px); }
|
|
67
244
|
|
|
68
|
-
|
|
69
|
-
background-color: #2563eb;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
input:checked + .slider:before {
|
|
73
|
-
transform: translateX(20px);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* Custom dropdown styles */
|
|
245
|
+
/* Custom dropdown */
|
|
77
246
|
.search-results {
|
|
78
247
|
position: absolute;
|
|
79
248
|
top: 100%;
|
|
80
249
|
left: 0;
|
|
81
250
|
right: 0;
|
|
82
251
|
z-index: 50;
|
|
83
|
-
background-color:
|
|
84
|
-
border: 1px solid
|
|
85
|
-
border-radius:
|
|
252
|
+
background-color: var(--surface-2);
|
|
253
|
+
border: 1px solid var(--border);
|
|
254
|
+
border-radius: var(--radius);
|
|
86
255
|
margin-top: 0.25rem;
|
|
87
256
|
max-height: 15rem;
|
|
88
257
|
overflow-y: auto;
|
|
89
|
-
box-shadow: 0
|
|
258
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
90
259
|
}
|
|
91
|
-
|
|
92
260
|
.search-result-item {
|
|
93
|
-
padding: 0.5rem
|
|
261
|
+
padding: 0.5rem 0.75rem;
|
|
94
262
|
cursor: pointer;
|
|
95
|
-
transition: background-color
|
|
263
|
+
transition: background-color .12s;
|
|
96
264
|
font-size: 0.875rem;
|
|
265
|
+
color: var(--text-dim);
|
|
97
266
|
}
|
|
98
|
-
|
|
99
267
|
.search-result-item:hover {
|
|
100
|
-
background-color:
|
|
101
|
-
color:
|
|
268
|
+
background-color: var(--surface);
|
|
269
|
+
color: var(--text);
|
|
102
270
|
}
|
|
103
|
-
|
|
104
271
|
.search-result-item.active {
|
|
105
|
-
background-color:
|
|
106
|
-
color:
|
|
107
|
-
outline: 1px solid #3b82f6;
|
|
272
|
+
background-color: var(--surface);
|
|
273
|
+
color: var(--text);
|
|
108
274
|
}
|
|
109
|
-
|
|
110
275
|
.search-result-item.selected {
|
|
111
|
-
background-color:
|
|
112
|
-
color:
|
|
276
|
+
background-color: rgba(77, 124, 254, 0.12);
|
|
277
|
+
color: var(--accent-hover);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Badge */
|
|
281
|
+
.badge {
|
|
282
|
+
display: inline-flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
padding: 0.15rem 0.6rem;
|
|
285
|
+
font-size: 0.75rem;
|
|
286
|
+
font-weight: 500;
|
|
287
|
+
color: var(--text-dim);
|
|
288
|
+
background-color: var(--surface-2);
|
|
289
|
+
border: 1px solid var(--border);
|
|
290
|
+
border-radius: 999px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Spinner */
|
|
294
|
+
.spinner {
|
|
295
|
+
display: inline-block;
|
|
296
|
+
width: 1rem;
|
|
297
|
+
height: 1rem;
|
|
298
|
+
border: 2px solid var(--border-strong);
|
|
299
|
+
border-top-color: var(--accent);
|
|
300
|
+
border-radius: 50%;
|
|
301
|
+
animation: spin 0.7s linear infinite;
|
|
113
302
|
}
|
|
303
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
114
304
|
|
|
115
305
|
/* Toasts */
|
|
116
306
|
.toast {
|
|
117
|
-
|
|
118
|
-
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 0.6rem;
|
|
310
|
+
padding: 0.7rem 0.9rem;
|
|
311
|
+
font-size: 0.875rem;
|
|
312
|
+
background-color: var(--surface-2);
|
|
313
|
+
border: 1px solid var(--border);
|
|
314
|
+
border-left-width: 3px;
|
|
315
|
+
border-radius: var(--radius);
|
|
316
|
+
color: var(--text);
|
|
317
|
+
max-width: 22rem;
|
|
318
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
319
|
+
animation: slideIn 0.25s ease-out forwards, fadeOut 0.3s ease-in forwards 4.7s;
|
|
119
320
|
}
|
|
321
|
+
.toast-success { border-left-color: var(--accent); }
|
|
322
|
+
.toast-error { border-left-color: var(--danger); }
|
|
120
323
|
|
|
121
324
|
@keyframes slideIn {
|
|
122
|
-
from { transform: translateX(
|
|
325
|
+
from { transform: translateX(110%); opacity: 0; }
|
|
123
326
|
to { transform: translateX(0); opacity: 1; }
|
|
124
327
|
}
|
|
125
|
-
|
|
126
328
|
@keyframes fadeOut {
|
|
127
329
|
from { opacity: 1; }
|
|
128
330
|
to { opacity: 0; }
|
|
129
331
|
}
|
|
130
332
|
</style>
|
|
131
333
|
</head>
|
|
132
|
-
<body class="min-h-screen p-
|
|
334
|
+
<body class="min-h-screen p-6 md:p-10">
|
|
133
335
|
<div class="app-container">
|
|
134
336
|
<!-- Sidebar -->
|
|
135
|
-
<aside
|
|
136
|
-
<div class="
|
|
137
|
-
<h2 class="
|
|
138
|
-
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
139
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
|
140
|
-
</svg>
|
|
141
|
-
Configuration
|
|
142
|
-
</h2>
|
|
337
|
+
<aside>
|
|
338
|
+
<div class="card p-5 sticky top-10">
|
|
339
|
+
<h2 class="section-label mb-4">Configuration</h2>
|
|
143
340
|
|
|
144
341
|
<!-- Configuration Selector -->
|
|
145
|
-
<div class="
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
<
|
|
152
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
153
|
-
</svg>
|
|
154
|
-
</button>
|
|
155
|
-
<button type="button" id="deleteConfigBtn" class="px-3 py-2 bg-red-600 hover:bg-red-700 disabled:bg-slate-700 disabled:cursor-not-allowed rounded-lg transition duration-200" title="Supprimer la configuration" disabled>
|
|
156
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
157
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
158
|
-
</svg>
|
|
159
|
-
</button>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<div class="border-t border-slate-700 pt-4 mb-4">
|
|
164
|
-
<h3 class="text-sm font-medium text-slate-400 mb-3">Actions</h3>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<div class="space-y-4">
|
|
168
|
-
<button type="button" onclick="document.getElementById('configForm').requestSubmit()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 shadow-lg flex justify-center items-center">
|
|
169
|
-
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
170
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
342
|
+
<div class="flex gap-2 mb-5">
|
|
343
|
+
<select id="configSelector" class="field-select flex-1">
|
|
344
|
+
<option value="">— Sélectionner —</option>
|
|
345
|
+
</select>
|
|
346
|
+
<button type="button" id="newConfigBtn" class="btn btn-secondary btn-icon" title="Nouvelle configuration">
|
|
347
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
348
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
171
349
|
</svg>
|
|
172
|
-
<span>Sauvegarder</span>
|
|
173
350
|
</button>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
|
|
351
|
+
<button type="button" id="deleteConfigBtn" class="btn btn-danger btn-icon" title="Supprimer la configuration" disabled>
|
|
352
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
353
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
178
354
|
</svg>
|
|
179
|
-
<span>Cloner le dépôt</span>
|
|
180
355
|
</button>
|
|
356
|
+
</div>
|
|
181
357
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
185
|
-
</svg>
|
|
186
|
-
<span>Redémarrer</span>
|
|
187
|
-
</button>
|
|
358
|
+
<div class="divider pt-4">
|
|
359
|
+
<h3 class="section-label mb-3">Actions</h3>
|
|
188
360
|
|
|
189
|
-
<
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
</
|
|
193
|
-
<span>Nettoyer Repo</span>
|
|
194
|
-
</button>
|
|
361
|
+
<div class="space-y-2.5">
|
|
362
|
+
<button type="button" onclick="document.getElementById('configForm').requestSubmit()" class="btn btn-primary">
|
|
363
|
+
Sauvegarder
|
|
364
|
+
</button>
|
|
195
365
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
366
|
+
<button type="button" id="cloneBtn" class="btn btn-secondary">
|
|
367
|
+
Cloner le dépôt
|
|
368
|
+
</button>
|
|
369
|
+
|
|
370
|
+
<button type="button" id="restartBtn" class="btn btn-secondary">
|
|
371
|
+
Redémarrer
|
|
372
|
+
</button>
|
|
373
|
+
|
|
374
|
+
<button type="button" id="deleteWorkingDirBtn" class="btn btn-danger">
|
|
375
|
+
Nettoyer le repo
|
|
376
|
+
</button>
|
|
377
|
+
|
|
378
|
+
<button type="button" id="stopBtn" class="btn btn-danger">
|
|
379
|
+
Arrêter le serveur
|
|
202
380
|
</button>
|
|
203
381
|
</div>
|
|
204
382
|
</div>
|
|
@@ -206,44 +384,44 @@
|
|
|
206
384
|
</aside>
|
|
207
385
|
|
|
208
386
|
<!-- Main Content -->
|
|
209
|
-
<main class="
|
|
210
|
-
<div class="flex items-center gap-
|
|
211
|
-
<h1 class="text-
|
|
212
|
-
<svg class="w-
|
|
213
|
-
<path
|
|
387
|
+
<main class="card p-7">
|
|
388
|
+
<div class="flex items-center gap-3 mb-7">
|
|
389
|
+
<h1 class="text-xl font-semibold text-main flex items-center gap-2.5">
|
|
390
|
+
<svg class="w-5 h-5 text-dim" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
391
|
+
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"></path>
|
|
214
392
|
</svg>
|
|
215
393
|
GitHub MCP
|
|
216
394
|
</h1>
|
|
217
|
-
<span id="agentNameBadge" class="hidden
|
|
395
|
+
<span id="agentNameBadge" class="badge hidden"></span>
|
|
218
396
|
</div>
|
|
219
|
-
|
|
220
|
-
<form id="configForm" class="space-y-
|
|
397
|
+
|
|
398
|
+
<form id="configForm" class="space-y-5">
|
|
221
399
|
<div class="space-y-4" id="inputs">
|
|
222
400
|
<!-- Inputs will be injected here -->
|
|
223
401
|
<div class="flex justify-center p-8">
|
|
224
|
-
<div class="
|
|
402
|
+
<div class="spinner"></div>
|
|
225
403
|
</div>
|
|
226
404
|
</div>
|
|
227
405
|
</form>
|
|
228
406
|
|
|
229
|
-
<div class="mt-8
|
|
230
|
-
|
|
407
|
+
<div class="mt-8 divider pt-4 text-xs text-muted">
|
|
408
|
+
Le serveur MCP doit être redémarré pour appliquer les changements.
|
|
231
409
|
</div>
|
|
232
410
|
</main>
|
|
233
411
|
</div>
|
|
234
412
|
|
|
235
413
|
<!-- Toast Container -->
|
|
236
|
-
<div id="toast-container" class="fixed bottom-
|
|
414
|
+
<div id="toast-container" class="fixed bottom-6 right-6 z-50 flex flex-col gap-2.5"></div>
|
|
237
415
|
|
|
238
416
|
<script>
|
|
239
417
|
// Détection automatique du base path pour fonctionner via proxy
|
|
240
418
|
const pathname = window.location.pathname;
|
|
241
|
-
const BASE_PATH = pathname.includes('.')
|
|
419
|
+
const BASE_PATH = pathname.includes('.')
|
|
242
420
|
? pathname.replace(/\/[^/]*$/, '') // /github-mcp/index.html -> /github-mcp
|
|
243
|
-
: pathname.endsWith('/')
|
|
421
|
+
: pathname.endsWith('/')
|
|
244
422
|
? pathname.slice(0, -1) // /github-mcp/ -> /github-mcp
|
|
245
423
|
: pathname === '/' ? '' : pathname; // /github-mcp -> /github-mcp, / -> ''
|
|
246
|
-
|
|
424
|
+
|
|
247
425
|
// Helper pour construire les URLs d'API
|
|
248
426
|
const api = (endpoint) => `${BASE_PATH}/${endpoint}`.replace(/\/+/g, '/');
|
|
249
427
|
|
|
@@ -258,13 +436,50 @@
|
|
|
258
436
|
{ id: 'TARGET_REPO', label: 'Target Repository', type: 'select', placeholder: 'owner/repo' },
|
|
259
437
|
{ id: 'BASE_BRANCH', label: 'Base Branch', type: 'select', placeholder: 'main' },
|
|
260
438
|
{ id: 'TARGET_BRANCH', label: 'Target Branch', type: 'select', placeholder: 'feature-branch' },
|
|
261
|
-
{ id: '
|
|
439
|
+
{ id: 'LOCAL_DEPLOY', label: 'Déployer en local (même serveur, sans SSH)', type: 'slider' },
|
|
440
|
+
{ id: 'SSH_HOST', label: 'Serveur SSH (SSH_HOST)', type: 'text', placeholder: 'srv.example.com' },
|
|
441
|
+
{ id: 'SSH_USER', label: 'Utilisateur SSH (SSH_USER)', type: 'text', placeholder: 'root' },
|
|
442
|
+
{ id: 'SSH_PORT', label: 'Port SSH (SSH_PORT)', type: 'text', placeholder: '22' },
|
|
443
|
+
{ id: 'SSH_KEY_PATH', label: 'Clé privée SSH (SSH_KEY_PATH)', type: 'text', placeholder: 'C:\\Users\\moi\\.ssh\\id_ed25519' },
|
|
444
|
+
{ id: 'SSH_REMOTE_PATH', label: 'Chemin du projet (distant ou local)', type: 'text', placeholder: '/var/www/mon-projet' },
|
|
262
445
|
{ id: 'TEST_COMMAND', label: 'Commande de test (TEST_COMMAND)', type: 'text', placeholder: 'npm test' },
|
|
263
446
|
{ id: 'READ_ONLY', label: 'Mode Lecture Seule', type: 'slider' },
|
|
264
447
|
{ id: 'INCLUDE_PUBLIC', label: 'Inclure les dépôts publics', type: 'slider' },
|
|
265
448
|
{ id: 'INCLUDE_NOT_OWNED', label: 'Inclure les dépôts dont je ne suis pas propriétaire', type: 'slider' }
|
|
266
449
|
];
|
|
267
450
|
|
|
451
|
+
// Index des champs par id (pour le rendu groupé en sections)
|
|
452
|
+
const fieldsById = Object.fromEntries(fields.map(f => [f.id, f]));
|
|
453
|
+
|
|
454
|
+
// Sections du formulaire. Le coeur (dépôt/branches) est toujours déplié ;
|
|
455
|
+
// les autres sont repliables, le paramétrage serveur étant fait une seule fois.
|
|
456
|
+
const sections = [
|
|
457
|
+
{
|
|
458
|
+
id: 'core',
|
|
459
|
+
title: 'Dépôt & branches',
|
|
460
|
+
collapsible: false,
|
|
461
|
+
fields: ['TARGET_REPO', 'BASE_BRANCH', 'TARGET_BRANCH']
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: 'options',
|
|
465
|
+
title: 'Options',
|
|
466
|
+
collapsible: true,
|
|
467
|
+
fields: ['READ_ONLY', 'INCLUDE_PUBLIC', 'INCLUDE_NOT_OWNED']
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: 'deploy',
|
|
471
|
+
title: 'Déploiement & test',
|
|
472
|
+
collapsible: true,
|
|
473
|
+
fields: ['LOCAL_DEPLOY', 'SSH_HOST', 'SSH_USER', 'SSH_PORT', 'SSH_KEY_PATH', 'SSH_REMOTE_PATH', 'TEST_COMMAND']
|
|
474
|
+
}
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
// État déplié/replié des sections repliables (false = replié par défaut)
|
|
478
|
+
const sectionState = { options: false, deploy: false };
|
|
479
|
+
|
|
480
|
+
// Champs propres à la connexion SSH : masqués quand LOCAL_DEPLOY est actif
|
|
481
|
+
const sshOnlyFields = ['SSH_HOST', 'SSH_USER', 'SSH_PORT', 'SSH_KEY_PATH'];
|
|
482
|
+
|
|
268
483
|
// État de l'application
|
|
269
484
|
let currentConfig = {};
|
|
270
485
|
let currentConfigName = '';
|
|
@@ -299,11 +514,11 @@
|
|
|
299
514
|
}
|
|
300
515
|
|
|
301
516
|
function renderConfigSelector() {
|
|
302
|
-
configSelector.innerHTML = '<option value=""
|
|
303
|
-
configNames.map(name =>
|
|
517
|
+
configSelector.innerHTML = '<option value="">— Sélectionner —</option>' +
|
|
518
|
+
configNames.map(name =>
|
|
304
519
|
`<option value="${name}" ${name === currentConfigName ? 'selected' : ''}>${name}</option>`
|
|
305
520
|
).join('');
|
|
306
|
-
|
|
521
|
+
|
|
307
522
|
deleteConfigBtn.disabled = !currentConfigName || configNames.length <= 1;
|
|
308
523
|
}
|
|
309
524
|
|
|
@@ -341,6 +556,13 @@
|
|
|
341
556
|
TARGET_REPO: '',
|
|
342
557
|
TARGET_BRANCH: '',
|
|
343
558
|
BASE_BRANCH: '',
|
|
559
|
+
LOCAL_DEPLOY: false,
|
|
560
|
+
SSH_HOST: '',
|
|
561
|
+
SSH_USER: '',
|
|
562
|
+
SSH_PORT: '',
|
|
563
|
+
SSH_KEY_PATH: '',
|
|
564
|
+
SSH_REMOTE_PATH: '',
|
|
565
|
+
TEST_COMMAND: '',
|
|
344
566
|
READ_ONLY: currentConfig.READ_ONLY || false,
|
|
345
567
|
INCLUDE_PUBLIC: currentConfig.INCLUDE_PUBLIC || false,
|
|
346
568
|
INCLUDE_NOT_OWNED: currentConfig.INCLUDE_NOT_OWNED || false
|
|
@@ -409,7 +631,7 @@
|
|
|
409
631
|
async function fetchRepositories(searchTerm = '') {
|
|
410
632
|
const loadingIndicator = document.getElementById('REPO_LOADING_INDICATOR');
|
|
411
633
|
if (loadingIndicator) loadingIndicator.classList.remove('hidden');
|
|
412
|
-
|
|
634
|
+
|
|
413
635
|
try {
|
|
414
636
|
const url = api(`api/repositories${searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''}`);
|
|
415
637
|
const response = await fetch(url);
|
|
@@ -434,13 +656,13 @@
|
|
|
434
656
|
resultsContainer.innerHTML = selectState.repos.map((repo, index) => `
|
|
435
657
|
<div class="search-result-item ${repo === currentRepo ? 'selected' : ''}" data-index="${index}" onclick="selectRepo('${repo}')">
|
|
436
658
|
${repo}
|
|
437
|
-
${repo === currentRepo ? '<span class="float-right
|
|
659
|
+
${repo === currentRepo ? '<span class="float-right">✓</span>' : ''}
|
|
438
660
|
</div>
|
|
439
661
|
`).join('');
|
|
440
662
|
resultsContainer.classList.remove('hidden');
|
|
441
663
|
activeIndex = -1;
|
|
442
664
|
} else {
|
|
443
|
-
resultsContainer.innerHTML = '<div class="
|
|
665
|
+
resultsContainer.innerHTML = '<div class="search-result-item">Aucun résultat</div>';
|
|
444
666
|
resultsContainer.classList.remove('hidden');
|
|
445
667
|
}
|
|
446
668
|
}
|
|
@@ -450,18 +672,18 @@
|
|
|
450
672
|
const searchInput = document.getElementById('REPO_SEARCH');
|
|
451
673
|
const hiddenInput = document.getElementById('TARGET_REPO');
|
|
452
674
|
const resultsContainer = document.getElementById('REPO_RESULTS');
|
|
453
|
-
|
|
675
|
+
|
|
454
676
|
if (searchInput) searchInput.value = repo;
|
|
455
677
|
if (hiddenInput) {
|
|
456
678
|
hiddenInput.value = repo;
|
|
457
679
|
currentConfig.TARGET_REPO = repo;
|
|
458
680
|
}
|
|
459
681
|
if (resultsContainer) resultsContainer.classList.add('hidden');
|
|
460
|
-
|
|
682
|
+
|
|
461
683
|
// Réinitialiser les branches lors du changement de repository
|
|
462
684
|
currentConfig.BASE_BRANCH = '';
|
|
463
685
|
currentConfig.TARGET_BRANCH = '';
|
|
464
|
-
|
|
686
|
+
|
|
465
687
|
// Charger les branches pour ce repo (renderInputs is called inside fetchBranches)
|
|
466
688
|
await fetchBranches(repo);
|
|
467
689
|
};
|
|
@@ -469,11 +691,11 @@
|
|
|
469
691
|
// Fetch branches for a repository
|
|
470
692
|
async function fetchBranches(repo, preserveConfig = false) {
|
|
471
693
|
if (!repo) return [];
|
|
472
|
-
|
|
694
|
+
|
|
473
695
|
selectState.loadingBranches[repo] = true;
|
|
474
696
|
// Use renderInputs directly to avoid overwriting currentConfig from DOM
|
|
475
697
|
renderInputs(currentConfig);
|
|
476
|
-
|
|
698
|
+
|
|
477
699
|
try {
|
|
478
700
|
const url = api(`api/branches?repo=${encodeURIComponent(repo)}`);
|
|
479
701
|
const response = await fetch(url);
|
|
@@ -492,42 +714,42 @@
|
|
|
492
714
|
|
|
493
715
|
// Render select field
|
|
494
716
|
function renderSelectField(field, value, config) {
|
|
495
|
-
let options = '<option value=""
|
|
496
|
-
|
|
717
|
+
let options = '<option value="">— Sélectionner une option —</option>';
|
|
718
|
+
|
|
497
719
|
if (field.id === 'TARGET_REPO') {
|
|
498
720
|
if (selectState.loadingRepos && selectState.repos.length === 0) {
|
|
499
721
|
return `
|
|
500
|
-
<div
|
|
501
|
-
<label for="${field.id}" class="
|
|
502
|
-
<div id="${field.id}_loading" class="
|
|
503
|
-
<div class="
|
|
722
|
+
<div>
|
|
723
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
724
|
+
<div id="${field.id}_loading" class="field-input flex items-center gap-2 text-dim">
|
|
725
|
+
<div class="spinner"></div>
|
|
504
726
|
Chargement des dépôts...
|
|
505
727
|
</div>
|
|
506
728
|
</div>
|
|
507
729
|
`;
|
|
508
730
|
}
|
|
509
|
-
|
|
510
|
-
options += selectState.repos.map(repo =>
|
|
731
|
+
|
|
732
|
+
options += selectState.repos.map(repo =>
|
|
511
733
|
`<option value="${repo}" ${value === repo ? 'selected' : ''}>${repo}</option>`
|
|
512
734
|
).join('');
|
|
513
|
-
|
|
735
|
+
|
|
514
736
|
if (value && !selectState.repos.includes(value)) {
|
|
515
737
|
options += `<option value="${value}" selected>${value}</option>`;
|
|
516
738
|
}
|
|
517
739
|
|
|
518
740
|
return `
|
|
519
|
-
<div
|
|
520
|
-
<label for="${field.id}" class="
|
|
741
|
+
<div>
|
|
742
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
521
743
|
<div class="relative">
|
|
522
744
|
<div class="relative">
|
|
523
745
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
524
|
-
<svg class="h-4 w-4 text-
|
|
746
|
+
<svg class="h-4 w-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
525
747
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
526
748
|
</svg>
|
|
527
749
|
</div>
|
|
528
|
-
<input type="text" id="REPO_SEARCH" placeholder="Rechercher un dépôt..." class="
|
|
750
|
+
<input type="text" id="REPO_SEARCH" placeholder="Rechercher un dépôt..." class="field-input" style="padding-left:2.25rem" autocomplete="off" value="${value || ''}">
|
|
529
751
|
<div id="REPO_LOADING_INDICATOR" class="absolute inset-y-0 right-0 pr-3 flex items-center hidden">
|
|
530
|
-
<div class="
|
|
752
|
+
<div class="spinner"></div>
|
|
531
753
|
</div>
|
|
532
754
|
</div>
|
|
533
755
|
<div id="REPO_RESULTS" class="search-results hidden"></div>
|
|
@@ -538,29 +760,29 @@
|
|
|
538
760
|
} else if (field.id === 'BASE_BRANCH') {
|
|
539
761
|
const currentRepo = config.TARGET_REPO;
|
|
540
762
|
const branches = selectState.branches[currentRepo] || [];
|
|
541
|
-
|
|
763
|
+
|
|
542
764
|
if (selectState.loadingBranches[currentRepo]) {
|
|
543
765
|
return `
|
|
544
|
-
<div
|
|
545
|
-
<label for="${field.id}" class="
|
|
546
|
-
<div id="${field.id}_loading" class="
|
|
547
|
-
<div class="
|
|
766
|
+
<div>
|
|
767
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
768
|
+
<div id="${field.id}_loading" class="field-input flex items-center gap-2 text-dim">
|
|
769
|
+
<div class="spinner"></div>
|
|
548
770
|
Chargement des branches...
|
|
549
771
|
</div>
|
|
550
772
|
</div>
|
|
551
773
|
`;
|
|
552
774
|
}
|
|
553
|
-
|
|
775
|
+
|
|
554
776
|
// Recréer les options avec l'option par défaut sélectionnée si value est vide
|
|
555
|
-
let branchOptions = `<option value="" ${!value ? 'selected' : ''}
|
|
777
|
+
let branchOptions = `<option value="" ${!value ? 'selected' : ''}>— Choisir une branche —</option>`;
|
|
556
778
|
branchOptions += branches.map(branch =>
|
|
557
779
|
`<option value="${branch}" ${value === branch ? 'selected' : ''}>${branch}</option>`
|
|
558
780
|
).join('');
|
|
559
|
-
|
|
781
|
+
|
|
560
782
|
return `
|
|
561
|
-
<div
|
|
562
|
-
<label for="${field.id}" class="
|
|
563
|
-
<select id="${field.id}" class="
|
|
783
|
+
<div>
|
|
784
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
785
|
+
<select id="${field.id}" class="field-select">
|
|
564
786
|
${branchOptions}
|
|
565
787
|
</select>
|
|
566
788
|
</div>
|
|
@@ -568,44 +790,44 @@
|
|
|
568
790
|
} else if (field.id === 'TARGET_BRANCH') {
|
|
569
791
|
const currentRepo = config.TARGET_REPO;
|
|
570
792
|
const branches = selectState.branches[currentRepo] || [];
|
|
571
|
-
|
|
793
|
+
|
|
572
794
|
if (selectState.loadingBranches[currentRepo]) {
|
|
573
795
|
return `
|
|
574
|
-
<div
|
|
575
|
-
<label for="${field.id}" class="
|
|
576
|
-
<div id="${field.id}_loading" class="
|
|
577
|
-
<div class="
|
|
796
|
+
<div>
|
|
797
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
798
|
+
<div id="${field.id}_loading" class="field-input flex items-center gap-2 text-dim">
|
|
799
|
+
<div class="spinner"></div>
|
|
578
800
|
Chargement des branches...
|
|
579
801
|
</div>
|
|
580
802
|
</div>
|
|
581
803
|
`;
|
|
582
804
|
}
|
|
583
|
-
|
|
584
|
-
let selectOptions = '<option value=""
|
|
805
|
+
|
|
806
|
+
let selectOptions = '<option value="">— Créer une nouvelle branche —</option>';
|
|
585
807
|
selectOptions += branches.map(branch =>
|
|
586
808
|
`<option value="${branch}" ${value === branch ? 'selected' : ''}>${branch}</option>`
|
|
587
809
|
).join('');
|
|
588
|
-
|
|
810
|
+
|
|
589
811
|
const isCustom = value && !branches.includes(value);
|
|
590
812
|
const shouldShowCustom = isCustom || value === '';
|
|
591
|
-
|
|
813
|
+
|
|
592
814
|
return `
|
|
593
|
-
<div
|
|
594
|
-
<label for="${field.id}" class="
|
|
815
|
+
<div>
|
|
816
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
595
817
|
<div class="space-y-2">
|
|
596
|
-
<select id="${field.id}" class="
|
|
818
|
+
<select id="${field.id}" class="field-select">
|
|
597
819
|
${selectOptions}
|
|
598
820
|
</select>
|
|
599
|
-
<input type="text" id="${field.id}_custom" placeholder="Entrez le nom de la nouvelle branche" value="${isCustom ? value : ''}" class="
|
|
821
|
+
<input type="text" id="${field.id}_custom" placeholder="Entrez le nom de la nouvelle branche" value="${isCustom ? value : ''}" class="field-input ${shouldShowCustom ? '' : 'hidden'}">
|
|
600
822
|
</div>
|
|
601
823
|
</div>
|
|
602
824
|
`;
|
|
603
825
|
}
|
|
604
|
-
|
|
826
|
+
|
|
605
827
|
return `
|
|
606
|
-
<div
|
|
607
|
-
<label for="${field.id}" class="
|
|
608
|
-
<select id="${field.id}" class="
|
|
828
|
+
<div>
|
|
829
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
830
|
+
<select id="${field.id}" class="field-select">
|
|
609
831
|
${options}
|
|
610
832
|
</select>
|
|
611
833
|
</div>
|
|
@@ -624,7 +846,7 @@
|
|
|
624
846
|
throw new Error(data.error || 'Erreur lors du chargement');
|
|
625
847
|
}
|
|
626
848
|
currentConfig = await response.json();
|
|
627
|
-
|
|
849
|
+
|
|
628
850
|
// Afficher le nom de l'agent si défini
|
|
629
851
|
const agentBadge = document.getElementById('agentNameBadge');
|
|
630
852
|
if (currentConfig.AGENT_NAME) {
|
|
@@ -633,60 +855,92 @@
|
|
|
633
855
|
} else {
|
|
634
856
|
agentBadge.classList.add('hidden');
|
|
635
857
|
}
|
|
636
|
-
|
|
858
|
+
|
|
637
859
|
// Le token est désormais côté serveur, on tente de charger les repos directement
|
|
638
860
|
await fetchRepositories();
|
|
639
861
|
if (currentConfig.TARGET_REPO) {
|
|
640
862
|
await fetchBranches(currentConfig.TARGET_REPO);
|
|
641
863
|
}
|
|
642
|
-
|
|
864
|
+
|
|
643
865
|
renderInputs(currentConfig);
|
|
644
866
|
} catch (error) {
|
|
645
867
|
showStatus(error.message || 'Erreur lors du chargement de la configuration', 'error');
|
|
646
868
|
}
|
|
647
869
|
}
|
|
648
870
|
|
|
649
|
-
// Render
|
|
871
|
+
// Render a single field based on its type
|
|
872
|
+
function renderField(field, config) {
|
|
873
|
+
if (field.type === 'slider') {
|
|
874
|
+
return `
|
|
875
|
+
<div class="toggle-row">
|
|
876
|
+
<label for="${field.id}" class="text">${field.label}</label>
|
|
877
|
+
<label class="switch">
|
|
878
|
+
<input type="checkbox" id="${field.id}" ${config[field.id] === 'true' || config[field.id] === true ? 'checked' : ''}>
|
|
879
|
+
<span class="slider"></span>
|
|
880
|
+
</label>
|
|
881
|
+
</div>
|
|
882
|
+
`;
|
|
883
|
+
}
|
|
884
|
+
if (field.type === 'checkbox') {
|
|
885
|
+
return `
|
|
886
|
+
<div class="toggle-row">
|
|
887
|
+
<label for="${field.id}" class="text">${field.label}</label>
|
|
888
|
+
<input type="checkbox" id="${field.id}" ${config[field.id] === 'true' || config[field.id] === true ? 'checked' : ''} class="w-4 h-4">
|
|
889
|
+
</div>
|
|
890
|
+
`;
|
|
891
|
+
}
|
|
892
|
+
if (field.type === 'select') {
|
|
893
|
+
return renderSelectField(field, config[field.id] || '', config);
|
|
894
|
+
}
|
|
895
|
+
const wrapperClass = sshOnlyFields.includes(field.id) ? 'ssh-only' : '';
|
|
896
|
+
return `
|
|
897
|
+
<div class="${wrapperClass}">
|
|
898
|
+
<label for="${field.id}" class="field-label">${field.label}</label>
|
|
899
|
+
<input type="${field.type}" id="${field.id}" value="${config[field.id] || ''}" placeholder="${field.placeholder}" class="field-input">
|
|
900
|
+
</div>
|
|
901
|
+
`;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Render inputs grouped into (collapsible) sections
|
|
650
905
|
function renderInputs(config) {
|
|
651
906
|
// CLONE_DIR en lecture seule (variable d'environnement)
|
|
652
907
|
const cloneDirInfo = config.CLONE_DIR
|
|
653
|
-
? `<div class="
|
|
654
|
-
<span class="text-
|
|
655
|
-
<span class="text-
|
|
908
|
+
? `<div class="info-row">
|
|
909
|
+
<span class="text-xs font-medium text-dim">Répertoire de clonage</span>
|
|
910
|
+
<span class="text-xs text-main font-mono">${config.CLONE_DIR}</span>
|
|
656
911
|
</div>`
|
|
657
912
|
: '';
|
|
658
913
|
|
|
659
|
-
inputsContainer.innerHTML =
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
<input type="checkbox" id="${field.id}" ${config[field.id] === 'true' || config[field.id] === true ? 'checked' : ''}>
|
|
666
|
-
<span class="slider"></span>
|
|
667
|
-
</label>
|
|
668
|
-
</div>
|
|
669
|
-
`;
|
|
670
|
-
}
|
|
671
|
-
if (field.type === 'checkbox') {
|
|
914
|
+
inputsContainer.innerHTML = sections.map(section => {
|
|
915
|
+
const bodyHtml = section.fields
|
|
916
|
+
.map(fid => fieldsById[fid] ? renderField(fieldsById[fid], config) : '')
|
|
917
|
+
.join('');
|
|
918
|
+
|
|
919
|
+
if (!section.collapsible) {
|
|
672
920
|
return `
|
|
673
|
-
<div class="
|
|
674
|
-
<
|
|
675
|
-
<
|
|
921
|
+
<div class="form-section">
|
|
922
|
+
<div class="section-static-header">${section.title}</div>
|
|
923
|
+
<div class="section-body space-y-4">${bodyHtml}</div>
|
|
676
924
|
</div>
|
|
677
925
|
`;
|
|
678
926
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
927
|
+
|
|
928
|
+
const open = !!sectionState[section.id];
|
|
682
929
|
return `
|
|
683
|
-
<div class="
|
|
684
|
-
<
|
|
685
|
-
|
|
930
|
+
<div class="form-section">
|
|
931
|
+
<button type="button" class="section-header ${open ? 'open' : ''}" data-section="${section.id}">
|
|
932
|
+
<span>${section.title}</span>
|
|
933
|
+
<svg class="chevron w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
934
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
935
|
+
</svg>
|
|
936
|
+
</button>
|
|
937
|
+
<div class="section-body space-y-4 ${open ? '' : 'hidden'}" data-section-body="${section.id}">
|
|
938
|
+
${bodyHtml}
|
|
939
|
+
</div>
|
|
686
940
|
</div>
|
|
687
941
|
`;
|
|
688
942
|
}).join('') + cloneDirInfo;
|
|
689
|
-
|
|
943
|
+
|
|
690
944
|
if (deleteWorkingDirBtn) {
|
|
691
945
|
deleteWorkingDirBtn.disabled = !config.REPO_PATH;
|
|
692
946
|
}
|
|
@@ -694,7 +948,7 @@
|
|
|
694
948
|
if (cloneBtn) {
|
|
695
949
|
cloneBtn.disabled = !config.TARGET_REPO || !config.TARGET_BRANCH;
|
|
696
950
|
}
|
|
697
|
-
|
|
951
|
+
|
|
698
952
|
setupEventListeners();
|
|
699
953
|
}
|
|
700
954
|
|
|
@@ -722,7 +976,28 @@
|
|
|
722
976
|
const targetBranchSelect = document.getElementById('TARGET_BRANCH');
|
|
723
977
|
const repoSearch = document.getElementById('REPO_SEARCH');
|
|
724
978
|
const resultsContainer = document.getElementById('REPO_RESULTS');
|
|
725
|
-
|
|
979
|
+
|
|
980
|
+
// Sections repliables : bascule sans re-render pour préserver les saisies
|
|
981
|
+
inputsContainer.querySelectorAll('.section-header').forEach(header => {
|
|
982
|
+
header.addEventListener('click', () => {
|
|
983
|
+
const id = header.dataset.section;
|
|
984
|
+
sectionState[id] = !sectionState[id];
|
|
985
|
+
header.classList.toggle('open', sectionState[id]);
|
|
986
|
+
const body = inputsContainer.querySelector(`[data-section-body="${id}"]`);
|
|
987
|
+
if (body) body.classList.toggle('hidden', !sectionState[id]);
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// Mode de déploiement : masque les champs SSH quand le déploiement local est actif
|
|
992
|
+
const localDeployToggle = document.getElementById('LOCAL_DEPLOY');
|
|
993
|
+
if (localDeployToggle) {
|
|
994
|
+
const applyDeployMode = (isLocal) => {
|
|
995
|
+
inputsContainer.querySelectorAll('.ssh-only').forEach(el => el.classList.toggle('hidden', isLocal));
|
|
996
|
+
};
|
|
997
|
+
applyDeployMode(localDeployToggle.checked);
|
|
998
|
+
localDeployToggle.addEventListener('change', () => applyDeployMode(localDeployToggle.checked));
|
|
999
|
+
}
|
|
1000
|
+
|
|
726
1001
|
if (repoSearch) {
|
|
727
1002
|
// Focus: ouvre la liste avec ce qui est déjà là
|
|
728
1003
|
repoSearch.addEventListener('focus', () => {
|
|
@@ -793,7 +1068,7 @@
|
|
|
793
1068
|
else config[f.id] = inp.value;
|
|
794
1069
|
}
|
|
795
1070
|
});
|
|
796
|
-
|
|
1071
|
+
|
|
797
1072
|
try {
|
|
798
1073
|
await fetch(api('api/config'), {
|
|
799
1074
|
method: 'POST',
|
|
@@ -807,12 +1082,12 @@
|
|
|
807
1082
|
});
|
|
808
1083
|
}
|
|
809
1084
|
});
|
|
810
|
-
|
|
1085
|
+
|
|
811
1086
|
if (targetBranchSelect) {
|
|
812
1087
|
targetBranchSelect.addEventListener('change', () => {
|
|
813
1088
|
const customInput = document.getElementById('TARGET_BRANCH_custom');
|
|
814
1089
|
if (!customInput) return;
|
|
815
|
-
|
|
1090
|
+
|
|
816
1091
|
const isNewBranch = targetBranchSelect.value === '';
|
|
817
1092
|
customInput.classList.toggle('hidden', !isNewBranch);
|
|
818
1093
|
if (isNewBranch) {
|
|
@@ -827,19 +1102,16 @@
|
|
|
827
1102
|
function showStatus(message, type) {
|
|
828
1103
|
const container = document.getElementById('toast-container');
|
|
829
1104
|
const toast = document.createElement('div');
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
: '<svg class="w-5 h-5 mr-3 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>';
|
|
837
|
-
|
|
838
|
-
toast.className = `toast flex items-center p-4 rounded-lg border shadow-xl ${bgColor} ${borderColor} ${textColor}`;
|
|
1105
|
+
|
|
1106
|
+
const icon = type === 'success'
|
|
1107
|
+
? '<svg class="w-4 h-4 shrink-0" style="color:var(--accent)" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>'
|
|
1108
|
+
: '<svg class="w-4 h-4 shrink-0" style="color:var(--danger)" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>';
|
|
1109
|
+
|
|
1110
|
+
toast.className = `toast ${type === 'success' ? 'toast-success' : 'toast-error'}`;
|
|
839
1111
|
toast.innerHTML = `${icon}<span class="flex-1">${message}</span>`;
|
|
840
|
-
|
|
1112
|
+
|
|
841
1113
|
container.appendChild(toast);
|
|
842
|
-
|
|
1114
|
+
|
|
843
1115
|
// Supprimer le toast après l'animation (5s total)
|
|
844
1116
|
setTimeout(() => {
|
|
845
1117
|
toast.remove();
|
|
@@ -898,7 +1170,7 @@
|
|
|
898
1170
|
try {
|
|
899
1171
|
restartBtn.disabled = true;
|
|
900
1172
|
restartBtn.innerHTML = '<span>Redémarrage en cours...</span>';
|
|
901
|
-
|
|
1173
|
+
|
|
902
1174
|
const response = await fetch(api('api/restart'), { method: 'POST' });
|
|
903
1175
|
|
|
904
1176
|
if (response.ok) {
|
|
@@ -923,7 +1195,7 @@
|
|
|
923
1195
|
try {
|
|
924
1196
|
stopBtn.disabled = true;
|
|
925
1197
|
stopBtn.innerHTML = '<span>Arrêt en cours...</span>';
|
|
926
|
-
|
|
1198
|
+
|
|
927
1199
|
const response = await fetch(api('api/stop'), { method: 'POST' });
|
|
928
1200
|
|
|
929
1201
|
if (response.ok) {
|
|
@@ -947,7 +1219,7 @@
|
|
|
947
1219
|
try {
|
|
948
1220
|
deleteWorkingDirBtn.disabled = true;
|
|
949
1221
|
deleteWorkingDirBtn.innerHTML = '<span>Suppression...</span>';
|
|
950
|
-
|
|
1222
|
+
|
|
951
1223
|
const response = await fetch(api('api/delete-working-dir'), { method: 'POST' });
|
|
952
1224
|
|
|
953
1225
|
if (response.ok) {
|
|
@@ -975,7 +1247,7 @@
|
|
|
975
1247
|
try {
|
|
976
1248
|
cloneBtn.disabled = true;
|
|
977
1249
|
cloneBtn.innerHTML = '<span>Clonage...</span>';
|
|
978
|
-
|
|
1250
|
+
|
|
979
1251
|
const response = await fetch(api('api/clone'), { method: 'POST' });
|
|
980
1252
|
|
|
981
1253
|
if (response.ok) {
|