@flowfuse/node-red-dashboard 1.32.0 → 1.32.1-15daa3c-202609281436.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.
Files changed (83) hide show
  1. package/nodes/config/ui_base.js +15 -0
  2. package/nodes/store/reactive.js +154 -0
  3. package/nodes/widgets/ui_gauge.js +1 -0
  4. package/package.json +1 -1
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/abnfDiagram-N423BO3Z-BOzAedUe.js +0 -1
  7. package/dist/assets/architectureDiagram-T3A2C74G-zEM8gtfO.js +0 -36
  8. package/dist/assets/blockDiagram-VBNYF7ZC-eKuDT4u2.js +0 -132
  9. package/dist/assets/c4Diagram-5PPSVZJV-CRPvpwR2.js +0 -10
  10. package/dist/assets/chunk-2GRJ4B5K-BUP9p1Ar.js +0 -1
  11. package/dist/assets/chunk-2Q5K7J3B-DASHHxaC.js +0 -1
  12. package/dist/assets/chunk-5RXB4S5H-C76llYJF.js +0 -231
  13. package/dist/assets/chunk-5VM5RSS4-DnmzQEFn.js +0 -15
  14. package/dist/assets/chunk-6Q2QTUOP-DpGB6o5S.js +0 -88
  15. package/dist/assets/chunk-GF5L2VYU-Bhzwn3VL.js +0 -206
  16. package/dist/assets/chunk-JWPE2WC7-UNqcBwu_.js +0 -1
  17. package/dist/assets/chunk-KBJHAD2P-C38IW_UU.js +0 -1
  18. package/dist/assets/chunk-RYQCIY6F-COKxTTcO.js +0 -1
  19. package/dist/assets/chunk-XXDRQBXY-BS0ojEzX.js +0 -1
  20. package/dist/assets/classDiagram-JCYQIIEL-CxMqqBtQ.js +0 -1
  21. package/dist/assets/classDiagram-v2-OCEON4UE-CxMqqBtQ.js +0 -1
  22. package/dist/assets/cose-bilkent-JH36ORCC-9jRlXuji.js +0 -1
  23. package/dist/assets/cynefin-VYW2F7L2-BDPznSFQ.js +0 -178
  24. package/dist/assets/cynefinDiagram-MW4NZA55-lSqlEkCZ.js +0 -62
  25. package/dist/assets/cytoscape.esm-D_LviqZs.js +0 -331
  26. package/dist/assets/dagre-VZM6K2ZE-Dfoe_jp9.js +0 -4
  27. package/dist/assets/diagram-7IWD3JNH-Df2T__dV.js +0 -30
  28. package/dist/assets/diagram-B4RE2ZJO-DoGtC0h7.js +0 -3
  29. package/dist/assets/diagram-LBJQPF4R-cH8DVJ6C.js +0 -24
  30. package/dist/assets/diagram-Q27KOJAE-8JJAGHRF.js +0 -24
  31. package/dist/assets/diagram-UB23O5K3-BGCDqdpG.js +0 -41
  32. package/dist/assets/disconnected-BuxohaUu.png +0 -0
  33. package/dist/assets/ebnfDiagram-BXEA7PRR-r2bgA64b.js +0 -1
  34. package/dist/assets/echarts-BB8VCqSB.js +0 -73
  35. package/dist/assets/erDiagram-JOGREHBK-BExdsKTd.js +0 -85
  36. package/dist/assets/flowDiagram-UKHOOZJN-B8wwUqLz.js +0 -156
  37. package/dist/assets/ganttDiagram-PKOTCBZU-SViiTCQN.js +0 -292
  38. package/dist/assets/gitGraphDiagram-DS77QQ5N-CmktWysd.js +0 -106
  39. package/dist/assets/graph-C9eacEi8.js +0 -1
  40. package/dist/assets/index-BzBvlpBo.js +0 -124
  41. package/dist/assets/index-DHk7_39t.css +0 -13
  42. package/dist/assets/infoDiagram-6WML65LV-BwUFVmXD.js +0 -2
  43. package/dist/assets/ishikawaDiagram-WSZJBQD7-Df6P2A6T.js +0 -70
  44. package/dist/assets/journeyDiagram-NVQOT4AX-Cjk4fifu.js +0 -139
  45. package/dist/assets/kanban-definition-27J2QSJJ-BcoyAznM.js +0 -89
  46. package/dist/assets/katex-C5jXJg4s.js +0 -257
  47. package/dist/assets/layout-DEXfKzaS.js +0 -1
  48. package/dist/assets/logo-DIAzbBuw.png +0 -0
  49. package/dist/assets/map-Czzmt4hB.js +0 -1
  50. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  51. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  52. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  53. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  54. package/dist/assets/mermaid-CX4mRs5N.js +0 -314
  55. package/dist/assets/mindmap-definition-FAOFIHXS-D6njotiS.js +0 -96
  56. package/dist/assets/pegDiagram-VL7TDLO6-CaA_iXxT.js +0 -1
  57. package/dist/assets/pieDiagram-7S7Q4E2Y-aYh5694F.js +0 -39
  58. package/dist/assets/quadrantDiagram-CIZ2JOQS-CaWyb4Jw.js +0 -7
  59. package/dist/assets/railroadDiagram-AXF67PYL-Bd_vMLsE.js +0 -1
  60. package/dist/assets/requirementDiagram-LRYGKXZP-CMxISoyd.js +0 -84
  61. package/dist/assets/sankeyDiagram-W5VNT64P-DLbFAyXe.js +0 -40
  62. package/dist/assets/sequenceDiagram-SI44F4Z6-CzdhMpvP.js +0 -162
  63. package/dist/assets/sizeCapture-X5ZJPWSS-CnezfYZ6.js +0 -1
  64. package/dist/assets/stateDiagram-OKZ733FA-5R9RLDEM.js +0 -1
  65. package/dist/assets/stateDiagram-v2-UEYNNEHI-Ck4l-gcT.js +0 -1
  66. package/dist/assets/swimlanes-SLNWSIFB-CeEnGZwJ.js +0 -2
  67. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DSebamlh.js +0 -8
  68. package/dist/assets/timeline-definition-Z64GVDOM-ZMWCENQJ.js +0 -120
  69. package/dist/assets/vennDiagram-T6HMQDX7-DVr1jz1I.js +0 -34
  70. package/dist/assets/vue-vendor-Bmy9n80X.js +0 -49
  71. package/dist/assets/wardleyDiagram-T6FBY63Y-DNp6yzs5.js +0 -78
  72. package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +0 -2
  73. package/dist/assets/xychartDiagram-ELKLHX3M-DC4NZzl1.js +0 -7
  74. package/dist/favicon.ico +0 -0
  75. package/dist/favicon.svg +0 -482
  76. package/dist/index.html +0 -24
  77. package/dist/logo-512x512.png +0 -0
  78. package/dist/logo.svg +0 -482
  79. package/dist/maskable-icon-512x512.png +0 -0
  80. package/dist/pwa-192x192.png +0 -0
  81. package/dist/pwa-512x512.png +0 -0
  82. package/dist/pwa-64x64.png +0 -0
  83. package/dist/sw.js +0 -2
@@ -6,6 +6,7 @@ const axios = require('axios')
6
6
  const v = require('../../package.json').version
7
7
  const { createClientStore } = require('../store/clients.js')
8
8
  const datastore = require('../store/data.js')
9
+ const { attachToContext } = require('../store/reactive.js')
9
10
  const statestore = require('../store/state.js')
10
11
  const { appendTopic, addConnectionCredentials, matchesClient, normalizeClientId, getThirdPartyWidgets } = require('../utils/index.js')
11
12
 
@@ -401,6 +402,20 @@ module.exports = function (RED) {
401
402
 
402
403
  node._created = Date.now()
403
404
 
405
+ try {
406
+ let unavailable = null
407
+ const store = attachToContext(node.context().global, {
408
+ clone: RED.util.cloneMessage,
409
+ onUnavailable: (err) => { unavailable = err },
410
+ onReplaced: () => node.warn('global.dashboardStore was replaced by a flow since the last deploy, discarding the stored value of every widget. The store has been re-created. To write your own data, set global.dashboardStore.<key> rather than replacing global.dashboardStore itself.')
411
+ })
412
+ if (!store) {
413
+ node.warn('Dashboard data store disabled: the global context store doesn\'t hold objects by reference (e.g. cache: false), so live state can\'t work. Use the memory store or localfilesystem with cache: true.' + (unavailable ? ' Reading it failed with: ' + unavailable.message : ''))
414
+ }
415
+ } catch (err) {
416
+ node.warn('Dashboard data store disabled: could not initialise it in global context (' + err.message + ').')
417
+ }
418
+
404
419
  n.root = RED.settings.httpNodeRoot || '/'
405
420
 
406
421
  /** @type {Object.<string, Socket>} */
@@ -0,0 +1,154 @@
1
+ const STORE = Symbol.for('@flowfuse/node-red-dashboard/store')
2
+ const NAMESPACE = 'dashboardStore'
3
+
4
+ class Entry {
5
+ constructor () {
6
+ this.value = undefined
7
+ this.quality = 'GOOD'
8
+ this.timestamp = 0
9
+ this.history = []
10
+ }
11
+ }
12
+
13
+ // only plain objects and arrays are deep-watched; Buffers, Dates, class instances etc. are opaque leaves
14
+ function isReactable (v) {
15
+ if (Array.isArray(v)) return true
16
+ if (v === null || typeof v !== 'object') return false
17
+ const proto = Object.getPrototypeOf(v)
18
+ return proto === null || Object.getPrototypeOf(proto) === null
19
+ }
20
+
21
+ // reads through proxies (structuredClone rejects them); production injects RED.util.cloneMessage
22
+ function deepClone (v, seen = new WeakMap()) {
23
+ if (v === null || typeof v !== 'object') return v
24
+ if (Buffer.isBuffer(v)) return Buffer.from(v)
25
+ if (v instanceof Date) return new Date(v)
26
+ if (seen.has(v)) return seen.get(v)
27
+ const out = Array.isArray(v) ? [] : {}
28
+ seen.set(v, out)
29
+ for (const k of Object.keys(v)) out[k] = deepClone(v[k], seen)
30
+ return out
31
+ }
32
+
33
+ function deepReactive (value, notify, path, clone, seen = new WeakMap()) {
34
+ if (!isReactable(value)) return value
35
+ if (seen.has(value)) return seen.get(value)
36
+ const proxy = new Proxy(value, {
37
+ get (t, p, r) { return Reflect.get(t, p, r) },
38
+ set (t, p, v) {
39
+ if (typeof p === 'symbol') return Reflect.set(t, p, v)
40
+ // a push writes the new index then grows length; only a shrink is a change worth reporting
41
+ const shrank = p === 'length' && Array.isArray(t) && v < t.length
42
+ t[p] = deepReactive(clone(v), notify, `${path}.${String(p)}`, clone)
43
+ if (p !== 'length') notify(`${path}.${String(p)}`)
44
+ else if (shrank) notify(path)
45
+ return true
46
+ },
47
+ deleteProperty (t, p) {
48
+ const existed = p in t
49
+ const ok = delete t[p]
50
+ if (existed) notify(`${path}.${String(p)}`)
51
+ return ok
52
+ }
53
+ })
54
+ // registered before walking children so a cycle resolves to this proxy instead of recursing
55
+ seen.set(value, proxy)
56
+ for (const k of Object.keys(value)) value[k] = deepReactive(value[k], notify, `${path}.${k}`, clone, seen)
57
+ return proxy
58
+ }
59
+
60
+ function createDataStore ({ maxHistory = 5, onChange, clone = deepClone, now = Date.now } = {}) {
61
+ const cloneValue = (v) => (v && typeof v === 'object') ? clone(v) : v
62
+ const emit = (prop, rec, path) => { try { onChange?.(prop, rec, path) } catch (err) {} }
63
+ const records = Object.create(null)
64
+
65
+ records[STORE] = true
66
+
67
+ const handler = {
68
+ get (target, prop, receiver) {
69
+ if (typeof prop === 'symbol') return Reflect.get(target, prop, receiver)
70
+ if (prop === 'toJSON') {
71
+ return () => Object.fromEntries(Object.entries(target).map(([k, r]) => [k, r.value]))
72
+ }
73
+ if (prop.startsWith('$')) {
74
+ const rec = target[prop.slice(1)]
75
+ return rec && Object.freeze({ value: rec.value, quality: rec.quality, timestamp: rec.timestamp, history: Object.freeze(rec.history.slice()) })
76
+ }
77
+ const rec = target[prop]
78
+ return rec ? rec.value : undefined
79
+ },
80
+ set (target, prop, value) {
81
+ if (typeof prop === 'symbol') return Reflect.set(target, prop, value)
82
+ // returning true rather than false so a strict caller picking a reserved name doesn't throw
83
+ if (prop === '__proto__' || prop === 'constructor' || prop === 'toJSON') return true
84
+ if (prop.startsWith('$')) return true // '$' is reserved for the read-only meta view
85
+
86
+ let rec = target[prop]
87
+ if (!rec) {
88
+ rec = new Entry()
89
+ target[prop] = rec
90
+ } else {
91
+ rec.history.push(Object.freeze({ value: cloneValue(rec.value), timestamp: rec.timestamp }))
92
+ if (rec.history.length > maxHistory) rec.history.shift()
93
+ }
94
+ const notify = (path) => {
95
+ // a replaced value keeps its proxies alive; they must not report into the record any more
96
+ if (rec.value !== wrapped) return
97
+ rec.timestamp = now()
98
+ emit(prop, rec, path)
99
+ }
100
+ // clone on write so a flow reusing its own object can't mutate stored state
101
+ const wrapped = deepReactive(cloneValue(value), notify, prop, cloneValue)
102
+ rec.value = wrapped
103
+ rec.timestamp = now()
104
+ emit(prop, rec, prop)
105
+ return true
106
+ },
107
+ ownKeys (target) { return Reflect.ownKeys(target) },
108
+ deleteProperty (target, prop) {
109
+ if (typeof prop === 'symbol') return Reflect.deleteProperty(target, prop)
110
+ const rec = target[prop]
111
+ const existed = prop in target
112
+ const ok = delete target[prop]
113
+ if (rec) rec.value = undefined
114
+ if (existed) emit(prop, undefined, prop)
115
+ return ok
116
+ }
117
+ }
118
+ return new Proxy(records, handler)
119
+ }
120
+
121
+ const injected = new WeakMap()
122
+
123
+ // Inject the store into global context once. On redeploy the existing store is reused;
124
+ // after a restart with a persistent context store, existing plain data is rehydrated.
125
+ // Returns null when the context store can't hold a live proxy: a cache-off store serves
126
+ // only async access so a sync get throws, and a serialising store hands back a copy.
127
+ function attachToContext (globalContext, opts = {}) {
128
+ const namespace = opts.namespace || NAMESPACE
129
+ let existing
130
+ try {
131
+ existing = globalContext.get(namespace)
132
+ } catch (err) {
133
+ opts.onUnavailable?.(err)
134
+ return null
135
+ }
136
+ if (existing && existing[STORE]) {
137
+ injected.set(globalContext, existing)
138
+ return existing
139
+ }
140
+
141
+ const previous = injected.get(globalContext)
142
+ if (previous && existing !== previous) opts.onReplaced?.()
143
+
144
+ const store = createDataStore(opts)
145
+ if (isReactable(existing) && !Array.isArray(existing)) {
146
+ for (const [k, v] of Object.entries(existing)) store[k] = v
147
+ }
148
+ globalContext.set(namespace, store)
149
+ if (globalContext.get(namespace) !== store) return null
150
+ injected.set(globalContext, store)
151
+ return store
152
+ }
153
+
154
+ module.exports = { createDataStore, attachToContext, NAMESPACE, STORE }
@@ -10,6 +10,7 @@ module.exports = function (RED) {
10
10
  const group = RED.nodes.getNode(config.group)
11
11
 
12
12
  const evts = {
13
+ onAction: true,
13
14
  beforeSend: async function (msg) {
14
15
  const updates = msg.ui_update
15
16
  if (updates) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowfuse/node-red-dashboard",
3
- "version": "1.32.0",
3
+ "version": "1.32.1-15daa3c-202609281436.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"
Binary file
@@ -1 +0,0 @@
1
- import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-DpGB6o5S.js";import{p as f}from"./chunk-JWPE2WC7-UNqcBwu_.js";import{_ as n,l as o}from"./mermaid-CX4mRs5N.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-BDPznSFQ.js";var v=d().RailroadAbnf.parser.LangiumParser,l=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),i=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:i,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return l(e.element);case"AbnfOptionalGroup":return{type:"optional",element:l(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:l(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},w={parser:R,db:a,renderer:u,styles:p};export{w as diagram};