restricted-github-mcp 1.1.39 → 1.1.41
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/dist/config-server.d.ts.map +1 -1
- package/dist/config-server.js +94 -2
- package/dist/config-server.js.map +1 -1
- package/dist/utils/config.d.ts +84 -11
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +281 -44
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
- package/public/index.html +992 -841
package/public/index.html
CHANGED
|
@@ -1,841 +1,992 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="fr" class="dark">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>GitHub MCP</title>
|
|
7
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
-
<style>
|
|
9
|
-
body {
|
|
10
|
-
background-color: #0f172a;
|
|
11
|
-
color: #f8fafc;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* Layout */
|
|
15
|
-
.app-container {
|
|
16
|
-
display: grid;
|
|
17
|
-
grid-template-columns: 320px 1fr;
|
|
18
|
-
gap: 2rem;
|
|
19
|
-
max-width: 1200px;
|
|
20
|
-
margin: 0 auto;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@media (max-width: 768px) {
|
|
24
|
-
.app-container {
|
|
25
|
-
grid-template-columns: 1fr;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/* iPhone style slider */
|
|
30
|
-
.switch {
|
|
31
|
-
position: relative;
|
|
32
|
-
display: inline-block;
|
|
33
|
-
width: 44px;
|
|
34
|
-
height: 24px;
|
|
35
|
-
flex-shrink: 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.switch input {
|
|
39
|
-
opacity: 0;
|
|
40
|
-
width: 0;
|
|
41
|
-
height: 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.slider {
|
|
45
|
-
position: absolute;
|
|
46
|
-
cursor: pointer;
|
|
47
|
-
top: 0;
|
|
48
|
-
left: 0;
|
|
49
|
-
right: 0;
|
|
50
|
-
bottom: 0;
|
|
51
|
-
background-color: #334155;
|
|
52
|
-
transition: .4s;
|
|
53
|
-
border-radius: 24px;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.slider:before {
|
|
57
|
-
position: absolute;
|
|
58
|
-
content: "";
|
|
59
|
-
height: 18px;
|
|
60
|
-
width: 18px;
|
|
61
|
-
left: 3px;
|
|
62
|
-
bottom: 3px;
|
|
63
|
-
background-color: white;
|
|
64
|
-
transition: .4s;
|
|
65
|
-
border-radius: 50%;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
input:checked + .slider {
|
|
69
|
-
background-color: #2563eb;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
input:checked + .slider:before {
|
|
73
|
-
transform: translateX(20px);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* Custom dropdown styles */
|
|
77
|
-
.search-results {
|
|
78
|
-
position: absolute;
|
|
79
|
-
top: 100%;
|
|
80
|
-
left: 0;
|
|
81
|
-
right: 0;
|
|
82
|
-
z-index: 50;
|
|
83
|
-
background-color: #0f172a;
|
|
84
|
-
border: 1px solid #334155;
|
|
85
|
-
border-radius: 0.5rem;
|
|
86
|
-
margin-top: 0.25rem;
|
|
87
|
-
max-height: 15rem;
|
|
88
|
-
overflow-y: auto;
|
|
89
|
-
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.search-result-item {
|
|
93
|
-
padding: 0.5rem 1rem;
|
|
94
|
-
cursor: pointer;
|
|
95
|
-
transition: background-color 0.2s;
|
|
96
|
-
font-size: 0.875rem;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.search-result-item:hover {
|
|
100
|
-
background-color: #1e293b;
|
|
101
|
-
color: #60a5fa;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.search-result-item.active {
|
|
105
|
-
background-color: #1e293b;
|
|
106
|
-
color: #60a5fa;
|
|
107
|
-
outline: 1px solid #3b82f6;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.search-result-item.selected {
|
|
111
|
-
background-color: #2563eb;
|
|
112
|
-
color: white;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/* Toasts */
|
|
116
|
-
.toast {
|
|
117
|
-
animation: slideIn 0.3s ease-out forwards, fadeOut 0.3s ease-in forwards 4.7s;
|
|
118
|
-
max-width: 24rem;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@keyframes slideIn {
|
|
122
|
-
from { transform: translateX(100%); opacity: 0; }
|
|
123
|
-
to { transform: translateX(0); opacity: 1; }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
@keyframes fadeOut {
|
|
127
|
-
from { opacity: 1; }
|
|
128
|
-
to { opacity: 0; }
|
|
129
|
-
}
|
|
130
|
-
</style>
|
|
131
|
-
</head>
|
|
132
|
-
<body class="min-h-screen p-8">
|
|
133
|
-
<div class="app-container">
|
|
134
|
-
<!-- Sidebar -->
|
|
135
|
-
<aside class="space-y-6">
|
|
136
|
-
<div class="bg-slate-800 rounded-xl shadow-2xl p-6 border border-slate-700 sticky top-8">
|
|
137
|
-
<h2 class="text-xl font-bold mb-
|
|
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="
|
|
140
|
-
</svg>
|
|
141
|
-
|
|
142
|
-
</h2>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</
|
|
180
|
-
</
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
</label>
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>GitHub MCP</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
background-color: #0f172a;
|
|
11
|
+
color: #f8fafc;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Layout */
|
|
15
|
+
.app-container {
|
|
16
|
+
display: grid;
|
|
17
|
+
grid-template-columns: 320px 1fr;
|
|
18
|
+
gap: 2rem;
|
|
19
|
+
max-width: 1200px;
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (max-width: 768px) {
|
|
24
|
+
.app-container {
|
|
25
|
+
grid-template-columns: 1fr;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* iPhone style slider */
|
|
30
|
+
.switch {
|
|
31
|
+
position: relative;
|
|
32
|
+
display: inline-block;
|
|
33
|
+
width: 44px;
|
|
34
|
+
height: 24px;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.switch input {
|
|
39
|
+
opacity: 0;
|
|
40
|
+
width: 0;
|
|
41
|
+
height: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.slider {
|
|
45
|
+
position: absolute;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
top: 0;
|
|
48
|
+
left: 0;
|
|
49
|
+
right: 0;
|
|
50
|
+
bottom: 0;
|
|
51
|
+
background-color: #334155;
|
|
52
|
+
transition: .4s;
|
|
53
|
+
border-radius: 24px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.slider:before {
|
|
57
|
+
position: absolute;
|
|
58
|
+
content: "";
|
|
59
|
+
height: 18px;
|
|
60
|
+
width: 18px;
|
|
61
|
+
left: 3px;
|
|
62
|
+
bottom: 3px;
|
|
63
|
+
background-color: white;
|
|
64
|
+
transition: .4s;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
input:checked + .slider {
|
|
69
|
+
background-color: #2563eb;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
input:checked + .slider:before {
|
|
73
|
+
transform: translateX(20px);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Custom dropdown styles */
|
|
77
|
+
.search-results {
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 100%;
|
|
80
|
+
left: 0;
|
|
81
|
+
right: 0;
|
|
82
|
+
z-index: 50;
|
|
83
|
+
background-color: #0f172a;
|
|
84
|
+
border: 1px solid #334155;
|
|
85
|
+
border-radius: 0.5rem;
|
|
86
|
+
margin-top: 0.25rem;
|
|
87
|
+
max-height: 15rem;
|
|
88
|
+
overflow-y: auto;
|
|
89
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.search-result-item {
|
|
93
|
+
padding: 0.5rem 1rem;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
transition: background-color 0.2s;
|
|
96
|
+
font-size: 0.875rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.search-result-item:hover {
|
|
100
|
+
background-color: #1e293b;
|
|
101
|
+
color: #60a5fa;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.search-result-item.active {
|
|
105
|
+
background-color: #1e293b;
|
|
106
|
+
color: #60a5fa;
|
|
107
|
+
outline: 1px solid #3b82f6;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.search-result-item.selected {
|
|
111
|
+
background-color: #2563eb;
|
|
112
|
+
color: white;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Toasts */
|
|
116
|
+
.toast {
|
|
117
|
+
animation: slideIn 0.3s ease-out forwards, fadeOut 0.3s ease-in forwards 4.7s;
|
|
118
|
+
max-width: 24rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@keyframes slideIn {
|
|
122
|
+
from { transform: translateX(100%); opacity: 0; }
|
|
123
|
+
to { transform: translateX(0); opacity: 1; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@keyframes fadeOut {
|
|
127
|
+
from { opacity: 1; }
|
|
128
|
+
to { opacity: 0; }
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body class="min-h-screen p-8">
|
|
133
|
+
<div class="app-container">
|
|
134
|
+
<!-- Sidebar -->
|
|
135
|
+
<aside class="space-y-6">
|
|
136
|
+
<div class="bg-slate-800 rounded-xl shadow-2xl p-6 border border-slate-700 sticky top-8">
|
|
137
|
+
<h2 class="text-xl font-bold mb-4 text-blue-400 flex items-center">
|
|
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>
|
|
143
|
+
|
|
144
|
+
<!-- Configuration Selector -->
|
|
145
|
+
<div class="mb-6 space-y-2">
|
|
146
|
+
<div class="flex gap-2">
|
|
147
|
+
<select id="configSelector" class="flex-1 px-3 py-2 bg-slate-900 border border-slate-600 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
|
148
|
+
<option value="">-- Sélectionner --</option>
|
|
149
|
+
</select>
|
|
150
|
+
<button type="button" id="newConfigBtn" class="px-3 py-2 bg-green-600 hover:bg-green-700 rounded-lg transition duration-200" title="Nouvelle configuration">
|
|
151
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
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>
|
|
171
|
+
</svg>
|
|
172
|
+
<span>Sauvegarder</span>
|
|
173
|
+
</button>
|
|
174
|
+
|
|
175
|
+
<button type="button" id="cloneBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-700 disabled:cursor-not-allowed text-white font-bold py-3 px-4 rounded-lg transition duration-200 shadow-lg flex justify-center items-center">
|
|
176
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
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>
|
|
178
|
+
</svg>
|
|
179
|
+
<span>Cloner le dépôt</span>
|
|
180
|
+
</button>
|
|
181
|
+
|
|
182
|
+
<button type="button" id="restartBtn" class="w-full bg-amber-600 hover:bg-amber-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 shadow-lg flex justify-center items-center">
|
|
183
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
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>
|
|
188
|
+
|
|
189
|
+
<button type="button" id="deleteWorkingDirBtn" class="w-full bg-orange-600 hover:bg-orange-700 disabled:bg-slate-700 disabled:cursor-not-allowed text-white font-bold py-3 px-4 rounded-lg transition duration-200 shadow-lg flex justify-center items-center">
|
|
190
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
191
|
+
<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>
|
|
192
|
+
</svg>
|
|
193
|
+
<span>Nettoyer Repo</span>
|
|
194
|
+
</button>
|
|
195
|
+
|
|
196
|
+
<div class="border-t border-slate-700 pt-4">
|
|
197
|
+
<button type="button" id="stopBtn" class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 shadow-lg flex justify-center items-center">
|
|
198
|
+
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
199
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
200
|
+
</svg>
|
|
201
|
+
<span>Arrêter</span>
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</aside>
|
|
207
|
+
|
|
208
|
+
<!-- Main Content -->
|
|
209
|
+
<main class="bg-slate-800 rounded-xl shadow-2xl p-8 border border-slate-700">
|
|
210
|
+
<div class="flex items-center gap-4 mb-6">
|
|
211
|
+
<h1 class="text-3xl font-bold text-blue-400 flex items-center">
|
|
212
|
+
<svg class="w-8 h-8 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
213
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
|
214
|
+
</svg>
|
|
215
|
+
GitHub MCP
|
|
216
|
+
</h1>
|
|
217
|
+
<span id="agentNameBadge" class="hidden px-3 py-1 bg-blue-900/50 text-blue-300 text-sm font-medium rounded-full border border-blue-700"></span>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<form id="configForm" class="space-y-6">
|
|
221
|
+
<div class="space-y-4" id="inputs">
|
|
222
|
+
<!-- Inputs will be injected here -->
|
|
223
|
+
<div class="flex justify-center p-8">
|
|
224
|
+
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-400"></div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</form>
|
|
228
|
+
|
|
229
|
+
<div class="mt-8 text-sm text-slate-400 border-t border-slate-700 pt-4 italic">
|
|
230
|
+
Note: Le serveur MCP doit être redémarré pour appliquer les changements.
|
|
231
|
+
</div>
|
|
232
|
+
</main>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<!-- Toast Container -->
|
|
236
|
+
<div id="toast-container" class="fixed bottom-8 right-8 z-50 flex flex-col gap-3"></div>
|
|
237
|
+
|
|
238
|
+
<script>
|
|
239
|
+
// Détection automatique du base path pour fonctionner via proxy
|
|
240
|
+
const pathname = window.location.pathname;
|
|
241
|
+
const BASE_PATH = pathname.includes('.')
|
|
242
|
+
? pathname.replace(/\/[^/]*$/, '') // /github-mcp/index.html -> /github-mcp
|
|
243
|
+
: pathname.endsWith('/')
|
|
244
|
+
? pathname.slice(0, -1) // /github-mcp/ -> /github-mcp
|
|
245
|
+
: pathname === '/' ? '' : pathname; // /github-mcp -> /github-mcp, / -> ''
|
|
246
|
+
|
|
247
|
+
// Helper pour construire les URLs d'API
|
|
248
|
+
const api = (endpoint) => `${BASE_PATH}/${endpoint}`.replace(/\/+/g, '/');
|
|
249
|
+
|
|
250
|
+
const form = document.getElementById('configForm');
|
|
251
|
+
const inputsContainer = document.getElementById('inputs');
|
|
252
|
+
const restartBtn = document.getElementById('restartBtn');
|
|
253
|
+
const stopBtn = document.getElementById('stopBtn');
|
|
254
|
+
const deleteWorkingDirBtn = document.getElementById('deleteWorkingDirBtn');
|
|
255
|
+
const cloneBtn = document.getElementById('cloneBtn');
|
|
256
|
+
|
|
257
|
+
const fields = [
|
|
258
|
+
{ id: 'TARGET_REPO', label: 'Target Repository', type: 'select', placeholder: 'owner/repo' },
|
|
259
|
+
{ id: 'BASE_BRANCH', label: 'Base Branch', type: 'select', placeholder: 'main' },
|
|
260
|
+
{ id: 'TARGET_BRANCH', label: 'Target Branch', type: 'select', placeholder: 'feature-branch' },
|
|
261
|
+
{ id: 'PROJECT_PATH', label: 'Parent Projects Path', type: 'text', placeholder: 'C:\\Users\\...\\Documents\\code' },
|
|
262
|
+
{ id: 'READ_ONLY', label: 'Mode Lecture Seule', type: 'slider' },
|
|
263
|
+
{ id: 'INCLUDE_PUBLIC', label: 'Inclure les dépôts publics', type: 'slider' },
|
|
264
|
+
{ id: 'INCLUDE_NOT_OWNED', label: 'Inclure les dépôts dont je ne suis pas propriétaire', type: 'slider' }
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
// État de l'application
|
|
268
|
+
let currentConfig = {};
|
|
269
|
+
let currentConfigName = '';
|
|
270
|
+
let configNames = [];
|
|
271
|
+
const selectState = {
|
|
272
|
+
repos: [],
|
|
273
|
+
branches: {},
|
|
274
|
+
loadingRepos: false,
|
|
275
|
+
loadingBranches: {}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
let searchTimeout;
|
|
279
|
+
let activeIndex = -1;
|
|
280
|
+
|
|
281
|
+
// Configuration management
|
|
282
|
+
const configSelector = document.getElementById('configSelector');
|
|
283
|
+
const newConfigBtn = document.getElementById('newConfigBtn');
|
|
284
|
+
const deleteConfigBtn = document.getElementById('deleteConfigBtn');
|
|
285
|
+
|
|
286
|
+
async function fetchConfigs() {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch(api('api/configs'));
|
|
289
|
+
if (response.ok) {
|
|
290
|
+
const data = await response.json();
|
|
291
|
+
configNames = data.names || [];
|
|
292
|
+
currentConfigName = data.current || '';
|
|
293
|
+
renderConfigSelector();
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn('Error fetching configs:', error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function renderConfigSelector() {
|
|
301
|
+
configSelector.innerHTML = '<option value="">-- Sélectionner --</option>' +
|
|
302
|
+
configNames.map(name =>
|
|
303
|
+
`<option value="${name}" ${name === currentConfigName ? 'selected' : ''}>${name}</option>`
|
|
304
|
+
).join('');
|
|
305
|
+
|
|
306
|
+
deleteConfigBtn.disabled = !currentConfigName || configNames.length <= 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function activateConfig(name) {
|
|
310
|
+
if (!name) return;
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(api(`api/configs/${encodeURIComponent(name)}/activate`), {
|
|
313
|
+
method: 'POST'
|
|
314
|
+
});
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
currentConfigName = name;
|
|
317
|
+
// Reset branches state to force reload for new config
|
|
318
|
+
selectState.branches = {};
|
|
319
|
+
showStatus(`Configuration "${name}" activée`, 'success');
|
|
320
|
+
await fetchConfig();
|
|
321
|
+
} else {
|
|
322
|
+
throw new Error('Erreur lors de l\'activation');
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
showStatus(error.message, 'error');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function createNewConfig() {
|
|
330
|
+
const name = prompt('Nom de la nouvelle configuration:');
|
|
331
|
+
if (!name || !name.trim()) return;
|
|
332
|
+
|
|
333
|
+
const trimmedName = name.trim();
|
|
334
|
+
if (configNames.includes(trimmedName)) {
|
|
335
|
+
showStatus('Une configuration avec ce nom existe déjà', 'error');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const newConfig = {
|
|
340
|
+
TARGET_REPO: '',
|
|
341
|
+
TARGET_BRANCH: '',
|
|
342
|
+
BASE_BRANCH: '',
|
|
343
|
+
PROJECT_PATH: currentConfig.PROJECT_PATH || '',
|
|
344
|
+
READ_ONLY: currentConfig.READ_ONLY || false,
|
|
345
|
+
INCLUDE_PUBLIC: currentConfig.INCLUDE_PUBLIC || false,
|
|
346
|
+
INCLUDE_NOT_OWNED: currentConfig.INCLUDE_NOT_OWNED || false
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const response = await fetch(api('api/configs'), {
|
|
351
|
+
method: 'POST',
|
|
352
|
+
headers: { 'Content-Type': 'application/json' },
|
|
353
|
+
body: JSON.stringify({ name: trimmedName, config: newConfig })
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (response.ok) {
|
|
357
|
+
showStatus(`Configuration "${trimmedName}" créée (basée sur la configuration actuelle)`, 'success');
|
|
358
|
+
await fetchConfigs();
|
|
359
|
+
await activateConfig(trimmedName);
|
|
360
|
+
} else {
|
|
361
|
+
throw new Error('Erreur lors de la création');
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
showStatus(error.message, 'error');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function deleteCurrentConfig() {
|
|
369
|
+
if (!currentConfigName) return;
|
|
370
|
+
if (configNames.length <= 1) {
|
|
371
|
+
showStatus('Impossible de supprimer la dernière configuration', 'error');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!confirm(`Êtes-vous sûr de vouloir supprimer la configuration "${currentConfigName}" ?`)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const response = await fetch(api(`api/configs/${encodeURIComponent(currentConfigName)}`), {
|
|
381
|
+
method: 'DELETE'
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (response.ok) {
|
|
385
|
+
showStatus(`Configuration "${currentConfigName}" supprimée`, 'success');
|
|
386
|
+
await fetchConfigs();
|
|
387
|
+
// Activate first remaining config
|
|
388
|
+
if (configNames.length > 0) {
|
|
389
|
+
await activateConfig(configNames[0]);
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
throw new Error('Erreur lors de la suppression');
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
showStatus(error.message, 'error');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
configSelector.addEventListener('change', (e) => {
|
|
400
|
+
if (e.target.value) {
|
|
401
|
+
activateConfig(e.target.value);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
newConfigBtn.addEventListener('click', createNewConfig);
|
|
406
|
+
deleteConfigBtn.addEventListener('click', deleteCurrentConfig);
|
|
407
|
+
|
|
408
|
+
// Fetch repositories
|
|
409
|
+
async function fetchRepositories(searchTerm = '') {
|
|
410
|
+
const loadingIndicator = document.getElementById('REPO_LOADING_INDICATOR');
|
|
411
|
+
if (loadingIndicator) loadingIndicator.classList.remove('hidden');
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const url = api(`api/repositories${searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''}`);
|
|
415
|
+
const response = await fetch(url);
|
|
416
|
+
if (response.ok) {
|
|
417
|
+
selectState.repos = await response.json();
|
|
418
|
+
renderRepoResults();
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.warn('Error fetching repositories:', error);
|
|
422
|
+
} finally {
|
|
423
|
+
if (loadingIndicator) loadingIndicator.classList.add('hidden');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Affiche les résultats chargés dans le DOM
|
|
428
|
+
function renderRepoResults() {
|
|
429
|
+
const resultsContainer = document.getElementById('REPO_RESULTS');
|
|
430
|
+
if (!resultsContainer) return;
|
|
431
|
+
|
|
432
|
+
if (selectState.repos.length > 0) {
|
|
433
|
+
const currentRepo = document.getElementById('TARGET_REPO')?.value;
|
|
434
|
+
resultsContainer.innerHTML = selectState.repos.map((repo, index) => `
|
|
435
|
+
<div class="search-result-item ${repo === currentRepo ? 'selected' : ''}" data-index="${index}" onclick="selectRepo('${repo}')">
|
|
436
|
+
${repo}
|
|
437
|
+
${repo === currentRepo ? '<span class="float-right text-blue-400">✓</span>' : ''}
|
|
438
|
+
</div>
|
|
439
|
+
`).join('');
|
|
440
|
+
resultsContainer.classList.remove('hidden');
|
|
441
|
+
activeIndex = -1;
|
|
442
|
+
} else {
|
|
443
|
+
resultsContainer.innerHTML = '<div class="p-3 text-slate-500 text-sm">Aucun résultat</div>';
|
|
444
|
+
resultsContainer.classList.remove('hidden');
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Sélectionner un repo depuis la liste custom
|
|
449
|
+
window.selectRepo = async function(repo) {
|
|
450
|
+
const searchInput = document.getElementById('REPO_SEARCH');
|
|
451
|
+
const hiddenInput = document.getElementById('TARGET_REPO');
|
|
452
|
+
const resultsContainer = document.getElementById('REPO_RESULTS');
|
|
453
|
+
|
|
454
|
+
if (searchInput) searchInput.value = repo;
|
|
455
|
+
if (hiddenInput) {
|
|
456
|
+
hiddenInput.value = repo;
|
|
457
|
+
currentConfig.TARGET_REPO = repo;
|
|
458
|
+
}
|
|
459
|
+
if (resultsContainer) resultsContainer.classList.add('hidden');
|
|
460
|
+
|
|
461
|
+
// Réinitialiser les branches lors du changement de repository
|
|
462
|
+
currentConfig.BASE_BRANCH = '';
|
|
463
|
+
currentConfig.TARGET_BRANCH = '';
|
|
464
|
+
|
|
465
|
+
// Charger les branches pour ce repo (renderInputs is called inside fetchBranches)
|
|
466
|
+
await fetchBranches(repo);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Fetch branches for a repository
|
|
470
|
+
async function fetchBranches(repo, preserveConfig = false) {
|
|
471
|
+
if (!repo) return [];
|
|
472
|
+
|
|
473
|
+
selectState.loadingBranches[repo] = true;
|
|
474
|
+
// Use renderInputs directly to avoid overwriting currentConfig from DOM
|
|
475
|
+
renderInputs(currentConfig);
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const url = api(`api/branches?repo=${encodeURIComponent(repo)}`);
|
|
479
|
+
const response = await fetch(url);
|
|
480
|
+
if (response.ok) {
|
|
481
|
+
selectState.branches[repo] = await response.json();
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error('[fetchBranches] Fetch error:', error);
|
|
485
|
+
selectState.branches[repo] = [];
|
|
486
|
+
} finally {
|
|
487
|
+
selectState.loadingBranches[repo] = false;
|
|
488
|
+
// Use renderInputs directly to avoid overwriting currentConfig from DOM
|
|
489
|
+
renderInputs(currentConfig);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Render select field
|
|
494
|
+
function renderSelectField(field, value, config) {
|
|
495
|
+
let options = '<option value="">-- Sélectionner une option --</option>';
|
|
496
|
+
|
|
497
|
+
if (field.id === 'TARGET_REPO') {
|
|
498
|
+
if (selectState.loadingRepos && selectState.repos.length === 0) {
|
|
499
|
+
return `
|
|
500
|
+
<div class="space-y-1">
|
|
501
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
502
|
+
<div id="${field.id}_loading" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg text-slate-400 flex items-center">
|
|
503
|
+
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-400 mr-2"></div>
|
|
504
|
+
Chargement des dépôts...
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
options += selectState.repos.map(repo =>
|
|
511
|
+
`<option value="${repo}" ${value === repo ? 'selected' : ''}>${repo}</option>`
|
|
512
|
+
).join('');
|
|
513
|
+
|
|
514
|
+
if (value && !selectState.repos.includes(value)) {
|
|
515
|
+
options += `<option value="${value}" selected>${value}</option>`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return `
|
|
519
|
+
<div class="space-y-1">
|
|
520
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
521
|
+
<div class="relative">
|
|
522
|
+
<div class="relative">
|
|
523
|
+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
524
|
+
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
525
|
+
<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
|
+
</svg>
|
|
527
|
+
</div>
|
|
528
|
+
<input type="text" id="REPO_SEARCH" placeholder="Rechercher un dépôt..." class="w-full pl-10 pr-4 py-2 bg-slate-900 border border-slate-700 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none text-sm text-slate-200" autocomplete="off" value="${value || ''}">
|
|
529
|
+
<div id="REPO_LOADING_INDICATOR" class="absolute inset-y-0 right-0 pr-3 flex items-center hidden">
|
|
530
|
+
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-400"></div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
<div id="REPO_RESULTS" class="search-results hidden"></div>
|
|
534
|
+
<input type="hidden" id="${field.id}" value="${value || ''}">
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
`;
|
|
538
|
+
} else if (field.id === 'BASE_BRANCH') {
|
|
539
|
+
const currentRepo = config.TARGET_REPO;
|
|
540
|
+
const branches = selectState.branches[currentRepo] || [];
|
|
541
|
+
|
|
542
|
+
if (selectState.loadingBranches[currentRepo]) {
|
|
543
|
+
return `
|
|
544
|
+
<div class="space-y-1">
|
|
545
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
546
|
+
<div id="${field.id}_loading" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg text-slate-400 flex items-center">
|
|
547
|
+
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-400 mr-2"></div>
|
|
548
|
+
Chargement des branches...
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Recréer les options avec l'option par défaut sélectionnée si value est vide
|
|
555
|
+
let branchOptions = `<option value="" ${!value ? 'selected' : ''}>-- Choisir une branche --</option>`;
|
|
556
|
+
branchOptions += branches.map(branch =>
|
|
557
|
+
`<option value="${branch}" ${value === branch ? 'selected' : ''}>${branch}</option>`
|
|
558
|
+
).join('');
|
|
559
|
+
|
|
560
|
+
return `
|
|
561
|
+
<div class="space-y-1">
|
|
562
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
563
|
+
<select id="${field.id}" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200 text-slate-100">
|
|
564
|
+
${branchOptions}
|
|
565
|
+
</select>
|
|
566
|
+
</div>
|
|
567
|
+
`;
|
|
568
|
+
} else if (field.id === 'TARGET_BRANCH') {
|
|
569
|
+
const currentRepo = config.TARGET_REPO;
|
|
570
|
+
const branches = selectState.branches[currentRepo] || [];
|
|
571
|
+
|
|
572
|
+
if (selectState.loadingBranches[currentRepo]) {
|
|
573
|
+
return `
|
|
574
|
+
<div class="space-y-1">
|
|
575
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
576
|
+
<div id="${field.id}_loading" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg text-slate-400 flex items-center">
|
|
577
|
+
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-400 mr-2"></div>
|
|
578
|
+
Chargement des branches...
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
let selectOptions = '<option value="">-- Créer une nouvelle branche --</option>';
|
|
585
|
+
selectOptions += branches.map(branch =>
|
|
586
|
+
`<option value="${branch}" ${value === branch ? 'selected' : ''}>${branch}</option>`
|
|
587
|
+
).join('');
|
|
588
|
+
|
|
589
|
+
const isCustom = value && !branches.includes(value);
|
|
590
|
+
const shouldShowCustom = isCustom || value === '';
|
|
591
|
+
|
|
592
|
+
return `
|
|
593
|
+
<div class="space-y-1">
|
|
594
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
595
|
+
<div class="space-y-2">
|
|
596
|
+
<select id="${field.id}" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200 text-slate-100">
|
|
597
|
+
${selectOptions}
|
|
598
|
+
</select>
|
|
599
|
+
<input type="text" id="${field.id}_custom" placeholder="Entrez le nom de la nouvelle branche" value="${isCustom ? value : ''}" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200 text-slate-100 ${shouldShowCustom ? '' : 'hidden'}">
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
`;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return `
|
|
606
|
+
<div class="space-y-1">
|
|
607
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
608
|
+
<select id="${field.id}" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200 text-slate-100">
|
|
609
|
+
${options}
|
|
610
|
+
</select>
|
|
611
|
+
</div>
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Fetch current config
|
|
616
|
+
async function fetchConfig() {
|
|
617
|
+
try {
|
|
618
|
+
// Fetch configs list first
|
|
619
|
+
await fetchConfigs();
|
|
620
|
+
|
|
621
|
+
const response = await fetch(api('api/config'));
|
|
622
|
+
if (!response.ok) {
|
|
623
|
+
const data = await response.json().catch(() => ({ error: 'Erreur lors du chargement' }));
|
|
624
|
+
throw new Error(data.error || 'Erreur lors du chargement');
|
|
625
|
+
}
|
|
626
|
+
currentConfig = await response.json();
|
|
627
|
+
|
|
628
|
+
// Afficher le nom de l'agent si défini
|
|
629
|
+
const agentBadge = document.getElementById('agentNameBadge');
|
|
630
|
+
if (currentConfig.AGENT_NAME) {
|
|
631
|
+
agentBadge.textContent = currentConfig.AGENT_NAME;
|
|
632
|
+
agentBadge.classList.remove('hidden');
|
|
633
|
+
} else {
|
|
634
|
+
agentBadge.classList.add('hidden');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Le token est désormais côté serveur, on tente de charger les repos directement
|
|
638
|
+
await fetchRepositories();
|
|
639
|
+
if (currentConfig.TARGET_REPO) {
|
|
640
|
+
await fetchBranches(currentConfig.TARGET_REPO);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
renderInputs(currentConfig);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
showStatus(error.message || 'Erreur lors du chargement de la configuration', 'error');
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Render inputs
|
|
650
|
+
function renderInputs(config) {
|
|
651
|
+
inputsContainer.innerHTML = fields.map(field => {
|
|
652
|
+
// ... (reste de la fonction identique)
|
|
653
|
+
if (field.type === 'slider') {
|
|
654
|
+
return `
|
|
655
|
+
<div class="flex items-center justify-between p-3 bg-slate-700/50 rounded-lg">
|
|
656
|
+
<label for="${field.id}" class="text-sm font-medium text-slate-200">${field.label}</label>
|
|
657
|
+
<label class="switch">
|
|
658
|
+
<input type="checkbox" id="${field.id}" ${config[field.id] === 'true' || config[field.id] === true ? 'checked' : ''}>
|
|
659
|
+
<span class="slider"></span>
|
|
660
|
+
</label>
|
|
661
|
+
</div>
|
|
662
|
+
`;
|
|
663
|
+
}
|
|
664
|
+
if (field.type === 'checkbox') {
|
|
665
|
+
return `
|
|
666
|
+
<div class="flex items-center space-x-3 p-3 bg-slate-700/50 rounded-lg">
|
|
667
|
+
<input type="checkbox" id="${field.id}" ${config[field.id] === 'true' || config[field.id] === true ? 'checked' : ''} class="w-5 h-5 text-blue-600 rounded focus:ring-blue-500 bg-slate-900 border-slate-600">
|
|
668
|
+
<label for="${field.id}" class="text-sm font-medium text-slate-200">${field.label}</label>
|
|
669
|
+
</div>
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
if (field.type === 'select') {
|
|
673
|
+
return renderSelectField(field, config[field.id] || '', config);
|
|
674
|
+
}
|
|
675
|
+
return `
|
|
676
|
+
<div class="space-y-1">
|
|
677
|
+
<label for="${field.id}" class="block text-sm font-medium text-slate-300">${field.label}</label>
|
|
678
|
+
<input type="${field.type}" id="${field.id}" value="${config[field.id] || ''}" placeholder="${field.placeholder}" class="w-full px-4 py-2 bg-slate-900 border border-slate-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200 text-slate-100">
|
|
679
|
+
</div>
|
|
680
|
+
`;
|
|
681
|
+
}).join('');
|
|
682
|
+
|
|
683
|
+
if (deleteWorkingDirBtn) {
|
|
684
|
+
deleteWorkingDirBtn.disabled = !config.REPO_PATH;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (cloneBtn) {
|
|
688
|
+
// Le clonage est possible si on a un repo, une branche et un chemin de projet
|
|
689
|
+
cloneBtn.disabled = !config.TARGET_REPO || !config.TARGET_BRANCH || !config.PROJECT_PATH;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
setupEventListeners();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Update select UI
|
|
696
|
+
function updateSelectUI() {
|
|
697
|
+
// Mettre à jour currentConfig à partir du DOM pour les éléments qui existent
|
|
698
|
+
fields.forEach(field => {
|
|
699
|
+
const input = document.getElementById(field.id);
|
|
700
|
+
if (input) {
|
|
701
|
+
if (field.type === 'checkbox' || field.type === 'slider') {
|
|
702
|
+
currentConfig[field.id] = input.checked;
|
|
703
|
+
} else if (field.id === 'TARGET_BRANCH') {
|
|
704
|
+
const customInput = document.getElementById('TARGET_BRANCH_custom');
|
|
705
|
+
currentConfig[field.id] = (input.value === '' && customInput && customInput.value) ? customInput.value : input.value;
|
|
706
|
+
} else {
|
|
707
|
+
currentConfig[field.id] = input.value;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
renderInputs(currentConfig);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Setup event listeners
|
|
715
|
+
function setupEventListeners() {
|
|
716
|
+
const targetBranchSelect = document.getElementById('TARGET_BRANCH');
|
|
717
|
+
const repoSearch = document.getElementById('REPO_SEARCH');
|
|
718
|
+
const resultsContainer = document.getElementById('REPO_RESULTS');
|
|
719
|
+
|
|
720
|
+
if (repoSearch) {
|
|
721
|
+
// Focus: ouvre la liste avec ce qui est déjà là
|
|
722
|
+
repoSearch.addEventListener('focus', () => {
|
|
723
|
+
if (selectState.repos.length > 0) {
|
|
724
|
+
renderRepoResults();
|
|
725
|
+
} else {
|
|
726
|
+
// Si vraiment vide, on tente de charger une fois
|
|
727
|
+
fetchRepositories();
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Clavier: navigation avec flèches et Enter
|
|
732
|
+
repoSearch.addEventListener('keydown', (e) => {
|
|
733
|
+
const items = resultsContainer.querySelectorAll('.search-result-item');
|
|
734
|
+
if (items.length === 0) return;
|
|
735
|
+
|
|
736
|
+
if (e.key === 'ArrowDown') {
|
|
737
|
+
e.preventDefault();
|
|
738
|
+
activeIndex = Math.min(activeIndex + 1, items.length - 1);
|
|
739
|
+
updateActiveItem(items);
|
|
740
|
+
} else if (e.key === 'ArrowUp') {
|
|
741
|
+
e.preventDefault();
|
|
742
|
+
activeIndex = Math.max(activeIndex - 1, 0);
|
|
743
|
+
updateActiveItem(items);
|
|
744
|
+
} else if (e.key === 'Enter') {
|
|
745
|
+
e.preventDefault();
|
|
746
|
+
if (activeIndex >= 0) {
|
|
747
|
+
selectRepo(selectState.repos[activeIndex]);
|
|
748
|
+
}
|
|
749
|
+
} else if (e.key === 'Escape') {
|
|
750
|
+
resultsContainer.classList.add('hidden');
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
repoSearch.addEventListener('input', (e) => {
|
|
755
|
+
const term = e.target.value;
|
|
756
|
+
clearTimeout(searchTimeout);
|
|
757
|
+
searchTimeout = setTimeout(() => {
|
|
758
|
+
fetchRepositories(term);
|
|
759
|
+
}, 500);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function updateActiveItem(items) {
|
|
764
|
+
items.forEach((item, index) => {
|
|
765
|
+
item.classList.toggle('active', index === activeIndex);
|
|
766
|
+
if (index === activeIndex) {
|
|
767
|
+
item.scrollIntoView({ block: 'nearest' });
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
document.addEventListener('click', (e) => {
|
|
773
|
+
if (resultsContainer && !resultsContainer.contains(e.target) && e.target !== repoSearch) {
|
|
774
|
+
resultsContainer.classList.add('hidden');
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
['INCLUDE_PUBLIC', 'INCLUDE_NOT_OWNED'].forEach(id => {
|
|
779
|
+
const input = document.getElementById(id);
|
|
780
|
+
if (input) {
|
|
781
|
+
input.addEventListener('change', async () => {
|
|
782
|
+
const config = {};
|
|
783
|
+
fields.forEach(f => {
|
|
784
|
+
const inp = document.getElementById(f.id);
|
|
785
|
+
if (inp) {
|
|
786
|
+
if (f.type === 'checkbox' || f.type === 'slider') config[f.id] = inp.checked;
|
|
787
|
+
else config[f.id] = inp.value;
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
await fetch(api('api/config'), {
|
|
793
|
+
method: 'POST',
|
|
794
|
+
headers: { 'Content-Type': 'application/json' },
|
|
795
|
+
body: JSON.stringify(config)
|
|
796
|
+
});
|
|
797
|
+
await fetchRepositories(repoSearch?.value || '');
|
|
798
|
+
} catch (e) {
|
|
799
|
+
console.error('Erreur lors de la mise à jour auto:', e);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
if (targetBranchSelect) {
|
|
806
|
+
targetBranchSelect.addEventListener('change', () => {
|
|
807
|
+
const customInput = document.getElementById('TARGET_BRANCH_custom');
|
|
808
|
+
if (!customInput) return;
|
|
809
|
+
|
|
810
|
+
const isNewBranch = targetBranchSelect.value === '';
|
|
811
|
+
customInput.classList.toggle('hidden', !isNewBranch);
|
|
812
|
+
if (isNewBranch) {
|
|
813
|
+
customInput.focus();
|
|
814
|
+
} else {
|
|
815
|
+
customInput.value = '';
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function showStatus(message, type) {
|
|
822
|
+
const container = document.getElementById('toast-container');
|
|
823
|
+
const toast = document.createElement('div');
|
|
824
|
+
|
|
825
|
+
const bgColor = type === 'success' ? 'bg-green-900/90' : 'bg-red-900/90';
|
|
826
|
+
const borderColor = type === 'success' ? 'border-green-500' : 'border-red-500';
|
|
827
|
+
const textColor = type === 'success' ? 'text-green-100' : 'text-red-100';
|
|
828
|
+
const icon = type === 'success'
|
|
829
|
+
? '<svg class="w-5 h-5 mr-3 text-green-400" 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>'
|
|
830
|
+
: '<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>';
|
|
831
|
+
|
|
832
|
+
toast.className = `toast flex items-center p-4 rounded-lg border shadow-xl ${bgColor} ${borderColor} ${textColor}`;
|
|
833
|
+
toast.innerHTML = `${icon}<span class="flex-1">${message}</span>`;
|
|
834
|
+
|
|
835
|
+
container.appendChild(toast);
|
|
836
|
+
|
|
837
|
+
// Supprimer le toast après l'animation (5s total)
|
|
838
|
+
setTimeout(() => {
|
|
839
|
+
toast.remove();
|
|
840
|
+
}, 5000);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Uniformisation: addEventListener au lieu de onsubmit/onclick
|
|
844
|
+
form.addEventListener('submit', async (e) => {
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
|
|
847
|
+
// Check if a configuration is selected
|
|
848
|
+
if (!currentConfigName) {
|
|
849
|
+
showStatus('Veuillez sélectionner ou créer une configuration', 'error');
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const config = {};
|
|
854
|
+
fields.forEach(field => {
|
|
855
|
+
const input = document.getElementById(field.id);
|
|
856
|
+
if (!input) return; // Protection contre les éléments manquants
|
|
857
|
+
if (field.type === 'checkbox' || field.type === 'slider') {
|
|
858
|
+
config[field.id] = input.checked;
|
|
859
|
+
} else if (field.id === 'TARGET_BRANCH') {
|
|
860
|
+
const customInput = document.getElementById(`${field.id}_custom`);
|
|
861
|
+
config[field.id] = customInput && customInput.value ? customInput.value : input.value;
|
|
862
|
+
} else {
|
|
863
|
+
config[field.id] = input.value;
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
try {
|
|
868
|
+
// Save to configs.json via the new endpoint
|
|
869
|
+
const response = await fetch(api('api/configs'), {
|
|
870
|
+
method: 'POST',
|
|
871
|
+
headers: { 'Content-Type': 'application/json' },
|
|
872
|
+
body: JSON.stringify({ name: currentConfigName, config })
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
if (response.ok) {
|
|
876
|
+
showStatus(`Configuration "${currentConfigName}" enregistrée avec succès !`, 'success');
|
|
877
|
+
} else {
|
|
878
|
+
const data = await response.json().catch(() => ({ error: 'Erreur inconnue' }));
|
|
879
|
+
throw new Error(data.error || 'Erreur lors de l\'enregistrement');
|
|
880
|
+
}
|
|
881
|
+
} catch (error) {
|
|
882
|
+
showStatus(error.message, 'error');
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
restartBtn.addEventListener('click', async () => {
|
|
887
|
+
if (!confirm('Êtes-vous sûr de vouloir redémarrer le serveur MCP ? Cela coupera la connexion actuelle pour appliquer les changements.')) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const originalHTML = restartBtn.innerHTML; // Sauvegarder le HTML original
|
|
892
|
+
try {
|
|
893
|
+
restartBtn.disabled = true;
|
|
894
|
+
restartBtn.innerHTML = '<span>Redémarrage en cours...</span>';
|
|
895
|
+
|
|
896
|
+
const response = await fetch(api('api/restart'), { method: 'POST' });
|
|
897
|
+
|
|
898
|
+
if (response.ok) {
|
|
899
|
+
showStatus('Signal de redémarrage envoyé. Le serveur va se relancer...', 'success');
|
|
900
|
+
setTimeout(() => window.location.reload(), 3000);
|
|
901
|
+
} else {
|
|
902
|
+
throw new Error('Erreur lors de la demande de redémarrage');
|
|
903
|
+
}
|
|
904
|
+
} catch (error) {
|
|
905
|
+
showStatus(error.message, 'error');
|
|
906
|
+
restartBtn.disabled = false;
|
|
907
|
+
restartBtn.innerHTML = originalHTML; // Restaurer le HTML original
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
stopBtn.addEventListener('click', async () => {
|
|
912
|
+
if (!confirm('Êtes-vous sûr de vouloir arrêter COMPLÈTEMENT le serveur MCP ?')) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const originalHTML = stopBtn.innerHTML; // Sauvegarder le HTML original
|
|
917
|
+
try {
|
|
918
|
+
stopBtn.disabled = true;
|
|
919
|
+
stopBtn.innerHTML = '<span>Arrêt en cours...</span>';
|
|
920
|
+
|
|
921
|
+
const response = await fetch(api('api/stop'), { method: 'POST' });
|
|
922
|
+
|
|
923
|
+
if (response.ok) {
|
|
924
|
+
showStatus('Le serveur s\'arrête. Cette page ne sera plus disponible.', 'success');
|
|
925
|
+
} else {
|
|
926
|
+
throw new Error('Erreur lors de la demande d\'arrêt');
|
|
927
|
+
}
|
|
928
|
+
} catch (error) {
|
|
929
|
+
showStatus(error.message, 'error');
|
|
930
|
+
stopBtn.disabled = false;
|
|
931
|
+
stopBtn.innerHTML = originalHTML; // Restaurer le HTML original
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
deleteWorkingDirBtn.addEventListener('click', async () => {
|
|
936
|
+
if (!confirm('Êtes-vous sûr de vouloir SUPPRIMER physiquement le dossier de travail local ? Cette action est irréversible et vous devrez effectuer un nouveau "clone" pour utiliser les outils Git.')) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const originalHTML = deleteWorkingDirBtn.innerHTML;
|
|
941
|
+
try {
|
|
942
|
+
deleteWorkingDirBtn.disabled = true;
|
|
943
|
+
deleteWorkingDirBtn.innerHTML = '<span>Suppression...</span>';
|
|
944
|
+
|
|
945
|
+
const response = await fetch(api('api/delete-working-dir'), { method: 'POST' });
|
|
946
|
+
|
|
947
|
+
if (response.ok) {
|
|
948
|
+
showStatus('Dossier de travail supprimé avec succès.', 'success');
|
|
949
|
+
currentConfig.REPO_PATH = '';
|
|
950
|
+
deleteWorkingDirBtn.disabled = true;
|
|
951
|
+
} else {
|
|
952
|
+
const data = await response.json().catch(() => ({ error: 'Erreur lors de la suppression' }));
|
|
953
|
+
throw new Error(data.error || 'Erreur lors de la suppression');
|
|
954
|
+
}
|
|
955
|
+
} catch (error) {
|
|
956
|
+
showStatus(error.message, 'error');
|
|
957
|
+
} finally {
|
|
958
|
+
deleteWorkingDirBtn.innerHTML = originalHTML;
|
|
959
|
+
deleteWorkingDirBtn.disabled = !currentConfig.REPO_PATH;
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
cloneBtn.addEventListener('click', async () => {
|
|
964
|
+
if (!confirm('Voulez-vous lancer le clonage du dépôt ? Assurez-vous d\'avoir enregistré la configuration (Parent Projects Path) avant.')) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const originalHTML = cloneBtn.innerHTML;
|
|
969
|
+
try {
|
|
970
|
+
cloneBtn.disabled = true;
|
|
971
|
+
cloneBtn.innerHTML = '<span>Clonage...</span>';
|
|
972
|
+
|
|
973
|
+
const response = await fetch(api('api/clone'), { method: 'POST' });
|
|
974
|
+
|
|
975
|
+
if (response.ok) {
|
|
976
|
+
showStatus('Le processus de clonage a été lancé en arrière-plan. Vérifiez les logs pour le résultat.', 'success');
|
|
977
|
+
} else {
|
|
978
|
+
const data = await response.json().catch(() => ({ error: 'Erreur lors du clonage' }));
|
|
979
|
+
throw new Error(data.error || 'Erreur lors du clonage');
|
|
980
|
+
}
|
|
981
|
+
} catch (error) {
|
|
982
|
+
showStatus(error.message, 'error');
|
|
983
|
+
cloneBtn.disabled = false;
|
|
984
|
+
} finally {
|
|
985
|
+
cloneBtn.innerHTML = originalHTML;
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
fetchConfig();
|
|
990
|
+
</script>
|
|
991
|
+
</body>
|
|
992
|
+
</html>
|