@mostajs/setup 1.1.2 → 1.1.3
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/api/reconfig.route.d.ts +18 -0
- package/dist/api/reconfig.route.js +74 -0
- package/dist/components/ReconfigPanel.d.ts +17 -0
- package/dist/components/ReconfigPanel.js +327 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lib/menu.js +7 -1
- package/package.json +17 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ReconfigHandlerConfig {
|
|
2
|
+
/** Callback to get current env values */
|
|
3
|
+
getCurrentConfig?: () => {
|
|
4
|
+
dialect: string;
|
|
5
|
+
uri: string;
|
|
6
|
+
modules: string[];
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates handlers for the reconfiguration API.
|
|
11
|
+
*
|
|
12
|
+
* GET — returns current config (dialect, active modules)
|
|
13
|
+
* POST — applies new config (db change, module toggle)
|
|
14
|
+
*/
|
|
15
|
+
export declare function createReconfigHandlers(config?: ReconfigHandlerConfig): {
|
|
16
|
+
GET: () => Promise<Response>;
|
|
17
|
+
POST: (req: Request) => Promise<Response>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @mostajs/setup — API Route factory for reconfiguration
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import { testDbConnection } from '../lib/db-test';
|
|
4
|
+
import { writeEnvLocal } from '../lib/env-writer';
|
|
5
|
+
import { composeDbUri } from '../lib/compose-uri';
|
|
6
|
+
/**
|
|
7
|
+
* Creates handlers for the reconfiguration API.
|
|
8
|
+
*
|
|
9
|
+
* GET — returns current config (dialect, active modules)
|
|
10
|
+
* POST — applies new config (db change, module toggle)
|
|
11
|
+
*/
|
|
12
|
+
export function createReconfigHandlers(config) {
|
|
13
|
+
async function GET() {
|
|
14
|
+
const dialect = process.env.DB_DIALECT || 'mongodb';
|
|
15
|
+
const uri = process.env.SGBD_URI || '';
|
|
16
|
+
const modulesStr = process.env.MOSTAJS_MODULES || '';
|
|
17
|
+
const modules = modulesStr ? modulesStr.split(',').map((m) => m.trim()) : [];
|
|
18
|
+
return Response.json({
|
|
19
|
+
dialect,
|
|
20
|
+
uri,
|
|
21
|
+
modules,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function POST(req) {
|
|
25
|
+
try {
|
|
26
|
+
const body = await req.json();
|
|
27
|
+
const { action } = body;
|
|
28
|
+
if (action === 'test-db') {
|
|
29
|
+
const { dialect, host, port, name, user, password } = body;
|
|
30
|
+
const result = await testDbConnection({ dialect, host, port, name, user, password });
|
|
31
|
+
return Response.json(result);
|
|
32
|
+
}
|
|
33
|
+
if (action === 'change-db') {
|
|
34
|
+
const { dialect, host, port, name, user, password } = body;
|
|
35
|
+
// Test first
|
|
36
|
+
const test = await testDbConnection({ dialect, host, port, name, user, password });
|
|
37
|
+
if (!test.ok) {
|
|
38
|
+
return Response.json({ ok: false, error: test.error || 'Connexion echouee' });
|
|
39
|
+
}
|
|
40
|
+
// Write .env.local
|
|
41
|
+
const uri = composeDbUri(dialect, { host, port, name, user, password });
|
|
42
|
+
const needsRestart = await writeEnvLocal({ dialect, uri });
|
|
43
|
+
// Update process.env in-memory
|
|
44
|
+
process.env.DB_DIALECT = dialect;
|
|
45
|
+
process.env.SGBD_URI = uri;
|
|
46
|
+
// Disconnect existing dialect
|
|
47
|
+
try {
|
|
48
|
+
const { disconnectDialect } = await import('@mostajs/orm');
|
|
49
|
+
await disconnectDialect();
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
return Response.json({ ok: true, needsRestart });
|
|
53
|
+
}
|
|
54
|
+
if (action === 'update-modules') {
|
|
55
|
+
const { modules } = body;
|
|
56
|
+
const modulesStr = modules.join(',');
|
|
57
|
+
// Read current .env.local and update MOSTAJS_MODULES
|
|
58
|
+
const needsRestart = await writeEnvLocal({
|
|
59
|
+
dialect: (process.env.DB_DIALECT || 'mongodb'),
|
|
60
|
+
uri: process.env.SGBD_URI || '',
|
|
61
|
+
extraVars: { MOSTAJS_MODULES: modulesStr },
|
|
62
|
+
});
|
|
63
|
+
process.env.MOSTAJS_MODULES = modulesStr;
|
|
64
|
+
return Response.json({ ok: true, needsRestart, modules });
|
|
65
|
+
}
|
|
66
|
+
return Response.json({ ok: false, error: 'Action inconnue' }, { status: 400 });
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const message = err instanceof Error ? err.message : 'Erreur serveur';
|
|
70
|
+
return Response.json({ ok: false, error: message }, { status: 500 });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { GET, POST };
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ReconfigPanelProps {
|
|
2
|
+
/** API endpoint for reconfiguration (default: '/api/setup/reconfig') */
|
|
3
|
+
apiEndpoint?: string;
|
|
4
|
+
/** API endpoint for module detection (default: '/api/setup/detect-modules') */
|
|
5
|
+
detectEndpoint?: string;
|
|
6
|
+
/** Translate function */
|
|
7
|
+
t?: (key: string) => string;
|
|
8
|
+
/** Called after successful DB change */
|
|
9
|
+
onDbChanged?: (needsRestart: boolean) => void;
|
|
10
|
+
/** Called after modules update */
|
|
11
|
+
onModulesChanged?: (modules: string[]) => void;
|
|
12
|
+
/** Whether to show seed option on DB change (default: true) */
|
|
13
|
+
showSeedOption?: boolean;
|
|
14
|
+
/** Callback to run seed after DB change */
|
|
15
|
+
onSeedRequested?: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export default function ReconfigPanel({ apiEndpoint, detectEndpoint, t, onDbChanged, onModulesChanged, showSeedOption, onSeedRequested, }: ReconfigPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// @mostajs/setup — Reconfiguration Panel
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
'use client';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
const DIALECTS = [
|
|
7
|
+
{ key: 'mongodb', name: 'MongoDB', icon: '🍃', defaultPort: 27017, defaultUser: '', defaultHost: 'localhost', requiresAuth: false },
|
|
8
|
+
{ key: 'sqlite', name: 'SQLite', icon: '📄', defaultPort: 0, defaultUser: '', defaultHost: '', requiresAuth: false },
|
|
9
|
+
{ key: 'postgres', name: 'PostgreSQL', icon: '🐘', defaultPort: 5432, defaultUser: 'postgres', defaultHost: 'localhost', requiresAuth: true },
|
|
10
|
+
{ key: 'mysql', name: 'MySQL', icon: '🐬', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
11
|
+
{ key: 'mariadb', name: 'MariaDB', icon: '🦭', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
12
|
+
{ key: 'mssql', name: 'SQL Server', icon: '🟦', defaultPort: 1433, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true },
|
|
13
|
+
{ key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true },
|
|
14
|
+
{ key: 'cockroachdb', name: 'CockroachDB', icon: '🪳', defaultPort: 26257, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
15
|
+
];
|
|
16
|
+
// ── Styles ────────────────────────────────────────────────────
|
|
17
|
+
const S = {
|
|
18
|
+
panel: { fontFamily: 'system-ui, sans-serif', fontSize: 14 },
|
|
19
|
+
section: { marginBottom: 32, padding: 24, border: '1px solid #e5e7eb', borderRadius: 12, backgroundColor: '#fff' },
|
|
20
|
+
sectionTitle: { fontSize: 18, fontWeight: 700, marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 },
|
|
21
|
+
sectionDesc: { fontSize: 13, color: '#6b7280', marginBottom: 16 },
|
|
22
|
+
grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 },
|
|
23
|
+
moduleCard: (active, required) => ({
|
|
24
|
+
padding: 16,
|
|
25
|
+
border: `2px solid ${active ? '#0284c7' : '#e5e7eb'}`,
|
|
26
|
+
borderRadius: 8,
|
|
27
|
+
backgroundColor: active ? '#f0f9ff' : '#fafafa',
|
|
28
|
+
cursor: required ? 'not-allowed' : 'pointer',
|
|
29
|
+
opacity: required ? 0.8 : 1,
|
|
30
|
+
transition: 'all 0.2s',
|
|
31
|
+
}),
|
|
32
|
+
moduleHeader: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 },
|
|
33
|
+
moduleIcon: { fontSize: 20 },
|
|
34
|
+
moduleName: { fontWeight: 600, fontSize: 14 },
|
|
35
|
+
moduleDesc: { fontSize: 12, color: '#6b7280' },
|
|
36
|
+
moduleBadge: (type) => ({
|
|
37
|
+
display: 'inline-block',
|
|
38
|
+
padding: '1px 6px',
|
|
39
|
+
borderRadius: 4,
|
|
40
|
+
fontSize: 10,
|
|
41
|
+
fontWeight: 600,
|
|
42
|
+
backgroundColor: type === 'required' ? '#fef3c7' : type === 'active' ? '#d1fae5' : '#e0e7ff',
|
|
43
|
+
color: type === 'required' ? '#92400e' : type === 'active' ? '#065f46' : '#3730a3',
|
|
44
|
+
marginLeft: 4,
|
|
45
|
+
}),
|
|
46
|
+
dialectGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))', gap: 8 },
|
|
47
|
+
dialectCard: (selected) => ({
|
|
48
|
+
padding: 12,
|
|
49
|
+
border: `2px solid ${selected ? '#0284c7' : '#e5e7eb'}`,
|
|
50
|
+
borderRadius: 8,
|
|
51
|
+
backgroundColor: selected ? '#f0f9ff' : '#fff',
|
|
52
|
+
cursor: 'pointer',
|
|
53
|
+
textAlign: 'center',
|
|
54
|
+
transition: 'all 0.2s',
|
|
55
|
+
}),
|
|
56
|
+
dialectIcon: { fontSize: 24, marginBottom: 4 },
|
|
57
|
+
dialectName: { fontSize: 12, fontWeight: 600 },
|
|
58
|
+
formGroup: { marginBottom: 12 },
|
|
59
|
+
label: { display: 'block', marginBottom: 4, fontWeight: 500, fontSize: 12, color: '#374151' },
|
|
60
|
+
input: { width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: 6, fontSize: 13, boxSizing: 'border-box' },
|
|
61
|
+
formRow: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 },
|
|
62
|
+
btn: (variant = 'primary') => ({
|
|
63
|
+
padding: '8px 16px',
|
|
64
|
+
border: 'none',
|
|
65
|
+
borderRadius: 6,
|
|
66
|
+
fontSize: 13,
|
|
67
|
+
fontWeight: 600,
|
|
68
|
+
cursor: 'pointer',
|
|
69
|
+
display: 'inline-flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
gap: 6,
|
|
72
|
+
backgroundColor: variant === 'primary' ? '#0284c7' :
|
|
73
|
+
variant === 'success' ? '#059669' :
|
|
74
|
+
variant === 'danger' ? '#dc2626' : '#e5e7eb',
|
|
75
|
+
color: variant === 'secondary' ? '#374151' : '#fff',
|
|
76
|
+
}),
|
|
77
|
+
alert: (type) => ({
|
|
78
|
+
padding: 12,
|
|
79
|
+
borderRadius: 8,
|
|
80
|
+
marginBottom: 16,
|
|
81
|
+
fontSize: 13,
|
|
82
|
+
backgroundColor: type === 'success' ? '#d1fae5' :
|
|
83
|
+
type === 'error' ? '#fee2e2' :
|
|
84
|
+
type === 'warning' ? '#fef3c7' : '#dbeafe',
|
|
85
|
+
color: type === 'success' ? '#065f46' :
|
|
86
|
+
type === 'error' ? '#991b1b' :
|
|
87
|
+
type === 'warning' ? '#92400e' : '#1e40af',
|
|
88
|
+
border: `1px solid ${type === 'success' ? '#a7f3d0' :
|
|
89
|
+
type === 'error' ? '#fecaca' :
|
|
90
|
+
type === 'warning' ? '#fde68a' : '#bfdbfe'}`,
|
|
91
|
+
}),
|
|
92
|
+
checkbox: { marginRight: 8, width: 16, height: 16, cursor: 'pointer' },
|
|
93
|
+
checkboxLabel: { display: 'flex', alignItems: 'center', fontSize: 13, cursor: 'pointer', padding: '8px 0' },
|
|
94
|
+
currentBadge: {
|
|
95
|
+
display: 'inline-block',
|
|
96
|
+
padding: '2px 8px',
|
|
97
|
+
borderRadius: 12,
|
|
98
|
+
fontSize: 11,
|
|
99
|
+
fontWeight: 600,
|
|
100
|
+
backgroundColor: '#d1fae5',
|
|
101
|
+
color: '#065f46',
|
|
102
|
+
marginLeft: 8,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
// ── Component ────────────────────────────────────────────────
|
|
106
|
+
export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', detectEndpoint = '/api/setup/detect-modules', t = (k) => k, onDbChanged, onModulesChanged, showSeedOption = true, onSeedRequested, }) {
|
|
107
|
+
// --- State ---
|
|
108
|
+
const [loading, setLoading] = useState(true);
|
|
109
|
+
const [currentDialect, setCurrentDialect] = useState('');
|
|
110
|
+
const [currentModules, setCurrentModules] = useState([]);
|
|
111
|
+
const [allModules, setAllModules] = useState([]);
|
|
112
|
+
const [installedModules, setInstalledModules] = useState([]);
|
|
113
|
+
const [activeModules, setActiveModules] = useState(new Set());
|
|
114
|
+
// DB form
|
|
115
|
+
const [selectedDialect, setSelectedDialect] = useState('mongodb');
|
|
116
|
+
const [dbForm, setDbForm] = useState({ host: 'localhost', port: 27017, name: '', user: '', password: '' });
|
|
117
|
+
const [dbTesting, setDbTesting] = useState(false);
|
|
118
|
+
const [dbTestResult, setDbTestResult] = useState(null);
|
|
119
|
+
const [dbSaving, setDbSaving] = useState(false);
|
|
120
|
+
const [dbMessage, setDbMessage] = useState(null);
|
|
121
|
+
const [wantSeed, setWantSeed] = useState(true);
|
|
122
|
+
const [seeding, setSeeding] = useState(false);
|
|
123
|
+
// Module saving
|
|
124
|
+
const [moduleSaving, setModuleSaving] = useState(false);
|
|
125
|
+
const [moduleMessage, setModuleMessage] = useState(null);
|
|
126
|
+
// --- Load current config ---
|
|
127
|
+
const loadConfig = useCallback(async () => {
|
|
128
|
+
setLoading(true);
|
|
129
|
+
try {
|
|
130
|
+
const [configRes, modulesRes] = await Promise.all([
|
|
131
|
+
fetch(apiEndpoint),
|
|
132
|
+
fetch(detectEndpoint),
|
|
133
|
+
]);
|
|
134
|
+
const config = await configRes.json();
|
|
135
|
+
const detected = await modulesRes.json();
|
|
136
|
+
setCurrentDialect(config.dialect);
|
|
137
|
+
setCurrentModules(config.modules || []);
|
|
138
|
+
setSelectedDialect(config.dialect);
|
|
139
|
+
setAllModules(detected.modules || []);
|
|
140
|
+
setInstalledModules(detected.installed || []);
|
|
141
|
+
// Active = modules in env or all installed if env empty
|
|
142
|
+
const active = config.modules?.length
|
|
143
|
+
? new Set(config.modules)
|
|
144
|
+
: new Set(detected.installed);
|
|
145
|
+
setActiveModules(active);
|
|
146
|
+
// Set DB form defaults for current dialect
|
|
147
|
+
const dialectInfo = DIALECTS.find((d) => d.key === config.dialect);
|
|
148
|
+
if (dialectInfo) {
|
|
149
|
+
setDbForm({
|
|
150
|
+
host: dialectInfo.defaultHost,
|
|
151
|
+
port: dialectInfo.defaultPort,
|
|
152
|
+
name: '',
|
|
153
|
+
user: dialectInfo.defaultUser,
|
|
154
|
+
password: '',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// ignore
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
setLoading(false);
|
|
163
|
+
}
|
|
164
|
+
}, [apiEndpoint, detectEndpoint]);
|
|
165
|
+
useEffect(() => { loadConfig(); }, [loadConfig]);
|
|
166
|
+
// --- Dialect change ---
|
|
167
|
+
const handleDialectChange = (dialect) => {
|
|
168
|
+
setSelectedDialect(dialect);
|
|
169
|
+
setDbTestResult(null);
|
|
170
|
+
setDbMessage(null);
|
|
171
|
+
const info = DIALECTS.find((d) => d.key === dialect);
|
|
172
|
+
if (info) {
|
|
173
|
+
setDbForm({
|
|
174
|
+
host: info.defaultHost,
|
|
175
|
+
port: info.defaultPort,
|
|
176
|
+
name: '',
|
|
177
|
+
user: info.defaultUser,
|
|
178
|
+
password: '',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
// --- Test DB ---
|
|
183
|
+
const handleTestDb = async () => {
|
|
184
|
+
setDbTesting(true);
|
|
185
|
+
setDbTestResult(null);
|
|
186
|
+
try {
|
|
187
|
+
const res = await fetch(apiEndpoint, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: { 'Content-Type': 'application/json' },
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
action: 'test-db',
|
|
192
|
+
dialect: selectedDialect,
|
|
193
|
+
...dbForm,
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
const result = await res.json();
|
|
197
|
+
setDbTestResult(result);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
setDbTestResult({ ok: false, error: 'Erreur reseau' });
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
setDbTesting(false);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// --- Apply DB change ---
|
|
207
|
+
const handleApplyDb = async () => {
|
|
208
|
+
setDbSaving(true);
|
|
209
|
+
setDbMessage(null);
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch(apiEndpoint, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
214
|
+
body: JSON.stringify({
|
|
215
|
+
action: 'change-db',
|
|
216
|
+
dialect: selectedDialect,
|
|
217
|
+
...dbForm,
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
const result = await res.json();
|
|
221
|
+
if (result.ok) {
|
|
222
|
+
setCurrentDialect(selectedDialect);
|
|
223
|
+
// Seed if requested
|
|
224
|
+
if (wantSeed && showSeedOption && onSeedRequested) {
|
|
225
|
+
setSeeding(true);
|
|
226
|
+
try {
|
|
227
|
+
await onSeedRequested();
|
|
228
|
+
setDbMessage({ type: 'success', text: 'Base de donnees changee et seed effectue. ' + (result.needsRestart ? 'Redemarrage necessaire.' : '') });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
setDbMessage({ type: 'warning', text: 'Base de donnees changee mais le seed a echoue. Vous pouvez relancer le seed manuellement.' });
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
setSeeding(false);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
setDbMessage({
|
|
239
|
+
type: result.needsRestart ? 'warning' : 'success',
|
|
240
|
+
text: result.needsRestart
|
|
241
|
+
? 'Base de donnees changee. Redemarrez le serveur pour appliquer.'
|
|
242
|
+
: 'Base de donnees changee avec succes.',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
onDbChanged?.(result.needsRestart);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
setDbMessage({ type: 'error', text: result.error || 'Erreur' });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
setDbMessage({ type: 'error', text: 'Erreur reseau' });
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
setDbSaving(false);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
// --- Toggle module ---
|
|
259
|
+
const toggleModule = (key) => {
|
|
260
|
+
const mod = allModules.find((m) => m.key === key);
|
|
261
|
+
if (mod?.required)
|
|
262
|
+
return;
|
|
263
|
+
setActiveModules((prev) => {
|
|
264
|
+
const next = new Set(prev);
|
|
265
|
+
if (next.has(key)) {
|
|
266
|
+
next.delete(key);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
next.add(key);
|
|
270
|
+
// Auto-add dependencies
|
|
271
|
+
if (mod?.dependsOn) {
|
|
272
|
+
for (const dep of mod.dependsOn)
|
|
273
|
+
next.add(dep);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return next;
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
// --- Save modules ---
|
|
280
|
+
const handleSaveModules = async () => {
|
|
281
|
+
setModuleSaving(true);
|
|
282
|
+
setModuleMessage(null);
|
|
283
|
+
try {
|
|
284
|
+
const modules = Array.from(activeModules);
|
|
285
|
+
const res = await fetch(apiEndpoint, {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers: { 'Content-Type': 'application/json' },
|
|
288
|
+
body: JSON.stringify({ action: 'update-modules', modules }),
|
|
289
|
+
});
|
|
290
|
+
const result = await res.json();
|
|
291
|
+
if (result.ok) {
|
|
292
|
+
setCurrentModules(modules);
|
|
293
|
+
setModuleMessage({
|
|
294
|
+
type: 'success',
|
|
295
|
+
text: result.needsRestart
|
|
296
|
+
? 'Modules mis a jour. Redemarrez le serveur pour appliquer.'
|
|
297
|
+
: 'Modules mis a jour avec succes.',
|
|
298
|
+
});
|
|
299
|
+
onModulesChanged?.(modules);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
setModuleMessage({ type: 'error', text: result.error || 'Erreur' });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
setModuleMessage({ type: 'error', text: 'Erreur reseau' });
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
setModuleSaving(false);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
// --- Render ---
|
|
313
|
+
if (loading) {
|
|
314
|
+
return _jsx("div", { style: { textAlign: 'center', padding: 40, color: '#6b7280' }, children: "Chargement de la configuration..." });
|
|
315
|
+
}
|
|
316
|
+
const dialectInfo = DIALECTS.find((d) => d.key === selectedDialect);
|
|
317
|
+
const isSqlite = selectedDialect === 'sqlite';
|
|
318
|
+
return (_jsxs("div", { style: S.panel, children: [_jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDCE6" }), " Modules actifs"] }), _jsx("div", { style: S.sectionDesc, children: "Activez ou desactivez les modules @mostajs. Les modules requis ne peuvent pas etre desactives. Seuls les modules installes (dans node_modules) peuvent etre actives." }), moduleMessage && (_jsx("div", { style: S.alert(moduleMessage.type), children: moduleMessage.text })), _jsx("div", { style: S.grid, children: allModules.map((mod) => {
|
|
319
|
+
const isInstalled = installedModules.includes(mod.key);
|
|
320
|
+
const isActive = activeModules.has(mod.key);
|
|
321
|
+
const isRequired = !!mod.required;
|
|
322
|
+
return (_jsxs("div", { style: {
|
|
323
|
+
...S.moduleCard(isActive, isRequired || !isInstalled),
|
|
324
|
+
opacity: isInstalled ? 1 : 0.5,
|
|
325
|
+
}, onClick: () => isInstalled && toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsx("span", { style: S.moduleIcon, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label }), isRequired && _jsx("span", { style: S.moduleBadge('required'), children: "requis" }), isActive && !isRequired && _jsx("span", { style: S.moduleBadge('active'), children: "actif" }), !isInstalled && _jsx("span", { style: S.moduleBadge('dep'), children: "non installe" })] }), _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));
|
|
326
|
+
}) }), _jsx("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: _jsx("button", { style: S.btn('primary'), onClick: handleSaveModules, disabled: moduleSaving, children: moduleSaving ? 'Enregistrement...' : 'Enregistrer les modules' }) })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDDC4\uFE0F" }), " Base de donnees", currentDialect && (_jsxs("span", { style: S.currentBadge, children: ["Actuelle : ", DIALECTS.find((d) => d.key === currentDialect)?.name || currentDialect] }))] }), _jsx("div", { style: S.sectionDesc, children: "Changez le dialecte ou les parametres de connexion. La connexion sera testee avant application." }), dbMessage && (_jsx("div", { style: S.alert(dbMessage.type), children: dbMessage.text })), _jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: { ...S.label, marginBottom: 8, fontSize: 13 }, children: "Dialecte" }), _jsx("div", { style: S.dialectGrid, children: DIALECTS.map((d) => (_jsxs("div", { style: S.dialectCard(selectedDialect === d.key), onClick: () => handleDialectChange(d.key), children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsxs("div", { style: S.dialectName, children: [d.name, d.key === currentDialect && _jsx("span", { style: { fontSize: 9, color: '#059669' }, children: " (actuel)" })] })] }, d.key))) })] }), !isSqlite && (_jsxs("div", { style: { maxWidth: 500 }, children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Hote" }), _jsx("input", { style: S.input, value: dbForm.host, onChange: (e) => setDbForm({ ...dbForm, host: e.target.value }), placeholder: "localhost" })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Port" }), _jsx("input", { style: S.input, type: "number", value: dbForm.port, onChange: (e) => setDbForm({ ...dbForm, port: Number(e.target.value) }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Nom de la base" }), _jsx("input", { style: S.input, value: dbForm.name, onChange: (e) => setDbForm({ ...dbForm, name: e.target.value }), placeholder: "ma_base_de_donnees" })] }), dialectInfo?.requiresAuth && (_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Utilisateur" }), _jsx("input", { style: S.input, value: dbForm.user, onChange: (e) => setDbForm({ ...dbForm, user: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Mot de passe" }), _jsx("input", { style: S.input, type: "password", value: dbForm.password, onChange: (e) => setDbForm({ ...dbForm, password: e.target.value }) })] })] }))] })), isSqlite && (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Nom du fichier (sans extension)" }), _jsx("input", { style: { ...S.input, maxWidth: 300 }, value: dbForm.name, onChange: (e) => setDbForm({ ...dbForm, name: e.target.value }), placeholder: "ma_base" }), _jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Le fichier sera cree dans ./data/", dbForm.name || 'ma_base', ".db"] })] })), dbTestResult && (_jsx("div", { style: S.alert(dbTestResult.ok ? 'success' : 'error'), children: dbTestResult.ok ? 'Connexion reussie !' : `Echec : ${dbTestResult.error}` })), showSeedOption && onSeedRequested && (selectedDialect !== currentDialect || dbForm.name) && (_jsx("div", { style: { marginBottom: 12, padding: 12, backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: _jsxs("label", { style: S.checkboxLabel, children: [_jsx("input", { type: "checkbox", checked: wantSeed, onChange: (e) => setWantSeed(e.target.checked), style: S.checkbox }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600, fontSize: 13 }, children: "Initialiser la nouvelle base (seed)" }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: "Cree les tables/collections, permissions, roles et categories dans la nouvelle base. Recommande si vous changez vers une base vide." })] })] }) })), _jsxs("div", { style: { display: 'flex', gap: 8, marginTop: 16 }, children: [_jsx("button", { style: S.btn('secondary'), onClick: handleTestDb, disabled: dbTesting || (!isSqlite && !dbForm.name), children: dbTesting ? 'Test en cours...' : 'Tester la connexion' }), _jsx("button", { style: S.btn('primary'), onClick: handleApplyDb, disabled: dbSaving || seeding || (!isSqlite && !dbForm.name) || (dbTestResult !== null && !dbTestResult.ok), children: seeding ? 'Seed en cours...' : dbSaving ? 'Application...' : 'Appliquer' })] })] })] }));
|
|
327
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export { createInstallHandler } from './api/install.route';
|
|
|
10
10
|
export { createStatusHandler } from './api/status.route';
|
|
11
11
|
export { createDetectModulesHandler } from './api/detect-modules.route';
|
|
12
12
|
export { createInstallModulesHandler } from './api/install-modules.route';
|
|
13
|
+
export { createReconfigHandlers } from './api/reconfig.route';
|
|
14
|
+
export { default as ReconfigPanel } from './components/ReconfigPanel';
|
|
13
15
|
export { setupMenuContribution } from './lib/menu';
|
|
14
16
|
export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index';
|
package/dist/index.js
CHANGED
|
@@ -16,5 +16,8 @@ export { createInstallHandler } from './api/install.route';
|
|
|
16
16
|
export { createStatusHandler } from './api/status.route';
|
|
17
17
|
export { createDetectModulesHandler } from './api/detect-modules.route';
|
|
18
18
|
export { createInstallModulesHandler } from './api/install-modules.route';
|
|
19
|
+
export { createReconfigHandlers } from './api/reconfig.route';
|
|
20
|
+
// Components
|
|
21
|
+
export { default as ReconfigPanel } from './components/ReconfigPanel';
|
|
19
22
|
// Menu contribution
|
|
20
23
|
export { setupMenuContribution } from './lib/menu';
|
package/dist/lib/menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @mostajs/setup — Menu contribution
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { Wrench } from 'lucide-react';
|
|
3
|
+
import { Wrench, Settings2 } from 'lucide-react';
|
|
4
4
|
export const setupMenuContribution = {
|
|
5
5
|
moduleKey: 'setup',
|
|
6
6
|
mergeIntoGroup: 'Administration',
|
|
@@ -12,5 +12,11 @@ export const setupMenuContribution = {
|
|
|
12
12
|
icon: Wrench,
|
|
13
13
|
permission: 'admin:access',
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
label: 'setup.reconfig.title',
|
|
17
|
+
href: '/dashboard/settings/reconfig',
|
|
18
|
+
icon: Settings2,
|
|
19
|
+
permission: 'admin:settings',
|
|
20
|
+
},
|
|
15
21
|
],
|
|
16
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/setup",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"types": "./dist/lib/menu.d.ts",
|
|
38
38
|
"import": "./dist/lib/menu.js",
|
|
39
39
|
"default": "./dist/lib/menu.js"
|
|
40
|
+
},
|
|
41
|
+
"./components/ReconfigPanel": {
|
|
42
|
+
"types": "./dist/components/ReconfigPanel.d.ts",
|
|
43
|
+
"import": "./dist/components/ReconfigPanel.js",
|
|
44
|
+
"default": "./dist/components/ReconfigPanel.js"
|
|
45
|
+
},
|
|
46
|
+
"./api/reconfig": {
|
|
47
|
+
"types": "./dist/api/reconfig.route.d.ts",
|
|
48
|
+
"import": "./dist/api/reconfig.route.js",
|
|
49
|
+
"default": "./dist/api/reconfig.route.js"
|
|
40
50
|
}
|
|
41
51
|
},
|
|
42
52
|
"files": [
|
|
@@ -73,14 +83,19 @@
|
|
|
73
83
|
"@mostajs/menu": "^1.0.2",
|
|
74
84
|
"@types/bcryptjs": "^2.4.0",
|
|
75
85
|
"@types/node": "^25.3.3",
|
|
86
|
+
"@types/react": "^19.2.14",
|
|
76
87
|
"typescript": "^5.6.0"
|
|
77
88
|
},
|
|
78
89
|
"peerDependencies": {
|
|
79
|
-
"@mostajs/menu": ">=1.0.2"
|
|
90
|
+
"@mostajs/menu": ">=1.0.2",
|
|
91
|
+
"react": ">=18.0.0"
|
|
80
92
|
},
|
|
81
93
|
"peerDependenciesMeta": {
|
|
82
94
|
"@mostajs/menu": {
|
|
83
95
|
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"react": {
|
|
98
|
+
"optional": true
|
|
84
99
|
}
|
|
85
100
|
}
|
|
86
101
|
}
|