4bnode 4.0.0 → 4.0.2

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.
Files changed (37) hide show
  1. package/README.md +10 -0
  2. package/add-login.js +2 -2
  3. package/index.js +57 -5
  4. package/lib/indexFile.js +60 -13
  5. package/package.json +1 -1
  6. package/skeleton/.4bnode/assets/js/App.js +115 -0
  7. package/skeleton/.4bnode/assets/js/Root.js +44 -0
  8. package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
  9. package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
  10. package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
  11. package/skeleton/.4bnode/assets/js/context.js +7 -0
  12. package/skeleton/.4bnode/assets/js/lib.js +83 -0
  13. package/skeleton/.4bnode/assets/js/main.js +5 -0
  14. package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
  15. package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
  16. package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
  17. package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
  18. package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
  19. package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
  20. package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
  21. package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
  22. package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
  23. package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
  24. package/skeleton/{dev-api.js → .4bnode/dev-api.js} +259 -114
  25. package/skeleton/.4bnode/ui.html +1556 -0
  26. package/skeleton/index.js +62 -16
  27. package/skeleton/package.json +1 -1
  28. package/skeleton/public/index.html +290 -7586
  29. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Bold.woff2 +0 -0
  30. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Light.woff2 +0 -0
  31. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Medium.woff2 +0 -0
  32. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Regular.woff2 +0 -0
  33. /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-SemiBold.woff2 +0 -0
  34. /package/skeleton/{public → .4bnode/assets}/images/mongo.png +0 -0
  35. /package/skeleton/{public → .4bnode/assets}/images/mysql.png +0 -0
  36. /package/skeleton/{public → .4bnode/assets}/images/postgresql.png +0 -0
  37. /package/skeleton/{public → .4bnode/assets}/images/sqlite.png +0 -0
@@ -0,0 +1,435 @@
1
+ import React from "react";
2
+ import { LuZap, LuRocket } from "react-icons/lu";
3
+ import { html, Icons } from "../lib.js";
4
+ import { useApp } from "../context.js";
5
+
6
+ export function DashboardPage() {
7
+ const { data, setPage, status } = useApp();
8
+ const modelCount = data.models?.length || 0;
9
+ const routeCount = data.routes?.length || 0;
10
+ const dbOk = data.mongoConfigured;
11
+ const totalFields =
12
+ data.models?.reduce((sum, m) => sum + (m.fields?.length || 0), 0) ||
13
+ 0;
14
+ const featuresActive = [
15
+ status?.socketConfigured,
16
+ status?.websocketConfigured,
17
+ status?.serialportConfigured,
18
+ ].filter(Boolean);
19
+
20
+ const steps = [
21
+ {
22
+ label: "Database",
23
+ done: dbOk,
24
+ page: "database",
25
+ icon: Icons.database,
26
+ },
27
+ {
28
+ label: "Schemas",
29
+ done: modelCount > 0,
30
+ page: "schemas",
31
+ icon: Icons.schema,
32
+ },
33
+ {
34
+ label: "Routes",
35
+ done: routeCount > 0,
36
+ page: "routes",
37
+ icon: Icons.route,
38
+ },
39
+ ];
40
+ const firstIncomplete = steps.findIndex((s) => !s.done);
41
+ const allDone = steps.every((s) => s.done);
42
+
43
+ const nextAction = !dbOk
44
+ ? {
45
+ label: "Connect Database",
46
+ page: "database",
47
+ desc: "Set up your database connection to get started",
48
+ }
49
+ : modelCount === 0
50
+ ? {
51
+ label: "Create First Schema",
52
+ page: "schemas",
53
+ desc: "Define your data models to structure your application",
54
+ }
55
+ : routeCount === 0
56
+ ? {
57
+ label: "Create First Route",
58
+ page: "routes",
59
+ desc: "Add API endpoints to expose your data",
60
+ }
61
+ : null;
62
+
63
+ return html`<div>
64
+ <div className="pipeline">
65
+ ${steps.map(
66
+ (step, i) =>
67
+ html`<${React.Fragment} key=${i}>
68
+ ${i > 0 &&
69
+ html`<div
70
+ className=${`pipeline-connector ${steps[i - 1].done ? "done" : ""}`}
71
+ />`}
72
+ <div
73
+ className="pipeline-step"
74
+ onClick=${() => setPage(step.page)}
75
+ style=${{ cursor: "pointer" }}
76
+ >
77
+ <div
78
+ className=${`pipeline-dot ${step.done ? "done" : i === firstIncomplete ? "current" : ""}`}
79
+ >
80
+ ${step.done ? "\u2713" : i + 1}
81
+ </div>
82
+ <div
83
+ className=${`pipeline-label ${step.done ? "done" : i === firstIncomplete ? "current" : ""}`}
84
+ >
85
+ ${step.label}
86
+ </div>
87
+ </div>
88
+ <//>`,
89
+ )}
90
+ </div>
91
+
92
+ ${nextAction &&
93
+ html`<div
94
+ className="card mb-6"
95
+ style=${{
96
+ borderColor: "var(--accent)",
97
+ borderWidth: "1px",
98
+ borderStyle: "solid",
99
+ background:
100
+ "linear-gradient(135deg, rgba(124,58,237,0.06), rgba(124,58,237,0.02))",
101
+ }}
102
+ >
103
+ <div className="flex items-center gap-4">
104
+ <div
105
+ style=${{
106
+ width: "44px",
107
+ height: "44px",
108
+ borderRadius: "12px",
109
+ background: "var(--accent-glow)",
110
+ display: "flex",
111
+ alignItems: "center",
112
+ justifyContent: "center",
113
+ flexShrink: 0,
114
+ }}
115
+ >
116
+ <${LuZap} size=${20} color="var(--accent-light)" />
117
+ </div>
118
+ <div style=${{ flex: 1 }}>
119
+ <div
120
+ style=${{
121
+ fontWeight: 600,
122
+ fontSize: "14px",
123
+ marginBottom: "2px",
124
+ }}
125
+ >
126
+ Next Step: ${nextAction.label}
127
+ </div>
128
+ <div className="text-sm text-muted">${nextAction.desc}</div>
129
+ </div>
130
+ <button
131
+ className="btn btn-primary"
132
+ onClick=${() => setPage(nextAction.page)}
133
+ >
134
+ ${nextAction.label}
135
+ </button>
136
+ </div>
137
+ </div>`}
138
+ ${allDone &&
139
+ html`<div
140
+ className="card mb-6"
141
+ style=${{
142
+ borderColor: "rgba(34,197,94,0.3)",
143
+ background:
144
+ "linear-gradient(135deg, rgba(34,197,94,0.06), rgba(34,197,94,0.02))",
145
+ }}
146
+ >
147
+ <div className="flex items-center gap-4">
148
+ <div
149
+ style=${{
150
+ width: "44px",
151
+ height: "44px",
152
+ borderRadius: "12px",
153
+ background: "var(--success-glow)",
154
+ display: "flex",
155
+ alignItems: "center",
156
+ justifyContent: "center",
157
+ flexShrink: 0,
158
+ fontSize: "20px",
159
+ }}
160
+ >
161
+ \u2713
162
+ </div>
163
+ <div style=${{ flex: 1 }}>
164
+ <div
165
+ style=${{
166
+ fontWeight: 600,
167
+ fontSize: "14px",
168
+ marginBottom: "2px",
169
+ color: "var(--success)",
170
+ }}
171
+ >
172
+ Project Ready
173
+ </div>
174
+ <div className="text-sm text-muted">
175
+ Database, schemas, and routes are configured. Add features or
176
+ refine your API.
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>`}
181
+
182
+ <div
183
+ className="stats-grid"
184
+ style=${{ gridTemplateColumns: "repeat(4, 1fr)" }}
185
+ >
186
+ <div
187
+ className="stat-card"
188
+ onClick=${() => setPage("database")}
189
+ style=${{ cursor: "pointer" }}
190
+ >
191
+ <div
192
+ className="flex items-center gap-2"
193
+ style=${{ marginBottom: "10px" }}
194
+ >
195
+ <span className="stat-label" style=${{ margin: 0 }}
196
+ >Database</span
197
+ >
198
+ <span
199
+ className=${`stat-badge ${dbOk ? "success" : "warning"}`}
200
+ style=${{ marginLeft: "auto" }}
201
+ >${dbOk ? "Connected" : "Pending"}</span
202
+ >
203
+ </div>
204
+ <span
205
+ className="stat-value"
206
+ style=${{ fontSize: "18px", fontWeight: 600 }}
207
+ >${dbOk ? data.dbType || "MongoDB" : "\u2014"}</span
208
+ >
209
+ </div>
210
+ <div
211
+ className="stat-card"
212
+ onClick=${() => setPage("schemas")}
213
+ style=${{ cursor: "pointer" }}
214
+ >
215
+ <span className="stat-label">Models</span>
216
+ <div className="flex items-center gap-2">
217
+ <span className="stat-value">${modelCount}</span>
218
+ <span
219
+ className="text-sm text-muted"
220
+ style=${{ marginTop: "6px" }}
221
+ >${totalFields} field${totalFields !== 1 ? "s" : ""}</span
222
+ >
223
+ </div>
224
+ </div>
225
+ <div
226
+ className="stat-card"
227
+ onClick=${() => setPage("routes")}
228
+ style=${{ cursor: "pointer" }}
229
+ >
230
+ <span className="stat-label">Routes</span>
231
+ <span className="stat-value">${routeCount}</span>
232
+ </div>
233
+ <div className="stat-card">
234
+ <span className="stat-label">Features</span>
235
+ <span className="stat-value">${featuresActive.length}</span>
236
+ <div
237
+ className="flex gap-1"
238
+ style=${{ flexWrap: "wrap", marginTop: "4px" }}
239
+ >
240
+ ${status?.socketConfigured &&
241
+ html`<span className="stat-badge info">Socket.io</span>`}
242
+ ${status?.websocketConfigured &&
243
+ html`<span className="stat-badge info">WebSocket</span>`}
244
+ ${status?.serialportConfigured &&
245
+ html`<span className="stat-badge info">Serial</span>`}
246
+ ${featuresActive.length === 0 &&
247
+ html`<span className="stat-badge info">None yet</span>`}
248
+ </div>
249
+ </div>
250
+ </div>
251
+
252
+ ${(modelCount > 0 || routeCount > 0) &&
253
+ html`<div className="grid-2">
254
+ ${modelCount > 0 &&
255
+ html`<div className="card">
256
+ <div className="flex items-center justify-between mb-3">
257
+ <div
258
+ className="card-title"
259
+ style=${{ margin: 0, fontSize: "13px" }}
260
+ >
261
+ Models
262
+ </div>
263
+ <button
264
+ className="btn btn-ghost btn-sm"
265
+ onClick=${() => setPage("schemas")}
266
+ style=${{ fontSize: "11px" }}
267
+ >
268
+ View All
269
+ </button>
270
+ </div>
271
+ ${data.models.slice(0, 5).map(
272
+ (m) =>
273
+ html`<div
274
+ key=${m.name}
275
+ className="flex items-center gap-3"
276
+ style=${{
277
+ padding: "8px 0",
278
+ borderBottom: "1px solid rgba(255,255,255,0.04)",
279
+ }}
280
+ >
281
+ <div
282
+ style=${{
283
+ width: "28px",
284
+ height: "28px",
285
+ borderRadius: "6px",
286
+ background: "var(--accent-glow)",
287
+ display: "flex",
288
+ alignItems: "center",
289
+ justifyContent: "center",
290
+ flexShrink: 0,
291
+ }}
292
+ >
293
+ <span
294
+ style=${{
295
+ fontSize: "11px",
296
+ fontWeight: 700,
297
+ color: "var(--accent-light)",
298
+ }}
299
+ >${m.name.charAt(0).toUpperCase()}</span
300
+ >
301
+ </div>
302
+ <div style=${{ flex: 1, minWidth: 0 }}>
303
+ <div style=${{ fontWeight: 500, fontSize: "13px" }}>
304
+ ${m.name}
305
+ </div>
306
+ <div
307
+ className="text-sm text-muted"
308
+ style=${{ fontSize: "11px" }}
309
+ >
310
+ ${m.fields?.length || 0} fields
311
+ </div>
312
+ </div>
313
+ <div className="flex gap-1">
314
+ ${(m.fields || []).filter((f) => f.required).length > 0 &&
315
+ html`<span
316
+ style=${{
317
+ fontSize: "9px",
318
+ padding: "2px 6px",
319
+ borderRadius: "4px",
320
+ background: "var(--success-glow)",
321
+ color: "var(--success)",
322
+ fontWeight: 600,
323
+ }}
324
+ >${(m.fields || []).filter((f) => f.required).length}
325
+ REQ</span
326
+ >`}
327
+ ${(m.fields || []).filter((f) => f.unique).length > 0 &&
328
+ html`<span
329
+ style=${{
330
+ fontSize: "9px",
331
+ padding: "2px 6px",
332
+ borderRadius: "4px",
333
+ background: "var(--warning-glow)",
334
+ color: "var(--warning)",
335
+ fontWeight: 600,
336
+ }}
337
+ >${(m.fields || []).filter((f) => f.unique).length}
338
+ UNQ</span
339
+ >`}
340
+ </div>
341
+ </div>`,
342
+ )}
343
+ ${modelCount > 5 &&
344
+ html`<div
345
+ className="text-sm text-muted"
346
+ style=${{ padding: "8px 0", textAlign: "center" }}
347
+ >
348
+ +${modelCount - 5} more
349
+ </div>`}
350
+ </div>`}
351
+ ${routeCount > 0 &&
352
+ html`<div className="card">
353
+ <div className="flex items-center justify-between mb-3">
354
+ <div
355
+ className="card-title"
356
+ style=${{ margin: 0, fontSize: "13px" }}
357
+ >
358
+ Routes
359
+ </div>
360
+ <button
361
+ className="btn btn-ghost btn-sm"
362
+ onClick=${() => setPage("routes")}
363
+ style=${{ fontSize: "11px" }}
364
+ >
365
+ View All
366
+ </button>
367
+ </div>
368
+ ${data.routes.slice(0, 5).map((r) => {
369
+ const rName = r.replace(".js", "");
370
+ return html`<div
371
+ key=${r}
372
+ className="flex items-center gap-3"
373
+ style=${{
374
+ padding: "8px 0",
375
+ borderBottom: "1px solid rgba(255,255,255,0.04)",
376
+ }}
377
+ >
378
+ <div
379
+ style=${{
380
+ width: "28px",
381
+ height: "28px",
382
+ borderRadius: "6px",
383
+ background: "rgba(59,130,246,0.1)",
384
+ display: "flex",
385
+ alignItems: "center",
386
+ justifyContent: "center",
387
+ flexShrink: 0,
388
+ }}
389
+ >
390
+ <span
391
+ style=${{
392
+ fontSize: "11px",
393
+ fontWeight: 700,
394
+ color: "#60a5fa",
395
+ }}
396
+ >${rName.charAt(0).toUpperCase()}</span
397
+ >
398
+ </div>
399
+ <div style=${{ flex: 1, minWidth: 0 }}>
400
+ <div style=${{ fontWeight: 500, fontSize: "13px" }}>
401
+ ${rName}
402
+ </div>
403
+ <div
404
+ className="text-sm text-muted"
405
+ style=${{
406
+ fontSize: "11px",
407
+ fontFamily: "'SF Mono', monospace",
408
+ }}
409
+ >
410
+ src/routes/${r}
411
+ </div>
412
+ </div>
413
+ </div>`;
414
+ })}
415
+ ${routeCount > 5 &&
416
+ html`<div
417
+ className="text-sm text-muted"
418
+ style=${{ padding: "8px 0", textAlign: "center" }}
419
+ >
420
+ +${routeCount - 5} more
421
+ </div>`}
422
+ </div>`}
423
+ </div>`}
424
+ ${modelCount === 0 &&
425
+ routeCount === 0 &&
426
+ !nextAction &&
427
+ html`<div className="card">
428
+ <div className="empty-state">
429
+ <div className="empty-icon"><${LuRocket} size=${36} /></div>
430
+ <h3>Ready to Build</h3>
431
+ <p>Start by creating schemas and routes for your API</p>
432
+ </div>
433
+ </div>`}
434
+ </div>`;
435
+ }