@aaqu/fromcubes-portal-react 0.1.0-alpha.3 → 0.1.0-alpha.5
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/nodes/portal-react.html +40 -2
- package/nodes/portal-react.js +36 -7
- package/package.json +2 -3
package/nodes/portal-react.html
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
var libContent = [
|
|
46
46
|
"declare var React: any;",
|
|
47
47
|
"declare var ReactDOM: any;",
|
|
48
|
-
"declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void };",
|
|
48
|
+
"declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void; user: { userId?: string; userName?: string; username?: string; email?: string; role?: string; groups?: any[] } | null };",
|
|
49
49
|
].join("\n");
|
|
50
50
|
console.log(PREFIX, "addExtraLib globals.d.ts");
|
|
51
51
|
jsDef.addExtraLib(libContent, "file:///globals.d.ts");
|
|
@@ -660,7 +660,7 @@
|
|
|
660
660
|
<div class="form-row" style="margin-bottom:0;">
|
|
661
661
|
<div style="font-size:11px;opacity:.6;margin-bottom:4px;">
|
|
662
662
|
<code>useNodeRed()</code> →
|
|
663
|
-
<code>{ data, send }</code> | Components from
|
|
663
|
+
<code>{ data, send, user }</code> | Components from
|
|
664
664
|
<code>fc-portal-component</code> nodes auto-imported |
|
|
665
665
|
Must export <code><App /></code> |
|
|
666
666
|
<strong>Transpiled server-side at deploy</strong>
|
|
@@ -711,6 +711,31 @@
|
|
|
711
711
|
</div>
|
|
712
712
|
</div>
|
|
713
713
|
</div>
|
|
714
|
+
<!-- ── Tab: Auth ── -->
|
|
715
|
+
<div id="fc-tab-auth" class="fc-tab-pane" style="display:none;">
|
|
716
|
+
<div class="form-row">
|
|
717
|
+
<label style="width:auto;">
|
|
718
|
+
<input
|
|
719
|
+
type="checkbox"
|
|
720
|
+
id="node-input-portalAuth"
|
|
721
|
+
style="width:auto;margin:0 8px 0 0;vertical-align:middle;"
|
|
722
|
+
/>
|
|
723
|
+
Enable Portal Auth headers
|
|
724
|
+
</label>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="form-row" style="padding-left:4px;">
|
|
727
|
+
<div style="font-size:11px;opacity:.6;line-height:1.5;">
|
|
728
|
+
Read <code>X-Portal-*</code> headers set by Nginx proxy and expose user data
|
|
729
|
+
via <code>useNodeRed()</code>.<br><br>
|
|
730
|
+
When enabled:<br>
|
|
731
|
+
• <code>useNodeRed()</code> returns <code>{ data, send, user }</code> where
|
|
732
|
+
<code>user</code> contains <code>userId</code>, <code>userName</code>,
|
|
733
|
+
<code>username</code>, <code>email</code>, <code>role</code>, <code>groups</code><br>
|
|
734
|
+
• Messages from WebSocket include <code>msg._client</code> with user data<br><br>
|
|
735
|
+
Requires <code>@aaqu/node-red-dashboard-2-portal-auth</code> Nginx setup.
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
714
739
|
</div>
|
|
715
740
|
|
|
716
741
|
<input type="hidden" id="node-input-componentCode" />
|
|
@@ -758,6 +783,7 @@
|
|
|
758
783
|
pageTitle: { value: "Portal" },
|
|
759
784
|
componentCode: { value: STARTER },
|
|
760
785
|
customHead: { value: "" },
|
|
786
|
+
portalAuth: { value: false },
|
|
761
787
|
},
|
|
762
788
|
inputs: 1,
|
|
763
789
|
outputs: 1,
|
|
@@ -798,6 +824,7 @@
|
|
|
798
824
|
fcTabs.addTab({ id: "fc-tab-jsx", label: "JSX" });
|
|
799
825
|
fcTabs.addTab({ id: "fc-tab-props", label: "Properties" });
|
|
800
826
|
fcTabs.addTab({ id: "fc-tab-head", label: "Head HTML" });
|
|
827
|
+
fcTabs.addTab({ id: "fc-tab-auth", label: "Auth" });
|
|
801
828
|
fcTabs.activateTab("fc-tab-jsx");
|
|
802
829
|
|
|
803
830
|
// URL hint
|
|
@@ -1048,6 +1075,17 @@ const { data, send } = useNodeRed();
|
|
|
1048
1075
|
by their component name.
|
|
1049
1076
|
</p>
|
|
1050
1077
|
|
|
1078
|
+
<h3>Portal Auth</h3>
|
|
1079
|
+
<p>
|
|
1080
|
+
When enabled in the Auth tab, reads <code>X-Portal-*</code> headers set by
|
|
1081
|
+
an Nginx proxy (via <code>@aaqu/node-red-dashboard-2-portal-auth</code>) and
|
|
1082
|
+
exposes user data:
|
|
1083
|
+
</p>
|
|
1084
|
+
<ul>
|
|
1085
|
+
<li><code>useNodeRed()</code> returns <code>{ data, send, user }</code></li>
|
|
1086
|
+
<li>Messages from WebSocket include <code>msg._client</code> with user info</li>
|
|
1087
|
+
</ul>
|
|
1088
|
+
|
|
1051
1089
|
<h3>Custom Head HTML</h3>
|
|
1052
1090
|
<p>
|
|
1053
1091
|
Inject CDN links, fonts, or extra stylesheets into
|
package/nodes/portal-react.js
CHANGED
|
@@ -134,6 +134,23 @@ module.exports = function (RED) {
|
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
function extractPortalUser(headers) {
|
|
138
|
+
const user = {};
|
|
139
|
+
if (headers["x-portal-user-id"]) user.userId = headers["x-portal-user-id"];
|
|
140
|
+
if (headers["x-portal-user-name"]) user.userName = headers["x-portal-user-name"];
|
|
141
|
+
if (headers["x-portal-user-username"]) user.username = headers["x-portal-user-username"];
|
|
142
|
+
if (headers["x-portal-user-email"]) user.email = headers["x-portal-user-email"];
|
|
143
|
+
if (headers["x-portal-user-role"]) user.role = headers["x-portal-user-role"];
|
|
144
|
+
if (headers["x-portal-user-groups"]) {
|
|
145
|
+
try {
|
|
146
|
+
user.groups = JSON.parse(headers["x-portal-user-groups"]);
|
|
147
|
+
} catch (_) {
|
|
148
|
+
user.groups = headers["x-portal-user-groups"];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Object.keys(user).length > 0 ? user : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
137
154
|
function removeRoute(router, path) {
|
|
138
155
|
if (!router || !router.stack) return;
|
|
139
156
|
router.stack = router.stack.filter(
|
|
@@ -196,6 +213,7 @@ module.exports = function (RED) {
|
|
|
196
213
|
const componentCode = config.componentCode || "";
|
|
197
214
|
const pageTitle = config.pageTitle || "Portal";
|
|
198
215
|
const customHead = config.customHead || "";
|
|
216
|
+
const portalAuth = config.portalAuth === true;
|
|
199
217
|
|
|
200
218
|
// State
|
|
201
219
|
const clients = new Set();
|
|
@@ -238,7 +256,8 @@ module.exports = function (RED) {
|
|
|
238
256
|
const send = React.useCallback((payload, topic) => {
|
|
239
257
|
window.__NR.send(payload, topic);
|
|
240
258
|
}, []);
|
|
241
|
-
|
|
259
|
+
const user = window.__NR._user || null;
|
|
260
|
+
return { data, send, user };
|
|
242
261
|
}`,
|
|
243
262
|
"",
|
|
244
263
|
"// ── Library components ──",
|
|
@@ -278,6 +297,7 @@ module.exports = function (RED) {
|
|
|
278
297
|
pageTitle,
|
|
279
298
|
wsPath,
|
|
280
299
|
customHead,
|
|
300
|
+
portalAuth,
|
|
281
301
|
};
|
|
282
302
|
}
|
|
283
303
|
|
|
@@ -305,6 +325,7 @@ module.exports = function (RED) {
|
|
|
305
325
|
return;
|
|
306
326
|
}
|
|
307
327
|
const cssHash = await state.cssHashReady;
|
|
328
|
+
const user = state.portalAuth ? extractPortalUser(_req.headers) : null;
|
|
308
329
|
res
|
|
309
330
|
.type("text/html")
|
|
310
331
|
.send(
|
|
@@ -314,6 +335,7 @@ module.exports = function (RED) {
|
|
|
314
335
|
state.wsPath,
|
|
315
336
|
state.customHead,
|
|
316
337
|
cssHash,
|
|
338
|
+
user,
|
|
317
339
|
),
|
|
318
340
|
);
|
|
319
341
|
});
|
|
@@ -351,11 +373,14 @@ module.exports = function (RED) {
|
|
|
351
373
|
RED.server.on("upgrade", onUpgrade);
|
|
352
374
|
upgradeHandlers[nodeId] = onUpgrade;
|
|
353
375
|
|
|
354
|
-
wsServer.on("connection", (ws) => {
|
|
376
|
+
wsServer.on("connection", (ws, request) => {
|
|
355
377
|
if (isClosing) {
|
|
356
378
|
ws.close();
|
|
357
379
|
return;
|
|
358
380
|
}
|
|
381
|
+
if (portalAuth) {
|
|
382
|
+
ws._portalUser = extractPortalUser(request.headers);
|
|
383
|
+
}
|
|
359
384
|
clients.add(ws);
|
|
360
385
|
updateStatus();
|
|
361
386
|
|
|
@@ -368,10 +393,14 @@ module.exports = function (RED) {
|
|
|
368
393
|
try {
|
|
369
394
|
const msg = JSON.parse(raw.toString());
|
|
370
395
|
if (msg.type === "output") {
|
|
371
|
-
|
|
396
|
+
const out = {
|
|
372
397
|
payload: msg.payload,
|
|
373
398
|
topic: msg.topic || "",
|
|
374
|
-
}
|
|
399
|
+
};
|
|
400
|
+
if (portalAuth && ws._portalUser) {
|
|
401
|
+
out._client = ws._portalUser;
|
|
402
|
+
}
|
|
403
|
+
node.send(out);
|
|
375
404
|
}
|
|
376
405
|
} catch (e) {
|
|
377
406
|
node.warn("Bad WS message: " + e.message);
|
|
@@ -534,7 +563,7 @@ module.exports = function (RED) {
|
|
|
534
563
|
|
|
535
564
|
// ── Page builders ─────────────────────────────────────────────
|
|
536
565
|
|
|
537
|
-
function buildPage(title, transpiledJs, wsPath, customHead, cssHash) {
|
|
566
|
+
function buildPage(title, transpiledJs, wsPath, customHead, cssHash, user) {
|
|
538
567
|
return `<!DOCTYPE html>
|
|
539
568
|
<html lang="en">
|
|
540
569
|
<head>
|
|
@@ -561,14 +590,14 @@ body{font-family:system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#e0
|
|
|
561
590
|
<div id="__cs" class="err">disconnected</div>
|
|
562
591
|
<script>
|
|
563
592
|
window.__NR={
|
|
564
|
-
_ws:null,_listeners:new Set(),_lastData:null,_retries:0,_wasConnected:false,
|
|
593
|
+
_ws:null,_listeners:new Set(),_lastData:null,_retries:0,_wasConnected:false,_user:${user ? escScript(JSON.stringify(user)) : 'null'},
|
|
565
594
|
connect(){
|
|
566
595
|
const p=location.protocol==='https:'?'wss:':'ws:';
|
|
567
596
|
const ws=new WebSocket(p+'//'+location.host+'${wsPath}');
|
|
568
597
|
this._ws=ws;
|
|
569
598
|
const s=document.getElementById('__cs');
|
|
570
599
|
ws.onopen=()=>{
|
|
571
|
-
if(this._wasConnected){
|
|
600
|
+
if(this._wasConnected){s.textContent='connected';s.className='ok';this._retries=0;return;}
|
|
572
601
|
s.textContent='connected';s.className='ok';this._retries=0;this._wasConnected=true;
|
|
573
602
|
};
|
|
574
603
|
ws.onmessage=(e)=>{
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaqu/fromcubes-portal-react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Fromcubes Portal - React for Node-RED",
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
|
+
"description": "Fromcubes Portal - React for Node-RED with Tailwind CSS and auto complete",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
7
|
-
"react",
|
|
8
7
|
"dashboard",
|
|
9
8
|
"portal",
|
|
10
9
|
"jsx",
|