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.
- package/README.md +144 -109
- package/add-api.js +288 -156
- package/add-crud.js +226 -0
- package/add-login.js +152 -319
- package/add-mongo-delete.js +67 -138
- package/add-mongo-insert.js +87 -204
- package/add-mongo-read.js +100 -216
- package/add-mongo-schema.js +431 -199
- package/add-mongo-update.js +89 -202
- package/add-mongo.js +125 -218
- package/add-serialport.js +53 -98
- package/add-socket.js +43 -66
- package/add-websocket.js +68 -140
- package/index.js +341 -156
- package/lib/env.js +18 -0
- package/lib/indexFile.js +134 -0
- package/lib/names.js +21 -0
- package/lib/project.js +122 -0
- package/lib/routeFile.js +56 -0
- package/lib/ui.js +161 -0
- package/package.json +9 -16
- package/skeleton/.4bnode/assets/fonts/NotoSans-Bold.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Light.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Medium.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-Regular.woff2 +0 -0
- package/skeleton/.4bnode/assets/fonts/NotoSans-SemiBold.woff2 +0 -0
- package/skeleton/.4bnode/assets/images/mongo.png +0 -0
- package/skeleton/.4bnode/assets/images/mysql.png +0 -0
- package/skeleton/.4bnode/assets/images/postgresql.png +0 -0
- package/skeleton/.4bnode/assets/images/sqlite.png +0 -0
- package/skeleton/.4bnode/assets/js/App.js +115 -0
- package/skeleton/.4bnode/assets/js/Root.js +44 -0
- package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
- package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
- package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
- package/skeleton/.4bnode/assets/js/context.js +7 -0
- package/skeleton/.4bnode/assets/js/lib.js +83 -0
- package/skeleton/.4bnode/assets/js/main.js +5 -0
- package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
- package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
- package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
- package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
- package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
- package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
- package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
- package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
- package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
- package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
- package/skeleton/.4bnode/dev-api.js +2930 -0
- package/skeleton/.4bnode/ui.html +1556 -0
- package/skeleton/index.js +70 -17
- package/skeleton/package.json +9 -5
- package/skeleton/public/index.html +288 -86
- package/README_4bnode.md +0 -197
- package/src/auth/passport.js +0 -21
- package/src/db.js +0 -13
- package/test-server/.env.development +0 -1
- package/test-server/.env.production +0 -1
- package/test-server/index.js +0 -82
- package/test-server/package-lock.json +0 -1267
- package/test-server/package.json +0 -20
- package/test-server/public/index.html +0 -90
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
3
|
+
import { LuActivity, LuCheck, LuRadio, LuZap } from "react-icons/lu";
|
|
4
|
+
import { html, api } from "../lib.js";
|
|
5
|
+
import { useApp } from "../context.js";
|
|
6
|
+
|
|
7
|
+
export function RealtimePage() {
|
|
8
|
+
const { toast, refresh, status, showConfirm } = useApp();
|
|
9
|
+
const [selected, setSelected] = useState(status?.socketConfigured ? 'socketio' : status?.websocketConfigured ? 'websocket' : 'socketio');
|
|
10
|
+
|
|
11
|
+
const items = [
|
|
12
|
+
{ id: 'socketio', label: 'Socket.IO', desc: 'Auto-reconnect, rooms, namespaces', configured: status?.socketConfigured },
|
|
13
|
+
{ id: 'websocket', label: 'WebSocket', desc: 'Native ws library, lightweight', configured: status?.websocketConfigured },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
return html`<div>
|
|
17
|
+
<div className="flex items-center justify-between mb-4">
|
|
18
|
+
<div>
|
|
19
|
+
<div className="card-title">Real-time</div>
|
|
20
|
+
<div className="card-subtitle">Configure real-time communication</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div className="routes-layout">
|
|
25
|
+
<div className="card" style=${{ padding: 0, overflow: 'hidden' }}>
|
|
26
|
+
<div style=${{ padding: '14px 16px 10px', borderBottom: '1px solid var(--border)' }}>
|
|
27
|
+
<span style=${{ fontWeight: 600, fontSize: '12px', textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)' }}>Protocols</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="routes-list">
|
|
30
|
+
${items.map(item => html`<div key=${item.id}
|
|
31
|
+
className=${`route-item ${selected === item.id ? 'active' : ''}`}
|
|
32
|
+
onClick=${() => setSelected(item.id)}>
|
|
33
|
+
<div style=${{ display: 'flex', flexDirection: 'column', gap: '4px', flex: 1, minWidth: 0 }}>
|
|
34
|
+
<span className="ri-name">${item.label}</span>
|
|
35
|
+
<span className="ri-file">${item.desc}</span>
|
|
36
|
+
</div>
|
|
37
|
+
${item.configured && html`<span className="badge badge-success" style=${{ fontSize: '10px' }}>Active</span>`}
|
|
38
|
+
</div>`)}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="routes-detail">
|
|
43
|
+
<${AnimatePresence} mode="wait">
|
|
44
|
+
<${motion.div} key=${selected}
|
|
45
|
+
initial=${{ opacity: 0, y: 6 }} animate=${{ opacity: 1, y: 0 }} exit=${{ opacity: 0, y: -6 }}
|
|
46
|
+
transition=${{ duration: 0.12 }}>
|
|
47
|
+
${selected === 'socketio' ? html`<${SocketIOSetup} />` : html`<${WebSocketSetup} />`}
|
|
48
|
+
<//>
|
|
49
|
+
<//>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function SocketIOSetup() {
|
|
56
|
+
const { toast, refresh, status, showConfirm } = useApp();
|
|
57
|
+
const [cors, setCors] = useState("*");
|
|
58
|
+
const [loading, setLoading] = useState(false);
|
|
59
|
+
|
|
60
|
+
if (status?.socketConfigured) {
|
|
61
|
+
return html`<div>
|
|
62
|
+
<div className="card mb-4" style=${{ borderColor: 'rgba(34,197,94,0.3)' }}>
|
|
63
|
+
<div className="flex items-center gap-3">
|
|
64
|
+
<${LuActivity} size=${20} color="var(--success)" />
|
|
65
|
+
<div style=${{ flex: 1 }}>
|
|
66
|
+
<div style=${{ fontWeight: 600 }}>Socket.IO Active</div>
|
|
67
|
+
<div className="text-sm text-muted">Real-time events are configured in index.js</div>
|
|
68
|
+
</div>
|
|
69
|
+
<span className="badge badge-success">Configured</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="card">
|
|
73
|
+
<div style=${{ fontSize: '11px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)', marginBottom: '8px' }}>Features</div>
|
|
74
|
+
${['Auto-reconnect on disconnect', 'Room & namespace support', 'Binary data streaming', 'Middleware pipeline'].map(f =>
|
|
75
|
+
html`<div key=${f} style=${{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 0', fontSize: '13px', color: 'var(--text-secondary)' }}>
|
|
76
|
+
<${LuCheck} size=${14} color="var(--success)" /> ${f}
|
|
77
|
+
</div>`
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
</div>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function handleSetup() {
|
|
84
|
+
if (status?.websocketConfigured) {
|
|
85
|
+
const ok = await showConfirm('WebSocket is currently configured. Enabling Socket.IO will replace your WebSocket setup. Do you want to continue?', { confirmLabel: 'Replace with Socket.IO' });
|
|
86
|
+
if (!ok) return;
|
|
87
|
+
}
|
|
88
|
+
setLoading(true);
|
|
89
|
+
try {
|
|
90
|
+
const result = await api('/add-socket', { method: 'POST', body: { corsOrigins: cors } });
|
|
91
|
+
toast(result.message, 'success');
|
|
92
|
+
refresh().catch(() => {});
|
|
93
|
+
} catch (err) { toast(err.message, 'error'); }
|
|
94
|
+
setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return html`<div className="card">
|
|
98
|
+
<div className="card-title mb-2">Socket.IO Setup</div>
|
|
99
|
+
<div className="card-subtitle mb-4">Add real-time bidirectional communication with auto-reconnect, rooms, and namespaces</div>
|
|
100
|
+
${status?.websocketConfigured && html`<div style=${{ padding: '10px 14px', borderRadius: '8px', background: 'var(--warning-glow)', border: '1px solid rgba(245,158,11,0.2)', marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
101
|
+
<${LuZap} size=${14} color="var(--warning)" />
|
|
102
|
+
<span style=${{ fontSize: '12px', color: 'var(--warning)' }}>WebSocket is currently active. Setting up Socket.IO will replace it.</span>
|
|
103
|
+
</div>`}
|
|
104
|
+
<div className="form-group">
|
|
105
|
+
<label className="form-label">CORS Origins</label>
|
|
106
|
+
<input className="form-input" value=${cors} onInput=${e => setCors(e.target.value)} placeholder="* or comma-separated origins" />
|
|
107
|
+
</div>
|
|
108
|
+
<button className="btn btn-primary" onClick=${handleSetup} disabled=${loading}>
|
|
109
|
+
${loading ? html`<span className="spinner" />` : null}
|
|
110
|
+
${loading ? 'Setting up...' : 'Setup Socket.IO'}
|
|
111
|
+
</button>
|
|
112
|
+
</div>`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function WebSocketSetup() {
|
|
116
|
+
const { toast, refresh, status, showConfirm } = useApp();
|
|
117
|
+
const [samePort, setSamePort] = useState(true);
|
|
118
|
+
const [wsPort, setWsPort] = useState("8080");
|
|
119
|
+
const [loading, setLoading] = useState(false);
|
|
120
|
+
|
|
121
|
+
if (status?.websocketConfigured) {
|
|
122
|
+
return html`<div>
|
|
123
|
+
<div className="card mb-4" style=${{ borderColor: 'rgba(34,197,94,0.3)' }}>
|
|
124
|
+
<div className="flex items-center gap-3">
|
|
125
|
+
<${LuRadio} size=${20} color="var(--success)" />
|
|
126
|
+
<div style=${{ flex: 1 }}>
|
|
127
|
+
<div style=${{ fontWeight: 600 }}>WebSocket Active</div>
|
|
128
|
+
<div className="text-sm text-muted">Native WebSocket is configured in index.js</div>
|
|
129
|
+
</div>
|
|
130
|
+
<span className="badge badge-success">Configured</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="card">
|
|
134
|
+
<div style=${{ fontSize: '11px', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)', marginBottom: '8px' }}>Features</div>
|
|
135
|
+
${['Lightweight native protocol', 'Low latency messaging', 'Binary frame support', 'Custom message handling'].map(f =>
|
|
136
|
+
html`<div key=${f} style=${{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 0', fontSize: '13px', color: 'var(--text-secondary)' }}>
|
|
137
|
+
<${LuCheck} size=${14} color="var(--success)" /> ${f}
|
|
138
|
+
</div>`
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
</div>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function handleSetup() {
|
|
145
|
+
if (status?.socketConfigured) {
|
|
146
|
+
const ok = await showConfirm('Socket.IO is currently configured. Enabling WebSocket will replace your Socket.IO setup. Do you want to continue?', { confirmLabel: 'Replace with WebSocket' });
|
|
147
|
+
if (!ok) return;
|
|
148
|
+
}
|
|
149
|
+
setLoading(true);
|
|
150
|
+
try {
|
|
151
|
+
const result = await api('/add-websocket', { method: 'POST', body: { samePort, wsPort: samePort ? null : parseInt(wsPort) } });
|
|
152
|
+
toast(result.message, 'success');
|
|
153
|
+
refresh().catch(() => {});
|
|
154
|
+
} catch (err) { toast(err.message, 'error'); }
|
|
155
|
+
setLoading(false);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return html`<div className="card">
|
|
159
|
+
<div className="card-title mb-2">Native WebSocket Setup</div>
|
|
160
|
+
<div className="card-subtitle mb-4">Add lightweight WebSocket support using the ws library for low-level control</div>
|
|
161
|
+
${status?.socketConfigured && html`<div style=${{ padding: '10px 14px', borderRadius: '8px', background: 'var(--warning-glow)', border: '1px solid rgba(245,158,11,0.2)', marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
162
|
+
<${LuZap} size=${14} color="var(--warning)" />
|
|
163
|
+
<span style=${{ fontSize: '12px', color: 'var(--warning)' }}>Socket.IO is currently active. Setting up WebSocket will replace it.</span>
|
|
164
|
+
</div>`}
|
|
165
|
+
<div className="form-group">
|
|
166
|
+
<label className="form-label">Server Port</label>
|
|
167
|
+
<div style=${{ display: 'flex', gap: '8px', marginTop: '4px' }}>
|
|
168
|
+
<label style=${{ display: 'flex', alignItems: 'center', gap: '6px', cursor: 'pointer', fontSize: '13px', color: 'var(--text-secondary)' }}>
|
|
169
|
+
<input type="radio" name="wsPort" checked=${samePort} onChange=${() => setSamePort(true)} /> Same as HTTP server
|
|
170
|
+
</label>
|
|
171
|
+
<label style=${{ display: 'flex', alignItems: 'center', gap: '6px', cursor: 'pointer', fontSize: '13px', color: 'var(--text-secondary)' }}>
|
|
172
|
+
<input type="radio" name="wsPort" checked=${!samePort} onChange=${() => setSamePort(false)} /> Separate port
|
|
173
|
+
</label>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
${!samePort && html`<div className="form-group">
|
|
177
|
+
<label className="form-label">WebSocket Port</label>
|
|
178
|
+
<input className="form-input" type="number" value=${wsPort} onInput=${e => setWsPort(e.target.value)} placeholder="8080" style=${{ maxWidth: '160px' }} />
|
|
179
|
+
</div>`}
|
|
180
|
+
<button className="btn btn-primary" onClick=${handleSetup} disabled=${loading}>
|
|
181
|
+
${loading ? html`<span className="spinner" />` : null}
|
|
182
|
+
${loading ? 'Setting up...' : 'Setup WebSocket'}
|
|
183
|
+
</button>
|
|
184
|
+
</div>`;
|
|
185
|
+
}
|
|
186
|
+
|