@imenam/self-register-proxy 1.0.37 → 1.0.40
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 +13 -1
- package/config.js +3 -0
- package/package.json +1 -1
- package/routes/dashboard.js +2 -2
- package/views/dashboard.js +111 -2
- package/views/terminal.html +3 -41
package/README.md
CHANGED
|
@@ -41,6 +41,18 @@ npm start
|
|
|
41
41
|
|
|
42
42
|
Le proxy démarre par défaut sur le port `3000`.
|
|
43
43
|
|
|
44
|
+
#### Interface HTML minimale (`--minimal-dashboard`)
|
|
45
|
+
|
|
46
|
+
Par défaut, la page d’accueil en HTML affiche l’interface complète (statistiques, barre d’outils, actions sur les services, etc.). Le flag **`--minimal-dashboard`** réduit cette page au **nom du proxy** (`PROXY_NAME`) et, pour chaque service enregistré, au **nom** et au bouton **Ouvrir** uniquement — sans URL détaillée, sans port visible, sans barre d’outils ni modale d’ajout. Il n’existe pas d’équivalent via variable d’environnement ; seul ce flag CLI est pris en charge.
|
|
47
|
+
|
|
48
|
+
Avec **`npx`**, placez **`--`** avant le flag pour qu’il soit transmis au script Node :
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @imenam/self-register-proxy -- --minimal-dashboard
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Les réponses **JSON** de `GET /` lorsque le client envoie `Accept: application/json` restent **inchangées** (le mode minimal ne concerne que le HTML).
|
|
55
|
+
|
|
44
56
|
### Configuration via fichier .env
|
|
45
57
|
|
|
46
58
|
Créez un fichier `.env` à la racine du projet (voir `.env.example`) :
|
|
@@ -91,7 +103,7 @@ http://localhost:3000/myapp/ → votre application sur le port 4000
|
|
|
91
103
|
|
|
92
104
|
L'URL racine `/` affiche une page listant tous les serveurs enregistrés avec des liens pour y accéder.
|
|
93
105
|
|
|
94
|
-
Le nom du proxy (configurable via `PROXY_NAME`) est affiché dans le titre et l'en-tête de la page.
|
|
106
|
+
Le nom du proxy (configurable via `PROXY_NAME`) est affiché dans le titre et l'en-tête de la page. Pour une variante HTML épurée (titre + noms + bouton « Ouvrir » uniquement), démarrez le proxy avec `--minimal-dashboard` (voir **Démarrage rapide**).
|
|
95
107
|
|
|
96
108
|
- **Navigateurs** (`Accept: text/html`) → Page HTML avec interface moderne
|
|
97
109
|
- **Clients API** (`Accept: application/json`) → Réponse JSON
|
package/config.js
CHANGED
|
@@ -15,6 +15,8 @@ const TERMINAL_CWD = process.env.PROXY_TERMINAL_CWD || process.env.HOME || proce
|
|
|
15
15
|
const TERMINAL_COMMAND = process.env.PROXY_TERMINAL_COMMAND || '';
|
|
16
16
|
const OPENCODE_CLEAN_UP = process.env.PROXY_OPENCODE_CLEAN_UP || '';
|
|
17
17
|
|
|
18
|
+
const MINIMAL_DASHBOARD = process.argv.includes('--minimal-dashboard');
|
|
19
|
+
|
|
18
20
|
module.exports = {
|
|
19
21
|
PROXY_PORT,
|
|
20
22
|
PROXY_NAME,
|
|
@@ -28,4 +30,5 @@ module.exports = {
|
|
|
28
30
|
TERMINAL_CWD,
|
|
29
31
|
TERMINAL_COMMAND,
|
|
30
32
|
OPENCODE_CLEAN_UP,
|
|
33
|
+
MINIMAL_DASHBOARD,
|
|
31
34
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imenam/self-register-proxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "Dynamic reverse proxy with self-registration API - applications can register themselves and receive an automatically assigned port",
|
|
5
5
|
"main": "proxy-client.js",
|
|
6
6
|
"bin": {
|
package/routes/dashboard.js
CHANGED
|
@@ -5,7 +5,7 @@ const { safeEqual, createSession, setSessionCookie, destroySession, clearSession
|
|
|
5
5
|
const { registry } = require('../services/registry');
|
|
6
6
|
const { renderLogin } = require('../views/login');
|
|
7
7
|
const { renderDashboard } = require('../views/dashboard');
|
|
8
|
-
const { PROXY_NAME, PROXY_PORT, ADMIN_PASSWORD, INTERNAL_PORT_START, INTERNAL_PORT_END, OPENCODE_CLEAN_UP, TERMINAL_CWD } = require('../config');
|
|
8
|
+
const { PROXY_NAME, PROXY_PORT, ADMIN_PASSWORD, INTERNAL_PORT_START, INTERNAL_PORT_END, OPENCODE_CLEAN_UP, TERMINAL_CWD, MINIMAL_DASHBOARD } = require('../config');
|
|
9
9
|
|
|
10
10
|
const router = Router();
|
|
11
11
|
|
|
@@ -100,7 +100,7 @@ router.get('/', requireAuth, (req, res) => {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
const availablePorts = (INTERNAL_PORT_END - INTERNAL_PORT_START) - registry.usedPorts.size;
|
|
103
|
-
const html = renderDashboard(routes, availablePorts, PROXY_NAME, !!ADMIN_PASSWORD, OPENCODE_CLEAN_UP);
|
|
103
|
+
const html = renderDashboard(routes, availablePorts, PROXY_NAME, !!ADMIN_PASSWORD, OPENCODE_CLEAN_UP, MINIMAL_DASHBOARD);
|
|
104
104
|
res.type('html').send(html);
|
|
105
105
|
});
|
|
106
106
|
|
package/views/dashboard.js
CHANGED
|
@@ -7,8 +7,117 @@ const { escapeHtml } = require('../services/proxyRouter');
|
|
|
7
7
|
* @param {string} proxyName
|
|
8
8
|
* @param {boolean} hasAuth - true si l'authentification est activée
|
|
9
9
|
* @param {string|undefined} cleanUpCommand - si défini, affiche le bouton de nettoyage
|
|
10
|
+
* @param {boolean} [minimalDashboard] - interface HTML réduite (nom + Ouvrir uniquement)
|
|
10
11
|
*/
|
|
11
|
-
function renderDashboard(routes, availablePorts, proxyName, hasAuth, cleanUpCommand) {
|
|
12
|
+
function renderDashboard(routes, availablePorts, proxyName, hasAuth, cleanUpCommand, minimalDashboard) {
|
|
13
|
+
if (minimalDashboard) {
|
|
14
|
+
const servicesHtml = routes.length > 0
|
|
15
|
+
? `<div class="services-list">
|
|
16
|
+
${routes.map(route => `
|
|
17
|
+
<div class="service-card">
|
|
18
|
+
<div class="service-info">
|
|
19
|
+
<span class="service-name">${escapeHtml(route.name)}</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="service-actions">
|
|
22
|
+
<a href="${escapeHtml(route.path)}" class="action-btn primary" title="Accéder" target="_blank" rel="noopener noreferrer">Ouvrir</a>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
`).join('')}
|
|
26
|
+
</div>`
|
|
27
|
+
: `<div class="empty-state minimal-empty">
|
|
28
|
+
<p>Aucun service</p>
|
|
29
|
+
</div>`;
|
|
30
|
+
|
|
31
|
+
return `<!DOCTYPE html>
|
|
32
|
+
<html lang="fr">
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="UTF-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
36
|
+
<title>${escapeHtml(proxyName)}</title>
|
|
37
|
+
<style>
|
|
38
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
39
|
+
body.minimal-dashboard {
|
|
40
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
41
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
42
|
+
min-height: 100vh;
|
|
43
|
+
color: #e4e4e7;
|
|
44
|
+
padding: 1rem;
|
|
45
|
+
}
|
|
46
|
+
.minimal-dashboard .container { max-width: 560px; margin: 0 auto; }
|
|
47
|
+
.minimal-dashboard header { text-align: center; margin-bottom: 1rem; }
|
|
48
|
+
.minimal-dashboard h1 {
|
|
49
|
+
font-size: 1.75rem;
|
|
50
|
+
background: linear-gradient(90deg, #6366f1, #8b5cf6);
|
|
51
|
+
-webkit-background-clip: text;
|
|
52
|
+
-webkit-text-fill-color: transparent;
|
|
53
|
+
background-clip: text;
|
|
54
|
+
}
|
|
55
|
+
.services-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
56
|
+
.service-card {
|
|
57
|
+
background: rgba(255, 255, 255, 0.05);
|
|
58
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
padding: 0.6rem 0.9rem;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
gap: 0.75rem;
|
|
65
|
+
transition: all 0.15s ease;
|
|
66
|
+
}
|
|
67
|
+
.service-card:hover {
|
|
68
|
+
background: rgba(255, 255, 255, 0.08);
|
|
69
|
+
border-color: rgba(139, 92, 246, 0.4);
|
|
70
|
+
}
|
|
71
|
+
.service-info { display: flex; align-items: center; min-width: 0; flex: 1; }
|
|
72
|
+
.service-name {
|
|
73
|
+
font-size: 0.95rem;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
color: #f4f4f5;
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
text-overflow: ellipsis;
|
|
79
|
+
}
|
|
80
|
+
.service-actions { display: flex; flex-shrink: 0; align-items: center; }
|
|
81
|
+
.action-btn {
|
|
82
|
+
background: rgba(255, 255, 255, 0.1);
|
|
83
|
+
color: #e4e4e7;
|
|
84
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
85
|
+
padding: 0.35rem 0.6rem;
|
|
86
|
+
border-radius: 5px;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
font-size: 0.75rem;
|
|
89
|
+
font-weight: 500;
|
|
90
|
+
text-decoration: none;
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
}
|
|
94
|
+
.action-btn.primary {
|
|
95
|
+
background: linear-gradient(90deg, #6366f1, #8b5cf6);
|
|
96
|
+
border: none;
|
|
97
|
+
color: white;
|
|
98
|
+
}
|
|
99
|
+
.action-btn.primary:hover { opacity: 0.9; }
|
|
100
|
+
.empty-state.minimal-empty {
|
|
101
|
+
text-align: center;
|
|
102
|
+
padding: 2rem 1rem;
|
|
103
|
+
background: rgba(255, 255, 255, 0.03);
|
|
104
|
+
border-radius: 10px;
|
|
105
|
+
border: 1px dashed rgba(255, 255, 255, 0.1);
|
|
106
|
+
}
|
|
107
|
+
.empty-state.minimal-empty p { color: #a1a1aa; font-size: 0.95rem; }
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body class="minimal-dashboard">
|
|
111
|
+
<div class="container">
|
|
112
|
+
<header>
|
|
113
|
+
<h1>${escapeHtml(proxyName)}</h1>
|
|
114
|
+
</header>
|
|
115
|
+
${servicesHtml}
|
|
116
|
+
</div>
|
|
117
|
+
</body>
|
|
118
|
+
</html>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
12
121
|
const servicesHtml = routes.length > 0
|
|
13
122
|
? `<div class="services-list">
|
|
14
123
|
${routes.map(route => `
|
|
@@ -21,7 +130,7 @@ function renderDashboard(routes, availablePorts, proxyName, hasAuth, cleanUpComm
|
|
|
21
130
|
<span class="service-config"></span>
|
|
22
131
|
</div>
|
|
23
132
|
<div class="service-actions">
|
|
24
|
-
<a href="${escapeHtml(route.path)}" class="action-btn primary" title="Accéder">Ouvrir</a>
|
|
133
|
+
<a href="${escapeHtml(route.path)}" class="action-btn primary" title="Accéder" target="_blank" rel="noopener noreferrer">Ouvrir</a>
|
|
25
134
|
<button onclick="checkHealth('${escapeHtml(route.path)}', this)" class="action-btn" title="Vérifier la santé">Santé</button>
|
|
26
135
|
<button onclick="killPort(${route.port}, this)" class="action-btn danger" title="Tuer le process">Tuer</button>
|
|
27
136
|
</div>
|
package/views/terminal.html
CHANGED
|
@@ -148,23 +148,6 @@
|
|
|
148
148
|
.sb-icon-btn-blue { background: rgba(99,102,241,0.75); }
|
|
149
149
|
.sb-icon-btn-amber { background: rgba(245,158,11,0.75); }
|
|
150
150
|
.sb-icon-btn-purple { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
|
|
151
|
-
.sb-modal-fullscreen {
|
|
152
|
-
width: calc(100vw - 6rem); height: calc(100vh - 2rem);
|
|
153
|
-
background: #0f0f1a;
|
|
154
|
-
border-radius: 10px; padding: 0;
|
|
155
|
-
border: 1px solid rgba(139,92,246,0.4);
|
|
156
|
-
box-shadow: 0 0 0 4px rgba(99,102,241,0.15), 0 24px 48px rgba(0,0,0,0.6);
|
|
157
|
-
display: flex; flex-direction: column; overflow: hidden;
|
|
158
|
-
}
|
|
159
|
-
.sb-modal-fullscreen-bar {
|
|
160
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
161
|
-
padding: 0.5rem 1rem;
|
|
162
|
-
background: rgba(255,255,255,0.04);
|
|
163
|
-
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
164
|
-
flex-shrink: 0;
|
|
165
|
-
}
|
|
166
|
-
.sb-modal-fullscreen-bar span { font-size: 0.85rem; font-weight: 600; color: #d4d4d8; }
|
|
167
|
-
.sb-modal-fullscreen iframe { flex: 1; border: none; width: 100%; background: #fff; }
|
|
168
151
|
.sb-section-label {
|
|
169
152
|
font-size: 0.65rem; font-weight: 700; letter-spacing: 0.08em;
|
|
170
153
|
color: #52525b; text-transform: uppercase;
|
|
@@ -370,16 +353,6 @@
|
|
|
370
353
|
<div id="terminal-container"></div>
|
|
371
354
|
</div>
|
|
372
355
|
|
|
373
|
-
<div class="sb-modal-overlay" id="mcpIframeModal" onclick="if(event.target===this)closeMcpIframeModal()">
|
|
374
|
-
<div class="sb-modal-fullscreen">
|
|
375
|
-
<div class="sb-modal-fullscreen-bar">
|
|
376
|
-
<span id="mcpIframeTitle"></span>
|
|
377
|
-
<button class="sb-icon-btn sb-icon-btn-ghost" onclick="closeMcpIframeModal()" title="Fermer">✕</button>
|
|
378
|
-
</div>
|
|
379
|
-
<iframe id="mcpIframeEl" src="about:blank" allowfullscreen></iframe>
|
|
380
|
-
</div>
|
|
381
|
-
</div>
|
|
382
|
-
|
|
383
356
|
<div class="sb-modal-overlay" id="sbModalOverlay" onclick="if(event.target===this)closeSbModal()">
|
|
384
357
|
<div class="sb-modal">
|
|
385
358
|
<div class="sb-modal-header">
|
|
@@ -965,11 +938,11 @@
|
|
|
965
938
|
return;
|
|
966
939
|
}
|
|
967
940
|
el.innerHTML = routes.map(r =>
|
|
968
|
-
'<div class="sb-service-card" data-path="' + sbEsc(r.path) + '"
|
|
941
|
+
'<div class="sb-service-card" data-path="' + sbEsc(r.path) + '" onclick="sidebarOpenRoute(this.dataset.path)">'
|
|
969
942
|
+ '<span class="sb-service-name" title="' + sbEsc(r.name || r.path) + '">' + sbEsc(r.name || r.path) + '</span>'
|
|
970
943
|
+ '<div class="sb-service-actions">'
|
|
971
|
-
+ '<button class="sb-icon-btn sb-icon-btn-purple" data-path="' + sbEsc(r.path) + '"
|
|
972
|
-
+ '<button class="sb-icon-btn sb-icon-btn-blue" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarOpenRoute(this.dataset.path)" title="Ouvrir">↗</button>'
|
|
944
|
+
+ '<button class="sb-icon-btn sb-icon-btn-purple" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarOpenRoute(this.dataset.path)" title="Ouvrir dans une nouvelle fenêtre">⛶</button>'
|
|
945
|
+
+ '<button class="sb-icon-btn sb-icon-btn-blue" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarOpenRoute(this.dataset.path)" title="Ouvrir dans une nouvelle fenêtre">↗</button>'
|
|
973
946
|
+ '<button class="sb-icon-btn sb-icon-btn-amber" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarCheckHealth(this.dataset.path, this)" title="Santé">❤</button>'
|
|
974
947
|
+ '</div></div>'
|
|
975
948
|
).join('');
|
|
@@ -979,17 +952,6 @@
|
|
|
979
952
|
window.open(path, '_blank');
|
|
980
953
|
}
|
|
981
954
|
|
|
982
|
-
function openMcpIframeModal(path, name) {
|
|
983
|
-
document.getElementById('mcpIframeEl').src = path;
|
|
984
|
-
document.getElementById('mcpIframeTitle').textContent = name || path;
|
|
985
|
-
document.getElementById('mcpIframeModal').classList.add('active');
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function closeMcpIframeModal() {
|
|
989
|
-
document.getElementById('mcpIframeModal').classList.remove('active');
|
|
990
|
-
document.getElementById('mcpIframeEl').src = 'about:blank';
|
|
991
|
-
}
|
|
992
|
-
|
|
993
955
|
async function sidebarCheckHealth(path, btn) {
|
|
994
956
|
btn.disabled = true;
|
|
995
957
|
btn.innerHTML = '<span class="sb-spinner"></span>';
|