@mostajs/setup 1.4.4 → 1.4.6
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.
|
@@ -12,10 +12,129 @@ export function createUploadJarHandlers() {
|
|
|
12
12
|
async function GET() {
|
|
13
13
|
try {
|
|
14
14
|
const { listJarFiles, getJdbcDialectStatus } = await import('@mostajs/orm');
|
|
15
|
+
// Scan active bridges: BridgeManager bridges + PID files + port probing
|
|
16
|
+
const bridges = [];
|
|
17
|
+
// 1. Check BridgeManager known bridges
|
|
18
|
+
try {
|
|
19
|
+
const { BridgeManager } = await import('@mostajs/orm');
|
|
20
|
+
const manager = BridgeManager.getInstance();
|
|
21
|
+
for (const b of manager.list()) {
|
|
22
|
+
bridges.push({ port: b.port, pid: b.pid, status: 'active', jdbcUrl: b.jdbcUrl });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch { /* ignore */ }
|
|
26
|
+
// 2. Scan PID files for orphan bridges
|
|
27
|
+
try {
|
|
28
|
+
const { existsSync, readdirSync, readFileSync } = await import('fs');
|
|
29
|
+
const { join } = await import('path');
|
|
30
|
+
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
31
|
+
if (existsSync(jarDir)) {
|
|
32
|
+
const pidFiles = readdirSync(jarDir).filter((f) => f.startsWith('.bridge-') && f.endsWith('.pid'));
|
|
33
|
+
for (const file of pidFiles) {
|
|
34
|
+
const portMatch = file.match(/\.bridge-(\d+)\.pid/);
|
|
35
|
+
if (!portMatch)
|
|
36
|
+
continue;
|
|
37
|
+
const port = parseInt(portMatch[1]);
|
|
38
|
+
// Skip if already known from BridgeManager
|
|
39
|
+
if (bridges.some(b => b.port === port))
|
|
40
|
+
continue;
|
|
41
|
+
const pidStr = readFileSync(join(jarDir, file), 'utf-8').trim();
|
|
42
|
+
const pid = parseInt(pidStr);
|
|
43
|
+
if (isNaN(pid))
|
|
44
|
+
continue;
|
|
45
|
+
// Check if process is alive
|
|
46
|
+
let alive = false;
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
alive = true;
|
|
50
|
+
}
|
|
51
|
+
catch { /* dead */ }
|
|
52
|
+
if (alive) {
|
|
53
|
+
// Probe health
|
|
54
|
+
let jdbcUrl;
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1000) });
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
const h = await res.json();
|
|
59
|
+
jdbcUrl = h.jdbcUrl;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { /* not responding */ }
|
|
63
|
+
bridges.push({ port, pid, status: jdbcUrl ? 'active' : 'orphan', jdbcUrl });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch { /* ignore */ }
|
|
69
|
+
// 3. Probe common bridge ports for unknown bridges (no PID file)
|
|
70
|
+
const basePort = parseInt(process.env.MOSTA_BRIDGE_PORT_BASE || '8765');
|
|
71
|
+
for (let port = basePort; port < basePort + 5; port++) {
|
|
72
|
+
if (bridges.some(b => b.port === port))
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(500) });
|
|
76
|
+
if (res.ok) {
|
|
77
|
+
const h = await res.json();
|
|
78
|
+
bridges.push({ port, pid: 0, status: 'active', jdbcUrl: h.jdbcUrl });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch { /* not a bridge */ }
|
|
82
|
+
}
|
|
83
|
+
// Check HSQLDB server status
|
|
84
|
+
let hsqldbServer = null;
|
|
85
|
+
try {
|
|
86
|
+
const { existsSync, readdirSync, readFileSync } = await import('fs');
|
|
87
|
+
const { join } = await import('path');
|
|
88
|
+
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
89
|
+
if (existsSync(jarDir)) {
|
|
90
|
+
const serverPidFiles = readdirSync(jarDir).filter((f) => f.startsWith('.hsqldb-server-') && f.endsWith('.pid'));
|
|
91
|
+
for (const file of serverPidFiles) {
|
|
92
|
+
const portMatch = file.match(/\.hsqldb-server-(\d+)\.pid/);
|
|
93
|
+
if (!portMatch)
|
|
94
|
+
continue;
|
|
95
|
+
const port = parseInt(portMatch[1]);
|
|
96
|
+
const pidStr = readFileSync(join(jarDir, file), 'utf-8').trim();
|
|
97
|
+
const pid = parseInt(pidStr);
|
|
98
|
+
let alive = false;
|
|
99
|
+
if (pid > 0) {
|
|
100
|
+
try {
|
|
101
|
+
process.kill(pid, 0);
|
|
102
|
+
alive = true;
|
|
103
|
+
}
|
|
104
|
+
catch { /* dead */ }
|
|
105
|
+
}
|
|
106
|
+
if (alive) {
|
|
107
|
+
hsqldbServer = { running: true, port, pid };
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Clean stale PID file
|
|
111
|
+
try {
|
|
112
|
+
(await import('fs')).unlinkSync(join(jarDir, file));
|
|
113
|
+
}
|
|
114
|
+
catch { /* ignore */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Also check port 9001 if no PID file found
|
|
119
|
+
if (!hsqldbServer) {
|
|
120
|
+
const { execSync } = await import('child_process');
|
|
121
|
+
try {
|
|
122
|
+
const out = execSync('fuser 9001/tcp 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
123
|
+
if (out) {
|
|
124
|
+
const pid = parseInt(out);
|
|
125
|
+
hsqldbServer = { running: true, port: 9001, pid: isNaN(pid) ? 0 : pid };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { /* not running */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch { /* ignore */ }
|
|
15
132
|
return Response.json({
|
|
16
133
|
ok: true,
|
|
17
134
|
jars: listJarFiles(),
|
|
18
135
|
dialects: getJdbcDialectStatus(),
|
|
136
|
+
bridges,
|
|
137
|
+
hsqldbServer,
|
|
19
138
|
});
|
|
20
139
|
}
|
|
21
140
|
catch (err) {
|
|
@@ -70,34 +189,163 @@ export function createUploadJarHandlers() {
|
|
|
70
189
|
}
|
|
71
190
|
}
|
|
72
191
|
/**
|
|
73
|
-
* PATCH —
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
192
|
+
* PATCH — Manage JDBC bridge and HSQLDB server.
|
|
193
|
+
* start-server: { action: 'start-server', dialect, name, host?, port? }
|
|
194
|
+
* stop-server: { action: 'stop-server', port? }
|
|
195
|
+
* start: { action: 'start', dialect, host, port, name, user?, password? }
|
|
196
|
+
* stop: { action: 'stop', port, pid? }
|
|
77
197
|
*/
|
|
78
198
|
async function PATCH(req) {
|
|
79
199
|
try {
|
|
80
200
|
const body = await req.json();
|
|
81
201
|
const { action } = body;
|
|
202
|
+
// ── Start HSQLDB server ──
|
|
203
|
+
if (action === 'start-server') {
|
|
204
|
+
const { dialect, name, host, port: sgbdPort } = body;
|
|
205
|
+
if (dialect !== 'hsqldb') {
|
|
206
|
+
return Response.json({ ok: false, error: 'start-server supporte uniquement hsqldb' }, { status: 400 });
|
|
207
|
+
}
|
|
208
|
+
const actualPort = sgbdPort || 9001;
|
|
209
|
+
// Check if already running
|
|
210
|
+
try {
|
|
211
|
+
const check = await fetch(`http://${host || 'localhost'}:${actualPort}`, { signal: AbortSignal.timeout(500) });
|
|
212
|
+
void check;
|
|
213
|
+
return Response.json({ ok: true, message: `Serveur HSQLDB deja en marche sur le port ${actualPort}`, port: actualPort, alreadyRunning: true });
|
|
214
|
+
}
|
|
215
|
+
catch { /* not running — proceed */ }
|
|
216
|
+
// Find JAR
|
|
217
|
+
const { JdbcNormalizer } = await import('@mostajs/orm');
|
|
218
|
+
const jarPath = JdbcNormalizer.findJar('hsqldb');
|
|
219
|
+
if (!jarPath) {
|
|
220
|
+
return Response.json({ ok: false, error: 'JAR HSQLDB non trouve. Uploadez hsqldb*.jar d\'abord.' });
|
|
221
|
+
}
|
|
222
|
+
// Launch server
|
|
223
|
+
const { spawn: spawnChild } = await import('child_process');
|
|
224
|
+
const { join } = await import('path');
|
|
225
|
+
const { writeFileSync, existsSync, mkdirSync } = await import('fs');
|
|
226
|
+
const dataDir = join(process.cwd(), 'data');
|
|
227
|
+
if (!existsSync(dataDir))
|
|
228
|
+
mkdirSync(dataDir, { recursive: true });
|
|
229
|
+
const dbAlias = name || 'mydb';
|
|
230
|
+
const serverProc = spawnChild('java', [
|
|
231
|
+
'-cp', jarPath,
|
|
232
|
+
'org.hsqldb.server.Server',
|
|
233
|
+
'--database.0', `file:${join(dataDir, dbAlias)}`,
|
|
234
|
+
'--dbname.0', dbAlias,
|
|
235
|
+
'--port', String(actualPort),
|
|
236
|
+
], {
|
|
237
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
238
|
+
detached: true,
|
|
239
|
+
});
|
|
240
|
+
serverProc.unref();
|
|
241
|
+
const serverPid = serverProc.pid || 0;
|
|
242
|
+
// Save PID file for cleanup
|
|
243
|
+
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
244
|
+
if (existsSync(jarDir)) {
|
|
245
|
+
writeFileSync(join(jarDir, `.hsqldb-server-${actualPort}.pid`), String(serverPid));
|
|
246
|
+
}
|
|
247
|
+
// Log stderr
|
|
248
|
+
serverProc.stderr?.on('data', (data) => {
|
|
249
|
+
const msg = data.toString().trim();
|
|
250
|
+
if (msg)
|
|
251
|
+
console.error(`[HSQLDB:server] ${msg}`);
|
|
252
|
+
});
|
|
253
|
+
// Wait for server to be ready
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
let serverReady = false;
|
|
256
|
+
while (Date.now() - startTime < 8000) {
|
|
257
|
+
try {
|
|
258
|
+
await new Promise(r => setTimeout(r, 500));
|
|
259
|
+
const sock = await fetch(`http://localhost:${actualPort}`, { signal: AbortSignal.timeout(500) });
|
|
260
|
+
void sock;
|
|
261
|
+
serverReady = true;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// HSQLDB server doesn't speak HTTP, but we can check if the port is listening
|
|
266
|
+
// Try a TCP connection test via the bridge health check pattern
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Alternative: check if process is still alive and port is open
|
|
270
|
+
if (!serverReady) {
|
|
271
|
+
try {
|
|
272
|
+
process.kill(serverPid, 0); // check alive
|
|
273
|
+
// Port might be open but not HTTP — that's fine for HSQLDB
|
|
274
|
+
serverReady = true;
|
|
275
|
+
}
|
|
276
|
+
catch { /* process died */ }
|
|
277
|
+
}
|
|
278
|
+
if (!serverReady) {
|
|
279
|
+
return Response.json({ ok: false, error: 'Le serveur HSQLDB n\'a pas demarre dans le delai imparti' });
|
|
280
|
+
}
|
|
281
|
+
return Response.json({ ok: true, port: actualPort, pid: serverPid, message: `Serveur HSQLDB lance (port ${actualPort}, PID ${serverPid}, alias: ${dbAlias})` });
|
|
282
|
+
}
|
|
283
|
+
// ── Stop HSQLDB server ──
|
|
284
|
+
if (action === 'stop-server') {
|
|
285
|
+
const sgbdPort = body.port || 9001;
|
|
286
|
+
try {
|
|
287
|
+
const { execSync } = await import('child_process');
|
|
288
|
+
const { existsSync, unlinkSync, readFileSync } = await import('fs');
|
|
289
|
+
const { join } = await import('path');
|
|
290
|
+
const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
|
|
291
|
+
// Kill by PID file
|
|
292
|
+
const pidFile = join(jarDir, `.hsqldb-server-${sgbdPort}.pid`);
|
|
293
|
+
if (existsSync(pidFile)) {
|
|
294
|
+
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
|
|
295
|
+
if (pid > 0) {
|
|
296
|
+
try {
|
|
297
|
+
process.kill(pid, 'SIGKILL');
|
|
298
|
+
}
|
|
299
|
+
catch { /* dead */ }
|
|
300
|
+
}
|
|
301
|
+
unlinkSync(pidFile);
|
|
302
|
+
}
|
|
303
|
+
// Also kill anything on the port
|
|
304
|
+
try {
|
|
305
|
+
execSync(`fuser -k ${sgbdPort}/tcp 2>/dev/null`, { stdio: 'ignore' });
|
|
306
|
+
}
|
|
307
|
+
catch { /* already free */ }
|
|
308
|
+
return Response.json({ ok: true, message: `Serveur HSQLDB arrete (port ${sgbdPort})` });
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
return Response.json({ ok: false, error: err instanceof Error ? err.message : 'Erreur' });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
82
314
|
if (action === 'stop') {
|
|
83
315
|
const bridgePort = body.port || 8765;
|
|
84
|
-
|
|
316
|
+
const bridgePid = body.pid || 0;
|
|
85
317
|
try {
|
|
318
|
+
// 1. Try BridgeManager
|
|
86
319
|
const { BridgeManager } = await import('@mostajs/orm');
|
|
87
320
|
const manager = BridgeManager.getInstance();
|
|
88
321
|
const bridges = manager.list();
|
|
89
322
|
const bridge = bridges.find((b) => b.port === bridgePort);
|
|
90
323
|
if (bridge) {
|
|
91
324
|
await manager.stop(bridge.key);
|
|
92
|
-
return Response.json({ ok: true, message: `Bridge arrete sur le port ${bridgePort}` });
|
|
93
325
|
}
|
|
94
|
-
//
|
|
326
|
+
// 2. Kill by PID if provided
|
|
327
|
+
if (bridgePid > 0) {
|
|
328
|
+
try {
|
|
329
|
+
process.kill(bridgePid, 'SIGKILL');
|
|
330
|
+
}
|
|
331
|
+
catch { /* already dead */ }
|
|
332
|
+
}
|
|
333
|
+
// 3. Fallback: kill process on port
|
|
95
334
|
const { execSync } = await import('child_process');
|
|
96
335
|
try {
|
|
97
336
|
execSync(`fuser -k ${bridgePort}/tcp 2>/dev/null`, { stdio: 'ignore' });
|
|
98
337
|
}
|
|
99
338
|
catch { /* port may already be free */ }
|
|
100
|
-
|
|
339
|
+
// 4. Clean PID file
|
|
340
|
+
try {
|
|
341
|
+
const { existsSync, unlinkSync } = await import('fs');
|
|
342
|
+
const { join } = await import('path');
|
|
343
|
+
const pidFile = join(process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files'), `.bridge-${bridgePort}.pid`);
|
|
344
|
+
if (existsSync(pidFile))
|
|
345
|
+
unlinkSync(pidFile);
|
|
346
|
+
}
|
|
347
|
+
catch { /* non-critical */ }
|
|
348
|
+
return Response.json({ ok: true, message: `Bridge arrete (port ${bridgePort})` });
|
|
101
349
|
}
|
|
102
350
|
catch (err) {
|
|
103
351
|
const msg = err instanceof Error ? err.message : 'Erreur';
|
|
@@ -215,24 +215,36 @@ async function fetchRetry(url, init, retries = 3, delay = 2000) {
|
|
|
215
215
|
}
|
|
216
216
|
return fetch(url, init);
|
|
217
217
|
}
|
|
218
|
-
// ── JAR Upload Sub-component ─────────────────────────────────
|
|
219
218
|
function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
|
|
220
219
|
const [uploading, setUploading] = useState(false);
|
|
221
|
-
const [
|
|
220
|
+
const [loading, setLoading] = useState(null); // tracks which action is loading
|
|
222
221
|
const [bridgePort, setBridgePort] = useState(null);
|
|
223
222
|
const [message, setMessage] = useState(null);
|
|
224
223
|
const [jarStatus, setJarStatus] = useState(null);
|
|
225
|
-
|
|
224
|
+
const [bridges, setBridges] = useState([]);
|
|
225
|
+
const [serverInfo, setServerInfo] = useState(null);
|
|
226
|
+
const loadStatus = useCallback(() => {
|
|
226
227
|
fetch(jarEndpoint)
|
|
227
228
|
.then(r => r.json())
|
|
228
229
|
.then(data => {
|
|
229
230
|
if (data.ok) {
|
|
230
231
|
const s = data.dialects?.find((d) => d.dialect === dialect);
|
|
231
232
|
setJarStatus(s || { hasJar: false, jarFile: null });
|
|
233
|
+
setBridges(data.bridges || []);
|
|
234
|
+
if (data.hsqldbServer)
|
|
235
|
+
setServerInfo(data.hsqldbServer);
|
|
236
|
+
else
|
|
237
|
+
setServerInfo(null);
|
|
238
|
+
const active = (data.bridges || []).find((b) => b.status === 'active');
|
|
239
|
+
if (active)
|
|
240
|
+
setBridgePort(active.port);
|
|
241
|
+
else
|
|
242
|
+
setBridgePort(null);
|
|
232
243
|
}
|
|
233
244
|
})
|
|
234
245
|
.catch(() => { });
|
|
235
246
|
}, [dialect, jarEndpoint]);
|
|
247
|
+
useEffect(() => { loadStatus(); }, [loadStatus]);
|
|
236
248
|
const handleUpload = async (e) => {
|
|
237
249
|
const file = e.target.files?.[0];
|
|
238
250
|
if (!file || !file.name.endsWith('.jar'))
|
|
@@ -260,57 +272,42 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
|
|
|
260
272
|
e.target.value = '';
|
|
261
273
|
}
|
|
262
274
|
};
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
setMessage(null);
|
|
266
|
-
try {
|
|
267
|
-
const res = await fetch(jarEndpoint, {
|
|
268
|
-
method: 'PATCH',
|
|
269
|
-
headers: { 'Content-Type': 'application/json' },
|
|
270
|
-
body: JSON.stringify({ action: 'start', dialect, ...dbConfig }),
|
|
271
|
-
});
|
|
272
|
-
const result = await res.json();
|
|
273
|
-
if (result.ok) {
|
|
274
|
-
setBridgePort(result.port || 8765);
|
|
275
|
-
setMessage({ ok: true, text: `Bridge JDBC lance sur le port ${result.port || 8765}` });
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
setMessage({ ok: false, text: result.error || 'Echec lancement du bridge' });
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
setMessage({ ok: false, text: 'Erreur reseau' });
|
|
283
|
-
}
|
|
284
|
-
finally {
|
|
285
|
-
setBridgeLoading(false);
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
const handleStopBridge = async () => {
|
|
289
|
-
setBridgeLoading(true);
|
|
275
|
+
const patchAction = async (payload, actionLabel) => {
|
|
276
|
+
setLoading(actionLabel);
|
|
290
277
|
setMessage(null);
|
|
291
278
|
try {
|
|
292
279
|
const res = await fetch(jarEndpoint, {
|
|
293
280
|
method: 'PATCH',
|
|
294
281
|
headers: { 'Content-Type': 'application/json' },
|
|
295
|
-
body: JSON.stringify(
|
|
282
|
+
body: JSON.stringify(payload),
|
|
296
283
|
});
|
|
297
284
|
const result = await res.json();
|
|
298
285
|
if (result.ok) {
|
|
299
|
-
|
|
300
|
-
|
|
286
|
+
setMessage({ ok: true, text: result.message || actionLabel + ' OK' });
|
|
287
|
+
if (result.port && payload.action === 'start')
|
|
288
|
+
setBridgePort(result.port);
|
|
289
|
+
if (payload.action === 'stop')
|
|
290
|
+
setBridgePort(null);
|
|
301
291
|
}
|
|
302
292
|
else {
|
|
303
|
-
setMessage({ ok: false, text: result.error || 'Echec
|
|
293
|
+
setMessage({ ok: false, text: result.error || 'Echec ' + actionLabel });
|
|
304
294
|
}
|
|
295
|
+
loadStatus();
|
|
305
296
|
}
|
|
306
297
|
catch {
|
|
307
298
|
setMessage({ ok: false, text: 'Erreur reseau' });
|
|
308
299
|
}
|
|
309
300
|
finally {
|
|
310
|
-
|
|
301
|
+
setLoading(null);
|
|
311
302
|
}
|
|
312
303
|
};
|
|
313
|
-
|
|
304
|
+
const isHsqldb = dialect === 'hsqldb';
|
|
305
|
+
const btnSmall = (color, disabled) => ({
|
|
306
|
+
...S.btn('primary', disabled), fontSize: 12, padding: '6px 14px',
|
|
307
|
+
backgroundColor: disabled ? '#9ca3af' : color, cursor: disabled ? 'not-allowed' : 'pointer',
|
|
308
|
+
borderRadius: 6, border: 'none', color: '#fff', fontWeight: 600,
|
|
309
|
+
});
|
|
310
|
+
return (_jsxs("div", { style: S.jarBox, children: [_jsxs("div", { style: S.flex(8), children: [_jsx("span", { style: S.jarTitle, children: "Driver JDBC" }), jarStatus?.hasJar ? (_jsx("span", { style: { ...S.badge('installed'), marginLeft: 0 }, children: jarStatus.jarFile })) : (_jsx("span", { style: { fontSize: 12, color: '#6b7280' }, children: "Aucun JAR installe" }))] }), _jsxs("div", { style: { marginTop: 8 }, children: [_jsxs("label", { style: { ...S.btn('primary', uploading), cursor: uploading ? 'wait' : 'pointer', fontSize: 12, padding: '6px 12px' }, children: [uploading ? 'Upload...' : 'Uploader un .jar', _jsx("input", { type: "file", accept: ".jar", onChange: handleUpload, disabled: uploading, style: { display: 'none' } })] }), _jsx("span", { style: { fontSize: 11, color: '#9ca3af', marginLeft: 8 }, children: "Ex: hsqldb*.jar, ojdbc*.jar" })] }), isHsqldb && jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#fef9c3', borderRadius: 6, border: '1px solid #fde68a' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#92400e', marginBottom: 6 }, children: ["Serveur HSQLDB", serverInfo?.running && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["En marche (port ", serverInfo.port, serverInfo.pid > 0 ? `, PID ${serverInfo.pid}` : '', ")"] })), !serverInfo?.running && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', marginLeft: 8 }, children: "Arrete" }))] }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { style: btnSmall('#059669', loading === 'start-server' || serverInfo?.running), onClick: () => patchAction({ action: 'start-server', dialect, name: dbConfig.name, host: dbConfig.host, port: dbConfig.port }, 'start-server'), disabled: loading === 'start-server' || !!serverInfo?.running, children: loading === 'start-server' ? 'Demarrage...' : 'Demarrer le serveur' }), _jsx("button", { style: btnSmall('#dc2626', loading === 'stop-server' || !serverInfo?.running), onClick: () => patchAction({ action: 'stop-server', port: serverInfo?.port || dbConfig.port || 9001 }, 'stop-server'), disabled: loading === 'stop-server' || !serverInfo?.running, children: loading === 'stop-server' ? 'Arret...' : 'Arreter le serveur' })] })] })), jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#f0fdf4', borderRadius: 6, border: '1px solid #bbf7d0' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#166534', marginBottom: 6 }, children: ["Bridge JDBC", bridgePort && _jsxs("span", { style: { fontWeight: 400, marginLeft: 8 }, children: ["Actif port ", bridgePort] }), !bridgePort && bridges.length === 0 && _jsx("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 8 }, children: "Inactif" })] }), _jsx("div", { style: { display: 'flex', gap: 8, marginBottom: bridges.length > 0 ? 8 : 0 }, children: _jsx("button", { style: btnSmall('#059669', loading === 'start-bridge' || bridges.length > 0), onClick: () => patchAction({ action: 'start', dialect, ...dbConfig }, 'start-bridge'), disabled: loading === 'start-bridge' || bridges.length > 0, children: loading === 'start-bridge' ? 'Lancement...' : 'Lancer le bridge' }) }), bridges.map(b => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', fontSize: 12 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', backgroundColor: b.status === 'active' ? '#22c55e' : '#f59e0b', flexShrink: 0 } }), _jsxs("span", { style: { color: '#374151', fontFamily: 'monospace', fontSize: 11 }, children: [":", b.port, " ", b.pid > 0 ? `(PID ${b.pid})` : '', " ", b.jdbcUrl ? `— ${b.jdbcUrl}` : ''] }), _jsx("button", { style: { ...btnSmall('#dc2626', loading === `kill-${b.port}`), fontSize: 11, padding: '2px 8px', marginLeft: 'auto' }, onClick: () => patchAction({ action: 'stop', port: b.port, pid: b.pid }, `kill-${b.port}`), disabled: loading === `kill-${b.port}`, children: loading === `kill-${b.port}` ? '...' : 'Kill' })] }, b.port)))] })), message && (_jsx("p", { style: { fontSize: 12, color: message.ok ? '#059669' : '#dc2626', marginTop: 8 }, children: message.text }))] }));
|
|
314
311
|
}
|
|
315
312
|
// ── Main Component ───────────────────────────────────────────
|
|
316
313
|
export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
|
|
@@ -537,7 +534,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
537
534
|
const isSelected = selectedModules.includes(mod.key);
|
|
538
535
|
const isDetected = detectedModules.includes(mod.key);
|
|
539
536
|
return (_jsxs("div", { style: S.moduleCard(isSelected, !!mod.required), onClick: () => toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsxs("div", { style: S.moduleLeft, children: [_jsx("span", { style: { fontSize: 20 }, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label })] }), _jsxs("div", { style: S.moduleBadges, children: [mod.discovered && _jsx("span", { style: S.badge('new'), children: "Nouveau" }), isDetected && _jsx("span", { style: S.badge('installed'), children: t('setup.modules.installed') }), mod.required && _jsx("span", { style: S.badge('required'), children: t('setup.modules.required') }), _jsx("input", { type: "checkbox", checked: isSelected, disabled: mod.required, readOnly: true, style: S.checkbox })] })] }), _jsx("div", { style: S.moduleDesc, children: mod.description }), mod.dependsOn?.length ? (_jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Depend de : ", mod.dependsOn.join(', ')] })) : null] }, mod.key));
|
|
540
|
-
}) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }),
|
|
537
|
+
}) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.checkRow, marginTop: 12, padding: '10px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsx("span", { style: { fontSize: 13, color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
|
|
541
538
|
? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
|
|
542
539
|
: `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }))] })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
|
|
543
540
|
const mod = availableModules.find(m => m.key === key);
|
package/dist/lib/compose-uri.js
CHANGED
|
@@ -28,6 +28,8 @@ export function composeDbUri(dialect, config) {
|
|
|
28
28
|
case 'hana':
|
|
29
29
|
return `hana://${eu}:${ep}@${host}:${port}`;
|
|
30
30
|
case 'hsqldb':
|
|
31
|
+
if (user)
|
|
32
|
+
return `hsqldb:hsql://${eu}:${ep}@${host}:${port}/${name}`;
|
|
31
33
|
return `hsqldb:hsql://${host}:${port}/${name}`;
|
|
32
34
|
case 'spanner':
|
|
33
35
|
return `spanner://projects/${name}`;
|
package/package.json
CHANGED