@flowfuse/node-red-dashboard 1.31.1-e3609ab-202609141637.0 → 1.32.0
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/dist/apple-touch-icon.png +0 -0
- package/dist/assets/abnfDiagram-N423BO3Z-BOzAedUe.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-zEM8gtfO.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-eKuDT4u2.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-CRPvpwR2.js +10 -0
- package/dist/assets/chunk-2GRJ4B5K-BUP9p1Ar.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-DASHHxaC.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-C76llYJF.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-DnmzQEFn.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-DpGB6o5S.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-Bhzwn3VL.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-UNqcBwu_.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-C38IW_UU.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-COKxTTcO.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-BS0ojEzX.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-CxMqqBtQ.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-CxMqqBtQ.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-9jRlXuji.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-BDPznSFQ.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-lSqlEkCZ.js +62 -0
- package/dist/assets/cytoscape.esm-D_LviqZs.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-Dfoe_jp9.js +4 -0
- package/dist/assets/diagram-7IWD3JNH-Df2T__dV.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-DoGtC0h7.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-cH8DVJ6C.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-8JJAGHRF.js +24 -0
- package/dist/assets/diagram-UB23O5K3-BGCDqdpG.js +41 -0
- package/dist/assets/disconnected-BuxohaUu.png +0 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-r2bgA64b.js +1 -0
- package/dist/assets/echarts-BB8VCqSB.js +73 -0
- package/dist/assets/erDiagram-JOGREHBK-BExdsKTd.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-B8wwUqLz.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-SViiTCQN.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-CmktWysd.js +106 -0
- package/dist/assets/graph-C9eacEi8.js +1 -0
- package/dist/assets/index-BzBvlpBo.js +124 -0
- package/dist/assets/index-DHk7_39t.css +13 -0
- package/dist/assets/infoDiagram-6WML65LV-BwUFVmXD.js +2 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-Df6P2A6T.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-Cjk4fifu.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-BcoyAznM.js +89 -0
- package/dist/assets/katex-C5jXJg4s.js +257 -0
- package/dist/assets/layout-DEXfKzaS.js +1 -0
- package/dist/assets/logo-DIAzbBuw.png +0 -0
- package/dist/assets/map-Czzmt4hB.js +1 -0
- package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/dist/assets/mermaid-CX4mRs5N.js +314 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-D6njotiS.js +96 -0
- package/dist/assets/pegDiagram-VL7TDLO6-CaA_iXxT.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-aYh5694F.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CaWyb4Jw.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-Bd_vMLsE.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-CMxISoyd.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DLbFAyXe.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-CzdhMpvP.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-CnezfYZ6.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-5R9RLDEM.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-Ck4l-gcT.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-CeEnGZwJ.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DSebamlh.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-ZMWCENQJ.js +120 -0
- package/dist/assets/vennDiagram-T6HMQDX7-DVr1jz1I.js +34 -0
- package/dist/assets/vue-vendor-Bmy9n80X.js +49 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-DNp6yzs5.js +78 -0
- package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +2 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DC4NZzl1.js +7 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.svg +482 -0
- package/dist/index.html +24 -0
- package/dist/logo-512x512.png +0 -0
- package/dist/logo.svg +482 -0
- package/dist/maskable-icon-512x512.png +0 -0
- package/dist/pwa-192x192.png +0 -0
- package/dist/pwa-512x512.png +0 -0
- package/dist/pwa-64x64.png +0 -0
- package/dist/sw.js +2 -0
- package/nodes/config/ui_base.js +12 -7
- package/nodes/store/clients.js +71 -0
- package/nodes/store/data.js +3 -1
- package/nodes/store/state.js +3 -1
- package/nodes/utils/index.js +29 -1
- package/nodes/widgets/locales/en-US/ui_control.json +2 -1
- package/nodes/widgets/ui_control.html +1 -1
- package/nodes/widgets/ui_control.js +28 -0
- package/package.json +2 -2
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const { EventEmitter } = require('events')
|
|
2
|
+
|
|
3
|
+
const DEFAULT_GRACE_MS = 20000
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tracks client presence across reconnects (socket.id churns every reconnect; clientId is stable).
|
|
7
|
+
* A client may hold several live sockets (one per tab); events fire on its socket count crossing
|
|
8
|
+
* zero, not per socket. Emits { event, clientId, socketId? }:
|
|
9
|
+
* connected: count 0 -> 1, no grace pending (new client)
|
|
10
|
+
* reconnected: count 0 -> 1 during the grace window (returned before being declared gone)
|
|
11
|
+
* gone: count -> 0 and the grace window elapses
|
|
12
|
+
* Extra tabs (1 -> 2, 2 -> 1) emit nothing; the client is still present.
|
|
13
|
+
*/
|
|
14
|
+
function createClientStore ({ graceMs = DEFAULT_GRACE_MS, setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout } = {}) {
|
|
15
|
+
const clients = {} // clientId -> { sockets: Set<socketId>, graceTO }
|
|
16
|
+
const events = new EventEmitter()
|
|
17
|
+
events.setMaxListeners(0)
|
|
18
|
+
|
|
19
|
+
function connect (clientId, socketId) {
|
|
20
|
+
if (!clientId) { return }
|
|
21
|
+
let entry = clients[clientId]
|
|
22
|
+
const returning = !!(entry && entry.graceTO)
|
|
23
|
+
if (!entry) {
|
|
24
|
+
entry = clients[clientId] = { sockets: new Set(), graceTO: null }
|
|
25
|
+
}
|
|
26
|
+
if (entry.graceTO) {
|
|
27
|
+
clearTimeoutFn(entry.graceTO)
|
|
28
|
+
entry.graceTO = null
|
|
29
|
+
}
|
|
30
|
+
const wasEmpty = entry.sockets.size === 0
|
|
31
|
+
entry.sockets.add(socketId)
|
|
32
|
+
if (wasEmpty) {
|
|
33
|
+
events.emit('client', { event: returning ? 'reconnected' : 'connected', clientId, socketId })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function disconnect (clientId, socketId) {
|
|
38
|
+
if (!clientId) { return }
|
|
39
|
+
const entry = clients[clientId]
|
|
40
|
+
if (!entry) { return }
|
|
41
|
+
entry.sockets.delete(socketId)
|
|
42
|
+
if (entry.sockets.size === 0 && !entry.graceTO) {
|
|
43
|
+
entry.graceTO = setTimeoutFn(() => {
|
|
44
|
+
delete clients[clientId]
|
|
45
|
+
events.emit('client', { event: 'gone', clientId })
|
|
46
|
+
}, graceMs)
|
|
47
|
+
if (typeof entry.graceTO?.unref === 'function') { entry.graceTO.unref() }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function dropSocket (clientId, socketId) {
|
|
52
|
+
if (!clientId) { return }
|
|
53
|
+
const entry = clients[clientId]
|
|
54
|
+
if (!entry) { return }
|
|
55
|
+
entry.sockets.delete(socketId)
|
|
56
|
+
if (entry.sockets.size === 0) {
|
|
57
|
+
if (entry.graceTO) { clearTimeoutFn(entry.graceTO) }
|
|
58
|
+
delete clients[clientId]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function list () {
|
|
63
|
+
return Object.entries(clients)
|
|
64
|
+
.filter(([, entry]) => entry.sockets.size > 0)
|
|
65
|
+
.map(([clientId, entry]) => ({ clientId, socketId: entry.sockets.values().next().value }))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { connect, disconnect, dropSocket, list, events }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { createClientStore }
|
package/nodes/store/data.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { isClientScoped } = require('../utils/index.js')
|
|
2
|
+
|
|
1
3
|
const data = {}
|
|
2
4
|
|
|
3
5
|
const config = {
|
|
@@ -19,7 +21,7 @@ function canSaveInStore (base, node, msg) {
|
|
|
19
21
|
|
|
20
22
|
if (constrained.includes(node.type)) {
|
|
21
23
|
// core check
|
|
22
|
-
if (msg
|
|
24
|
+
if (isClientScoped(msg)) {
|
|
23
25
|
// we are in a node type that allows for definition of specific clients,
|
|
24
26
|
// and a client has been defined
|
|
25
27
|
checks.push(false)
|
package/nodes/store/state.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Store to manage any dynamic properties set
|
|
2
2
|
|
|
3
|
+
const { isClientScoped } = require('../utils/index.js')
|
|
4
|
+
|
|
3
5
|
const state = {}
|
|
4
6
|
const config = {
|
|
5
7
|
RED: null
|
|
@@ -18,7 +20,7 @@ function canSaveInStore (base, node, msg) {
|
|
|
18
20
|
// core check
|
|
19
21
|
if (base.acceptsClientConfig.includes(node.type)) {
|
|
20
22
|
// we are in a node type that allows for definition of specific clients,
|
|
21
|
-
if (msg
|
|
23
|
+
if (isClientScoped(msg)) {
|
|
22
24
|
// and a client has been defined
|
|
23
25
|
checks.push(false)
|
|
24
26
|
} else {
|
package/nodes/utils/index.js
CHANGED
|
@@ -40,6 +40,30 @@ async function getTopic (RED, config, wNode, msg) {
|
|
|
40
40
|
return topic
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function normalizeClientId (raw) {
|
|
44
|
+
if (Array.isArray(raw)) { raw = raw[0] }
|
|
45
|
+
return (raw === undefined || raw === null || raw === '') ? undefined : String(raw)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const CLIENT_TARGETS = {
|
|
49
|
+
socketId: (conn) => conn.id,
|
|
50
|
+
clientId: (conn) => conn._clientId
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clientTargets (msg) {
|
|
54
|
+
return Object.entries(CLIENT_TARGETS)
|
|
55
|
+
.map(([field, connValue]) => ({ value: normalizeClientId(msg?._client?.[field]), connValue }))
|
|
56
|
+
.filter((target) => target.value !== undefined)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isClientScoped (msg) {
|
|
60
|
+
return clientTargets(msg).length > 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function matchesClient (conn, msg) {
|
|
64
|
+
return clientTargets(msg).every((target) => target.value === target.connValue(conn))
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
/**
|
|
44
68
|
* Adds socket/client data to a msg payload, if enabled
|
|
45
69
|
*
|
|
@@ -60,7 +84,8 @@ function addConnectionCredentials (RED, msg, conn, config) {
|
|
|
60
84
|
...item._client,
|
|
61
85
|
...{
|
|
62
86
|
socketId: conn.id,
|
|
63
|
-
socketIp: conn.handshake?.address
|
|
87
|
+
socketIp: conn.handshake?.address,
|
|
88
|
+
clientId: normalizeClientId(conn.handshake?.query?.clientId)
|
|
64
89
|
}
|
|
65
90
|
}
|
|
66
91
|
return item
|
|
@@ -127,5 +152,8 @@ module.exports = {
|
|
|
127
152
|
appendTopic,
|
|
128
153
|
getTopic,
|
|
129
154
|
addConnectionCredentials,
|
|
155
|
+
normalizeClientId,
|
|
156
|
+
isClientScoped,
|
|
157
|
+
matchesClient,
|
|
130
158
|
getThirdPartyWidgets
|
|
131
159
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
oneditprepare: function () {
|
|
25
25
|
const node = this
|
|
26
26
|
const sel = $('#node-input-events')
|
|
27
|
-
for (const name of ['all', 'change', 'connect']) {
|
|
27
|
+
for (const name of ['all', 'change', 'connect', 'clients']) {
|
|
28
28
|
const text = c_('events.' + name)
|
|
29
29
|
$('<option/>').val(name).text(text).appendTo(sel)
|
|
30
30
|
}
|
|
@@ -411,7 +411,35 @@ module.exports = function (RED) {
|
|
|
411
411
|
node.error('No UI configured')
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
let onClientEvent = null
|
|
415
|
+
let clientEventStore = null
|
|
416
|
+
if (ui && (config.events === 'all' || config.events === 'clients')) {
|
|
417
|
+
clientEventStore = ui.uiShared?.clientStore
|
|
418
|
+
if (clientEventStore) {
|
|
419
|
+
onClientEvent = (e) => {
|
|
420
|
+
const wNode = RED.nodes.getNode(node.id)
|
|
421
|
+
if (wNode && typeof wNode.send === 'function') {
|
|
422
|
+
const client = { clientId: e.clientId }
|
|
423
|
+
if (e.socketId) { client.socketId = e.socketId }
|
|
424
|
+
let msg = { payload: 'client-' + e.event, _client: client }
|
|
425
|
+
const conn = e.socketId ? ui.uiShared?.connections?.[e.socketId] : null
|
|
426
|
+
if (conn) { msg = addConnectionCredentials(RED, msg, conn, ui) }
|
|
427
|
+
wNode.send(msg)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
clientEventStore.events.on('client', onClientEvent)
|
|
431
|
+
setImmediate(() => {
|
|
432
|
+
for (const c of clientEventStore.list()) {
|
|
433
|
+
onClientEvent({ event: 'connected', clientId: c.clientId, socketId: c.socketId })
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
414
439
|
node.on('close', function (removed, done) {
|
|
440
|
+
if (onClientEvent && clientEventStore) {
|
|
441
|
+
clientEventStore.events.off('client', onClientEvent)
|
|
442
|
+
}
|
|
415
443
|
if (removed) {
|
|
416
444
|
// handle node being removed
|
|
417
445
|
ui?.deregister(null, null, node)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowfuse/node-red-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.0",
|
|
4
4
|
"description": "FlowFuse Dashboard - A collection of Node-RED nodes that provide functionality to build your own UI applications (inc. forms, buttons, charts).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red"
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"marked-highlight": "^2.2.3",
|
|
99
99
|
"medium-zoom": "^1.1.0",
|
|
100
100
|
"mermaid": "^11.12.2",
|
|
101
|
-
"mocha": "^
|
|
101
|
+
"mocha": "^12.0.1",
|
|
102
102
|
"node-red": "^4.1.8",
|
|
103
103
|
"node-red-node-test-helper": "^0.3.5",
|
|
104
104
|
"npm-run-all": "^4.1.5",
|