4bnode 3.5.3 → 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 (62) hide show
  1. package/README.md +144 -109
  2. package/add-api.js +288 -156
  3. package/add-crud.js +226 -0
  4. package/add-login.js +152 -319
  5. package/add-mongo-delete.js +67 -138
  6. package/add-mongo-insert.js +87 -204
  7. package/add-mongo-read.js +100 -216
  8. package/add-mongo-schema.js +431 -199
  9. package/add-mongo-update.js +89 -202
  10. package/add-mongo.js +125 -218
  11. package/add-serialport.js +53 -98
  12. package/add-socket.js +43 -66
  13. package/add-websocket.js +68 -140
  14. package/index.js +341 -156
  15. package/lib/env.js +18 -0
  16. package/lib/indexFile.js +134 -0
  17. package/lib/names.js +21 -0
  18. package/lib/project.js +122 -0
  19. package/lib/routeFile.js +56 -0
  20. package/lib/ui.js +161 -0
  21. package/package.json +9 -16
  22. package/skeleton/.4bnode/assets/fonts/NotoSans-Bold.woff2 +0 -0
  23. package/skeleton/.4bnode/assets/fonts/NotoSans-Light.woff2 +0 -0
  24. package/skeleton/.4bnode/assets/fonts/NotoSans-Medium.woff2 +0 -0
  25. package/skeleton/.4bnode/assets/fonts/NotoSans-Regular.woff2 +0 -0
  26. package/skeleton/.4bnode/assets/fonts/NotoSans-SemiBold.woff2 +0 -0
  27. package/skeleton/.4bnode/assets/images/mongo.png +0 -0
  28. package/skeleton/.4bnode/assets/images/mysql.png +0 -0
  29. package/skeleton/.4bnode/assets/images/postgresql.png +0 -0
  30. package/skeleton/.4bnode/assets/images/sqlite.png +0 -0
  31. package/skeleton/.4bnode/assets/js/App.js +115 -0
  32. package/skeleton/.4bnode/assets/js/Root.js +44 -0
  33. package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
  34. package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
  35. package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
  36. package/skeleton/.4bnode/assets/js/context.js +7 -0
  37. package/skeleton/.4bnode/assets/js/lib.js +83 -0
  38. package/skeleton/.4bnode/assets/js/main.js +5 -0
  39. package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
  40. package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
  41. package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
  42. package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
  43. package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
  44. package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
  45. package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
  46. package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
  47. package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
  48. package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
  49. package/skeleton/.4bnode/dev-api.js +2930 -0
  50. package/skeleton/.4bnode/ui.html +1556 -0
  51. package/skeleton/index.js +70 -17
  52. package/skeleton/package.json +9 -5
  53. package/skeleton/public/index.html +288 -86
  54. package/README_4bnode.md +0 -197
  55. package/src/auth/passport.js +0 -21
  56. package/src/db.js +0 -13
  57. package/test-server/.env.development +0 -1
  58. package/test-server/.env.production +0 -1
  59. package/test-server/index.js +0 -82
  60. package/test-server/package-lock.json +0 -1267
  61. package/test-server/package.json +0 -20
  62. package/test-server/public/index.html +0 -90
@@ -0,0 +1,44 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { AnimatePresence } from "framer-motion";
3
+ import { motion } from "framer-motion";
4
+ import { html, API, getStoredPasskey } from "./lib.js";
5
+ import { App } from "./App.js";
6
+ import { SplashScreen } from "./screens/SplashScreen.js";
7
+ import { PasskeyScreen } from "./screens/PasskeyScreen.js";
8
+
9
+ export function Root() {
10
+ const [showSplash, setShowSplash] = useState(true);
11
+ const [authed, setAuthed] = useState(false);
12
+ const [checking, setChecking] = useState(true);
13
+
14
+ useEffect(() => {
15
+ (async () => {
16
+ try {
17
+ const stored = getStoredPasskey();
18
+ const res = await fetch(API + '/auth', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ passkey: stored }),
22
+ });
23
+ if (res.ok) setAuthed(true);
24
+ } catch {}
25
+ setChecking(false);
26
+ })();
27
+ }, []);
28
+
29
+ if (checking) return null;
30
+
31
+ return html`<${AnimatePresence} mode="wait">
32
+ ${showSplash
33
+ ? html`<${SplashScreen} key="splash" onDone=${() => setShowSplash(false)} />`
34
+ : !authed
35
+ ? html`<${PasskeyScreen} key="passkey" onSuccess=${() => setAuthed(true)} />`
36
+ : html`<${motion.div} key="app"
37
+ style=${{ display: 'flex', height: '100vh', width: '100%' }}
38
+ initial=${{ opacity: 0 }}
39
+ animate=${{ opacity: 1 }}
40
+ transition=${{ duration: 0.4 }}>
41
+ <${App} />
42
+ <//>`}
43
+ <//>`;
44
+ }
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { motion, AnimatePresence } from "framer-motion";
3
+ import { LuTrash2, LuZap } from "react-icons/lu";
4
+ import { html } from "../lib.js";
5
+
6
+ export function Modal({ title, children, onClose, actions }) {
7
+ return html`<${AnimatePresence}><${motion.div}
8
+ className="modal-overlay"
9
+ onClick=${(e) => e.target === e.currentTarget && onClose()}
10
+ initial=${{ opacity: 0 }} animate=${{ opacity: 1 }} exit=${{ opacity: 0 }}
11
+ transition=${{ duration: 0.15 }}
12
+ >
13
+ <${motion.div} className="modal"
14
+ initial=${{ opacity: 0, scale: 0.95, y: 10 }}
15
+ animate=${{ opacity: 1, scale: 1, y: 0 }}
16
+ exit=${{ opacity: 0, scale: 0.95, y: 10 }}
17
+ transition=${{ duration: 0.15 }}>
18
+ <div className="modal-title">${title}</div>
19
+ ${children}
20
+ ${actions && html`<div className="modal-actions">${actions}</div>`}
21
+ <//>
22
+ <//><//>`;
23
+ }
24
+
25
+ export function ConfirmDialog({ message, onConfirm, onCancel, confirmLabel, danger }) {
26
+ return html`<${AnimatePresence}><${motion.div}
27
+ className="modal-overlay"
28
+ onClick=${onCancel}
29
+ initial=${{ opacity: 0 }} animate=${{ opacity: 1 }} exit=${{ opacity: 0 }}
30
+ transition=${{ duration: 0.15 }}
31
+ >
32
+ <${motion.div} className="modal" onClick=${e => e.stopPropagation()}
33
+ style=${{ minWidth: '380px', maxWidth: '440px' }}
34
+ initial=${{ opacity: 0, scale: 0.95, y: 10 }}
35
+ animate=${{ opacity: 1, scale: 1, y: 0 }}
36
+ exit=${{ opacity: 0, scale: 0.95, y: 10 }}
37
+ transition=${{ duration: 0.15 }}>
38
+ <div className="flex items-center gap-3" style=${{ marginBottom: '16px' }}>
39
+ <div style=${{ width: '40px', height: '40px', borderRadius: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
40
+ background: danger ? 'var(--error-glow)' : 'var(--warning-glow)' }}>
41
+ <${danger ? LuTrash2 : LuZap} size=${18} color=${danger ? 'var(--error)' : 'var(--warning)'} />
42
+ </div>
43
+ <div style=${{ fontSize: '15px', fontWeight: 600 }}>Confirm Action</div>
44
+ </div>
45
+ <div style=${{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: '1.6', marginBottom: '20px' }}>${message}</div>
46
+ <div className="modal-actions">
47
+ <button className="btn btn-ghost" onClick=${onCancel}>Cancel</button>
48
+ <button className=${`btn ${danger ? 'btn-danger' : 'btn-primary'}`} onClick=${onConfirm}>${confirmLabel || 'Confirm'}</button>
49
+ </div>
50
+ <//>
51
+ <//><//>`;
52
+ }
@@ -0,0 +1,68 @@
1
+ import React from "react";
2
+ import { html, Icons } from "../lib.js";
3
+ import { useApp } from "../context.js";
4
+
5
+ export function Sidebar() {
6
+ const { page, setPage, data } = useApp();
7
+ const dbOk = data.mongoConfigured;
8
+
9
+ const navItems = [
10
+ {
11
+ id: "dashboard",
12
+ label: "Dashboard",
13
+ icon: Icons.dashboard,
14
+ section: "Overview",
15
+ },
16
+ {
17
+ id: "database",
18
+ label: "Database",
19
+ icon: Icons.database,
20
+ section: "Setup",
21
+ },
22
+ {
23
+ id: "schemas",
24
+ label: "Schemas",
25
+ icon: Icons.schema,
26
+ locked: !dbOk,
27
+ lockMsg: "Requires database",
28
+ },
29
+ { id: "routes", label: "API Routes", icon: Icons.route },
30
+ { id: "api-tester", label: "API Tester", icon: Icons.apiTester },
31
+ {
32
+ id: "realtime",
33
+ label: "Real-time",
34
+ icon: Icons.realtime,
35
+ section: "Features",
36
+ },
37
+ { id: "serialport", label: "SerialPort", icon: Icons.serial },
38
+ { id: "env", label: "Environment", icon: Icons.env, section: "Settings" },
39
+ ];
40
+
41
+ let currentSection = null;
42
+ return html`<div className="sidebar">
43
+ <div className="sidebar-brand">
44
+ <pre>${`██╗ ██╗██████╗ ███╗ ██╗ ██████╗ ██████╗ ███████╗\n██║ ██║██╔══██╗████╗ ██║██╔═══██╗██╔══██╗██╔════╝\n███████║██████╔╝██╔██╗ ██║██║ ██║██║ ██║█████╗\n╚════██║██╔══██╗██║╚██╗██║██║ ██║██║ ██║██╔══╝\n ██║██████╔╝██║ ╚████║╚██████╔╝██████╔╝███████╗\n ╚═╝╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝`}</pre>
45
+ <span>Project Dashboard</span>
46
+ </div>
47
+ ${navItems.map((item) => {
48
+ const sectionHeader =
49
+ item.section && item.section !== currentSection
50
+ ? ((currentSection = item.section),
51
+ html`<div className="sidebar-section">${item.section}</div>`)
52
+ : null;
53
+ return html`<${React.Fragment} key=${item.id}>
54
+ ${sectionHeader}
55
+ <div
56
+ className=${`sidebar-item ${page === item.id ? "active" : ""} ${item.locked ? "locked" : ""}`}
57
+ onClick=${() => !item.locked && setPage(item.id)}
58
+ title=${item.locked ? item.lockMsg : item.label}
59
+ >
60
+ <span className="sidebar-icon">${item.icon}</span>
61
+ ${item.label}
62
+ ${item.locked &&
63
+ html`<span className="lock-icon">${Icons.lock}</span>`}
64
+ </div>
65
+ <//>`;
66
+ })}
67
+ </div>`;
68
+ }
@@ -0,0 +1,15 @@
1
+ import { html } from "../lib.js";
2
+ import { useApp } from "../context.js";
3
+
4
+ export function ToastContainer() {
5
+ const { toasts } = useApp();
6
+ if (toasts.length === 0) return null;
7
+ return html`<div className="toast-container">
8
+ ${toasts.map(
9
+ (t) =>
10
+ html`<div key=${t.id} className=${`toast ${t.type}`}>
11
+ ${t.message}
12
+ </div>`,
13
+ )}
14
+ </div>`;
15
+ }
@@ -0,0 +1,7 @@
1
+ import React, { createContext, useContext } from "react";
2
+
3
+ export const AppContext = createContext();
4
+
5
+ export function useApp() {
6
+ return useContext(AppContext);
7
+ }
@@ -0,0 +1,83 @@
1
+ import React from "react";
2
+ import htm from "htm";
3
+ import {
4
+ LuLayoutDashboard,
5
+ LuDatabase,
6
+ LuFileText,
7
+ LuRadio,
8
+ LuPlus,
9
+ LuTrash2,
10
+ LuCheck,
11
+ LuLock,
12
+ LuArrowRight,
13
+ LuPencil,
14
+ LuCable,
15
+ LuActivity,
16
+ LuZap,
17
+ LuShield,
18
+ LuBox,
19
+ LuFlaskConical,
20
+ LuSend,
21
+ LuSettings,
22
+ } from "react-icons/lu";
23
+
24
+ export const html = htm.bind(React.createElement);
25
+ export const API = "/_dev/api";
26
+
27
+ export function getStoredPasskey() {
28
+ return sessionStorage.getItem("_dev_passkey") || "";
29
+ }
30
+
31
+ export async function api(path, options = {}) {
32
+ const passkey = getStoredPasskey();
33
+ const headers = { "Content-Type": "application/json" };
34
+ if (passkey) headers["x-passkey"] = passkey;
35
+ const res = await fetch(API + path, {
36
+ headers,
37
+ ...options,
38
+ body: options.body ? JSON.stringify(options.body) : undefined,
39
+ });
40
+ const text = await res.text();
41
+ let data;
42
+ try {
43
+ data = JSON.parse(text);
44
+ } catch {
45
+ if (res.ok) return { message: "Done" };
46
+ throw new Error("Server restarting, please wait...");
47
+ }
48
+ if (!res.ok)
49
+ throw new Error(data.error || data.message || "Request failed");
50
+ return data;
51
+ }
52
+
53
+ export const Icons = {
54
+ dashboard: html`<${LuLayoutDashboard} size=${16} />`,
55
+ database: html`<${LuDatabase} size=${16} />`,
56
+ schema: html`<${LuFileText} size=${16} />`,
57
+ route: html`<${LuRadio} size=${16} />`,
58
+ realtime: html`<${LuActivity} size=${16} />`,
59
+ env: html`<${LuSettings} size=${16} />`,
60
+ plus: html`<${LuPlus} size=${16} />`,
61
+ trash: html`<${LuTrash2} size=${16} />`,
62
+ check: html`<${LuCheck} size=${16} />`,
63
+ lock: html`<${LuLock} size=${12} />`,
64
+ arrow: html`<${LuArrowRight} size=${14} />`,
65
+ edit: html`<${LuPencil} size=${16} />`,
66
+ serial: html`<${LuCable} size=${16} />`,
67
+ zap: html`<${LuZap} size=${16} />`,
68
+ shield: html`<${LuShield} size=${16} />`,
69
+ box: html`<${LuBox} size=${16} />`,
70
+ apiTester: html`<${LuFlaskConical} size=${16} />`,
71
+ send: html`<${LuSend} size=${16} />`,
72
+ };
73
+
74
+ export const PAGE_TITLES = {
75
+ dashboard: "Dashboard",
76
+ database: "Database",
77
+ schemas: "Schemas",
78
+ routes: "API Routes",
79
+ "api-tester": "API Tester",
80
+ realtime: "Real-time",
81
+ serialport: "SerialPort",
82
+ env: "Environment",
83
+ };
@@ -0,0 +1,5 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import { html } from "./lib.js";
3
+ import { Root } from "./Root.js";
4
+
5
+ createRoot(document.getElementById("root")).render(html`<${Root} />`);