@mostajs/setup 1.1.2 → 1.1.4

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
@@ -24,7 +24,8 @@ Part of the [@mosta suite](https://mostajs.dev).
24
24
  5. [Les 13 dialectes supportes](#5-les-13-dialectes-supportes)
25
25
  6. [Systeme de modules](#6-systeme-de-modules)
26
26
  7. [Exemples avances](#7-exemples-avances)
27
- 8. [FAQ / Troubleshooting](#8-faq--troubleshooting)
27
+ 8. [Reconfiguration (post-installation)](#8-reconfiguration-post-installation)
28
+ 9. [FAQ / Troubleshooting](#9-faq--troubleshooting)
28
29
 
29
30
  ---
30
31
 
@@ -56,7 +57,10 @@ npm install @mostajs/setup @mostajs/orm
56
57
  │ ├── test-db.route.ts # Factory POST /api/setup/test-db
57
58
  │ ├── install.route.ts # Factory POST /api/setup/install
58
59
  │ ├── detect-modules.route.ts # Factory GET /api/setup/detect-modules
59
- └── install-modules.route.ts # Factory POST /api/setup/install-modules
60
+ ├── install-modules.route.ts # Factory POST /api/setup/install-modules
61
+ │ └── reconfig.route.ts # Factory GET+POST /api/setup/reconfig
62
+ ├── components/
63
+ │ └── ReconfigPanel.tsx # UI reconfiguration (modules + DB)
60
64
  ├── types/
61
65
  │ └── index.ts # Tous les types TypeScript
62
66
  └── index.ts # Barrel exports
@@ -922,7 +926,87 @@ const dialectChanged = await writeEnvLocal({
922
926
 
923
927
  ---
924
928
 
925
- ## 8. FAQ / Troubleshooting
929
+ ## 8. Reconfiguration (post-installation)
930
+
931
+ Apres l'installation initiale, le module fournit un **panneau de reconfiguration** permettant de :
932
+ - Changer de base de donnees (dialecte, connexion, test)
933
+ - Activer / desactiver des modules @mostajs
934
+ - Optionnellement re-seeder la nouvelle base
935
+
936
+ ### Integration dans le projet hote
937
+
938
+ Ce module exporte un **composant React** (`ReconfigPanel`) et une **factory API** (`createReconfigHandlers`),
939
+ mais ne cree pas de pages Next.js. Le projet hote doit creer la route API et la page.
940
+
941
+ #### 1. Route API
942
+
943
+ **`src/app/api/setup/reconfig/route.ts`**
944
+ ```typescript
945
+ import { createReconfigHandlers } from '@mostajs/setup/api/reconfig'
946
+
947
+ const { GET, POST } = createReconfigHandlers()
948
+ export { GET, POST }
949
+ ```
950
+
951
+ #### 2. Page de reconfiguration
952
+
953
+ **`src/app/dashboard/settings/reconfig/page.tsx`**
954
+ ```tsx
955
+ 'use client'
956
+ import ReconfigPanel from '@mostajs/setup/components/ReconfigPanel'
957
+
958
+ export default function ReconfigPage() {
959
+ return (
960
+ <div className="space-y-6">
961
+ <h1 className="text-2xl font-bold">Reconfiguration</h1>
962
+ <ReconfigPanel
963
+ apiEndpoint="/api/setup/reconfig"
964
+ detectEndpoint="/api/setup/detect-modules"
965
+ showSeedOption
966
+ onDbChanged={() => window.location.reload()}
967
+ onSeedRequested={async () => {
968
+ await fetch('/api/setup/install', {
969
+ method: 'POST',
970
+ headers: { 'Content-Type': 'application/json' },
971
+ body: JSON.stringify({ action: 'seed-only' }),
972
+ })
973
+ }}
974
+ />
975
+ </div>
976
+ )
977
+ }
978
+ ```
979
+
980
+ #### 3. Menu dynamique
981
+
982
+ Le module exporte `setupMenuContribution` qui declare la route `/dashboard/settings/reconfig`
983
+ dans le groupe "Administration". Importez-le via le deep import :
984
+
985
+ ```tsx
986
+ import { setupMenuContribution } from '@mostajs/setup/lib/menu'
987
+ ```
988
+
989
+ #### 4. Props de ReconfigPanel
990
+
991
+ | Prop | Type | Description |
992
+ |------|------|-------------|
993
+ | `apiEndpoint` | `string` | URL de l'API reconfig (ex: `/api/setup/reconfig`) |
994
+ | `detectEndpoint` | `string` | URL de l'API detect-modules (ex: `/api/setup/detect-modules`) |
995
+ | `t` | `(key: string) => string` | Fonction de traduction (optionnel) |
996
+ | `showSeedOption` | `boolean` | Afficher la checkbox "Re-seeder" lors d'un changement de DB |
997
+ | `onDbChanged` | `() => void` | Callback apres changement de DB reussi |
998
+ | `onModulesChanged` | `(modules: string[]) => void` | Callback apres maj des modules |
999
+ | `onSeedRequested` | `() => Promise<void>` | Callback pour executer le seed |
1000
+
1001
+ #### 5. Pourquoi le projet hote doit creer les pages ?
1002
+
1003
+ Les modules `@mostajs/*` sont des **bibliotheques npm** (composants, hooks, utilitaires), pas des applications.
1004
+ Next.js App Router exige que les fichiers `page.tsx` soient dans le dossier `src/app/` du projet.
1005
+ Un package npm ne peut pas injecter de pages dans le routeur — c'est donc au projet hote de creer ces fichiers wrapper, meme s'ils ne font qu'importer et afficher un composant du module.
1006
+
1007
+ ---
1008
+
1009
+ ## 9. FAQ / Troubleshooting
926
1010
 
927
1011
  ### L'installation tourne en boucle (GET /setup se repete)
928
1012
 
@@ -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.2",
3
+ "version": "1.1.4",
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
  }