@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 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.37",
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": {
@@ -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
 
@@ -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&eacute;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&eacute;der">Ouvrir</a>
133
+ <a href="${escapeHtml(route.path)}" class="action-btn primary" title="Acc&eacute;der" target="_blank" rel="noopener noreferrer">Ouvrir</a>
25
134
  <button onclick="checkHealth('${escapeHtml(route.path)}', this)" class="action-btn" title="V&eacute;rifier la sant&eacute;">Sant&eacute;</button>
26
135
  <button onclick="killPort(${route.port}, this)" class="action-btn danger" title="Tuer le process">Tuer</button>
27
136
  </div>
@@ -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">&#x2715;</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) + '" data-name="' + sbEsc(r.name || r.path) + '" onclick="openMcpIframeModal(this.dataset.path, this.dataset.name)">'
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) + '" data-name="' + sbEsc(r.name || r.path) + '" onclick="event.stopPropagation(); openMcpIframeModal(this.dataset.path, this.dataset.name)" title="Ouvrir dans une fen&#234;tre">&#x26F6;</button>'
972
- + '<button class="sb-icon-btn sb-icon-btn-blue" data-path="' + sbEsc(r.path) + '" onclick="event.stopPropagation(); sidebarOpenRoute(this.dataset.path)" title="Ouvrir">&#8599;</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&#234;tre">&#x26F6;</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&#234;tre">&#8599;</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&#233;">&#10084;</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>';