ultravisor 1.0.3 → 1.0.8

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 (248) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.claude/ultravisor-dev-config.json +3 -0
  3. package/card-audit.json +1545 -0
  4. package/docs/_sidebar.md +17 -0
  5. package/docs/css/docuserve.css +73 -0
  6. package/docs/features/beacon-authentication.md +209 -0
  7. package/docs/features/beacon-providers.md +1299 -0
  8. package/docs/features/beacons.md +530 -0
  9. package/docs/features/capabilities.md +193 -0
  10. package/docs/features/llm-model-setup.md +505 -0
  11. package/docs/features/llm.md +262 -0
  12. package/docs/features/tasks-content-system.md +110 -0
  13. package/docs/features/tasks-data-transform.md +233 -0
  14. package/docs/features/tasks-extension.md +36 -0
  15. package/docs/features/tasks-file-system.md +203 -0
  16. package/docs/features/tasks-flow-control.md +125 -0
  17. package/docs/features/tasks-http-client.md +119 -0
  18. package/docs/features/tasks-llm.md +118 -0
  19. package/docs/features/tasks-meadow-api.md +163 -0
  20. package/docs/features/tasks-user-interaction.md +49 -0
  21. package/docs/features/tasks.md +14 -0
  22. package/docs/index.html +1 -1
  23. package/docs/retold-catalog.json +1 -1
  24. package/docs/retold-keyword-index.json +17374 -5
  25. package/operation-library/api-data-pipeline.json +143 -0
  26. package/operation-library/beacon-remote-command.json +72 -0
  27. package/operation-library/conditional-file-backup.json +143 -0
  28. package/operation-library/config-processor.json +136 -0
  29. package/operation-library/csv-data-analysis.json +228 -0
  30. package/operation-library/csv-to-json.json +160 -0
  31. package/operation-library/directory-inventory.json +128 -0
  32. package/operation-library/expression-calculator.json +114 -0
  33. package/operation-library/file-search-replace.json +175 -0
  34. package/operation-library/git-status-report.json +368 -0
  35. package/operation-library/health-check-ping.json +329 -0
  36. package/operation-library/json-config-merge.json +313 -0
  37. package/operation-library/llm-embedding-tool-use.json +314 -0
  38. package/operation-library/llm-multi-turn-analysis.json +322 -0
  39. package/operation-library/llm-summarize.json +264 -0
  40. package/operation-library/log-line-counter.json +185 -0
  41. package/operation-library/meadow-crud-lifecycle.json +264 -0
  42. package/operation-library/npm-project-validator.json +193 -0
  43. package/operation-library/rest-api-orchestrator.json +218 -0
  44. package/operation-library/shell-system-info.json +137 -0
  45. package/operation-library/simple-echo.json +95 -0
  46. package/operation-library/template-processor.json +299 -0
  47. package/operation-library/text-sanitizer.json +176 -0
  48. package/package.json +12 -8
  49. package/source/Ultravisor.cjs +30 -3
  50. package/source/beacon/Ultravisor-Beacon-CLI.cjs +143 -0
  51. package/source/beacon/Ultravisor-Beacon-CapabilityProvider.cjs +129 -0
  52. package/source/beacon/Ultravisor-Beacon-Client.cjs +568 -0
  53. package/source/beacon/Ultravisor-Beacon-Executor.cjs +500 -0
  54. package/source/beacon/Ultravisor-Beacon-ProviderRegistry.cjs +330 -0
  55. package/source/beacon/providers/Ultravisor-Beacon-Provider-FileSystem.cjs +331 -0
  56. package/source/beacon/providers/Ultravisor-Beacon-Provider-LLM.cjs +966 -0
  57. package/source/beacon/providers/Ultravisor-Beacon-Provider-Shell.cjs +95 -0
  58. package/source/cli/Ultravisor-CLIProgram.cjs +44 -22
  59. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +29 -18
  60. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +62 -19
  61. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +27 -15
  62. package/source/config/Ultravisor-Default-Command-Configuration.cjs +12 -3
  63. package/source/services/Ultravisor-Beacon-Coordinator.cjs +966 -0
  64. package/source/services/Ultravisor-ExecutionEngine.cjs +1093 -0
  65. package/source/services/Ultravisor-ExecutionManifest.cjs +629 -0
  66. package/source/services/Ultravisor-Hypervisor-State.cjs +270 -97
  67. package/source/services/Ultravisor-Hypervisor.cjs +185 -75
  68. package/source/services/Ultravisor-Schedule-Persistence-Base.cjs +45 -0
  69. package/source/services/Ultravisor-StateManager.cjs +253 -0
  70. package/source/services/Ultravisor-TaskTypeRegistry.cjs +213 -0
  71. package/source/services/persistence/Ultravisor-Schedule-Persistence-JSONFile.cjs +140 -0
  72. package/source/services/tasks/Ultravisor-BuiltIn-TaskConfigs.cjs +19 -0
  73. package/source/services/tasks/Ultravisor-TaskHelper-BeaconDispatch.cjs +107 -0
  74. package/source/services/tasks/Ultravisor-TaskType-Base.cjs +125 -0
  75. package/source/services/tasks/content-system/Ultravisor-TaskConfigs-ContentSystem.cjs +90 -0
  76. package/source/services/tasks/content-system/definitions/content-create-folder.json +24 -0
  77. package/source/services/tasks/content-system/definitions/content-list-files.json +27 -0
  78. package/source/services/tasks/content-system/definitions/content-read-file.json +26 -0
  79. package/source/services/tasks/content-system/definitions/content-save-file.json +26 -0
  80. package/source/services/tasks/data-transform/Ultravisor-TaskConfigs-DataTransform.cjs +577 -0
  81. package/source/services/tasks/data-transform/Ultravisor-TaskType-ReplaceString.cjs +80 -0
  82. package/source/services/tasks/data-transform/Ultravisor-TaskType-SetValues.cjs +74 -0
  83. package/source/services/tasks/data-transform/Ultravisor-TaskType-StringAppender.cjs +90 -0
  84. package/source/services/tasks/data-transform/definitions/comprehension-intersect.json +24 -0
  85. package/source/services/tasks/data-transform/definitions/csv-transform.json +24 -0
  86. package/source/services/tasks/data-transform/definitions/expression-solver.json +20 -0
  87. package/source/services/tasks/data-transform/definitions/histogram.json +23 -0
  88. package/source/services/tasks/data-transform/definitions/parse-csv.json +27 -0
  89. package/source/services/tasks/data-transform/definitions/replace-string.json +27 -0
  90. package/source/services/tasks/data-transform/definitions/set-values.json +17 -0
  91. package/source/services/tasks/data-transform/definitions/string-appender.json +22 -0
  92. package/source/services/tasks/data-transform/definitions/template-string.json +20 -0
  93. package/source/services/tasks/extension/Ultravisor-TaskConfigs-Extension.cjs +87 -0
  94. package/source/services/tasks/extension/definitions/beacon-dispatch.json +32 -0
  95. package/source/services/tasks/file-system/Ultravisor-TaskConfigs-FileSystem.cjs +509 -0
  96. package/source/services/tasks/file-system/Ultravisor-TaskType-ReadFile.cjs +96 -0
  97. package/source/services/tasks/file-system/Ultravisor-TaskType-ReadFileBuffered.cjs +123 -0
  98. package/source/services/tasks/file-system/Ultravisor-TaskType-WriteFile.cjs +106 -0
  99. package/source/services/tasks/file-system/definitions/copy-file.json +27 -0
  100. package/source/services/tasks/file-system/definitions/list-files.json +27 -0
  101. package/source/services/tasks/file-system/definitions/read-file-buffered.json +31 -0
  102. package/source/services/tasks/file-system/definitions/read-file.json +26 -0
  103. package/source/services/tasks/file-system/definitions/read-json.json +23 -0
  104. package/source/services/tasks/file-system/definitions/write-file.json +29 -0
  105. package/source/services/tasks/file-system/definitions/write-json.json +30 -0
  106. package/source/services/tasks/flow-control/Ultravisor-TaskConfigs-FlowControl.cjs +353 -0
  107. package/source/services/tasks/flow-control/Ultravisor-TaskType-IfConditional.cjs +125 -0
  108. package/source/services/tasks/flow-control/Ultravisor-TaskType-LaunchOperation.cjs +186 -0
  109. package/source/services/tasks/flow-control/Ultravisor-TaskType-SplitExecute.cjs +164 -0
  110. package/source/services/tasks/flow-control/definitions/if-conditional.json +25 -0
  111. package/source/services/tasks/flow-control/definitions/launch-operation.json +27 -0
  112. package/source/services/tasks/flow-control/definitions/split-execute.json +32 -0
  113. package/source/services/tasks/http-client/Ultravisor-TaskConfigs-HttpClient.cjs +281 -0
  114. package/source/services/tasks/http-client/definitions/get-json.json +26 -0
  115. package/source/services/tasks/http-client/definitions/get-text.json +26 -0
  116. package/source/services/tasks/http-client/definitions/rest-request.json +32 -0
  117. package/source/services/tasks/http-client/definitions/send-json.json +28 -0
  118. package/source/services/tasks/llm/Ultravisor-TaskConfigs-LLM.cjs +605 -0
  119. package/source/services/tasks/llm/definitions/llm-chat-completion.json +46 -0
  120. package/source/services/tasks/llm/definitions/llm-embedding.json +31 -0
  121. package/source/services/tasks/llm/definitions/llm-tool-use.json +42 -0
  122. package/source/services/tasks/meadow-api/Ultravisor-TaskConfigs-MeadowApi.cjs +341 -0
  123. package/source/services/tasks/meadow-api/definitions/meadow-count.json +26 -0
  124. package/source/services/tasks/meadow-api/definitions/meadow-create.json +26 -0
  125. package/source/services/tasks/meadow-api/definitions/meadow-delete.json +23 -0
  126. package/source/services/tasks/meadow-api/definitions/meadow-read.json +26 -0
  127. package/source/services/tasks/meadow-api/definitions/meadow-reads.json +29 -0
  128. package/source/services/tasks/meadow-api/definitions/meadow-update.json +26 -0
  129. package/source/services/tasks/shell/Ultravisor-TaskConfigs-Shell.cjs +63 -0
  130. package/source/services/tasks/shell/definitions/command.json +29 -0
  131. package/source/services/tasks/user-interaction/Ultravisor-TaskConfigs-UserInteraction.cjs +64 -0
  132. package/source/services/tasks/user-interaction/Ultravisor-TaskType-ErrorMessage.cjs +55 -0
  133. package/source/services/tasks/user-interaction/Ultravisor-TaskType-ValueInput.cjs +47 -0
  134. package/source/services/tasks/user-interaction/definitions/error-message.json +18 -0
  135. package/source/services/tasks/user-interaction/definitions/value-input.json +23 -0
  136. package/source/web_server/Ultravisor-API-Server.cjs +849 -124
  137. package/test/Ultravisor_browser_tests.js +2226 -0
  138. package/test/Ultravisor_operation_library_tests.js +894 -0
  139. package/test/Ultravisor_tests.js +4946 -5044
  140. package/test/workflows/test-content-concatenate-all.json +339 -0
  141. package/test/workflows/test-content-full-pipeline.json +159 -0
  142. package/test/workflows/test-content-list-files.json +76 -0
  143. package/test/workflows/test-content-save-and-read.json +106 -0
  144. package/webinterface/css/ultravisor-themes.css +668 -0
  145. package/webinterface/css/ultravisor.css +37 -14
  146. package/webinterface/docs/card-help/beacon-dispatch.md +30 -0
  147. package/webinterface/docs/card-help/command.md +27 -0
  148. package/webinterface/docs/card-help/comprehension-intersect.md +24 -0
  149. package/webinterface/docs/card-help/content-create-folder.md +22 -0
  150. package/webinterface/docs/card-help/content-list-files.md +25 -0
  151. package/webinterface/docs/card-help/content-read-file.md +24 -0
  152. package/webinterface/docs/card-help/content-save-file.md +24 -0
  153. package/webinterface/docs/card-help/copy-file.md +25 -0
  154. package/webinterface/docs/card-help/csv-transform.md +24 -0
  155. package/webinterface/docs/card-help/end.md +11 -0
  156. package/webinterface/docs/card-help/error-message.md +16 -0
  157. package/webinterface/docs/card-help/expression-solver.md +21 -0
  158. package/webinterface/docs/card-help/get-json.md +24 -0
  159. package/webinterface/docs/card-help/get-text.md +24 -0
  160. package/webinterface/docs/card-help/histogram.md +23 -0
  161. package/webinterface/docs/card-help/if-conditional.md +23 -0
  162. package/webinterface/docs/card-help/launch-operation.md +25 -0
  163. package/webinterface/docs/card-help/list-files.md +25 -0
  164. package/webinterface/docs/card-help/llm-chat-completion.md +43 -0
  165. package/webinterface/docs/card-help/llm-embedding.md +24 -0
  166. package/webinterface/docs/card-help/llm-tool-use.md +39 -0
  167. package/webinterface/docs/card-help/meadow-count.md +24 -0
  168. package/webinterface/docs/card-help/meadow-create.md +24 -0
  169. package/webinterface/docs/card-help/meadow-delete.md +19 -0
  170. package/webinterface/docs/card-help/meadow-read.md +24 -0
  171. package/webinterface/docs/card-help/meadow-reads.md +27 -0
  172. package/webinterface/docs/card-help/meadow-update.md +24 -0
  173. package/webinterface/docs/card-help/parse-csv.md +27 -0
  174. package/webinterface/docs/card-help/read-file-buffered.md +29 -0
  175. package/webinterface/docs/card-help/read-file.md +24 -0
  176. package/webinterface/docs/card-help/read-json.md +21 -0
  177. package/webinterface/docs/card-help/replace-string.md +25 -0
  178. package/webinterface/docs/card-help/rest-request.md +30 -0
  179. package/webinterface/docs/card-help/send-json.md +26 -0
  180. package/webinterface/docs/card-help/set-values.md +16 -0
  181. package/webinterface/docs/card-help/split-execute.md +35 -0
  182. package/webinterface/docs/card-help/start.md +11 -0
  183. package/webinterface/docs/card-help/string-appender.md +22 -0
  184. package/webinterface/docs/card-help/template-string.md +21 -0
  185. package/webinterface/docs/card-help/value-input.md +24 -0
  186. package/webinterface/docs/card-help/write-file.md +27 -0
  187. package/webinterface/docs/card-help/write-json.md +28 -0
  188. package/webinterface/html/index.html +5 -0
  189. package/webinterface/package.json +13 -4
  190. package/webinterface/source/Pict-Application-Ultravisor.js +564 -70
  191. package/webinterface/source/Ultravisor-TimingUtils.js +99 -0
  192. package/webinterface/source/card-help-content.js +46 -0
  193. package/webinterface/source/cards/Ultravisor-BuiltIn-CardConfigs.js +174 -0
  194. package/webinterface/source/cards/Ultravisor-CardConfigGenerator.js +431 -0
  195. package/webinterface/source/data/ExampleFlow-CSVPipeline.js +231 -0
  196. package/webinterface/source/data/ExampleFlow-FileProcessor.js +315 -0
  197. package/webinterface/source/data/ExampleFlow-MeadowPipeline.js +328 -0
  198. package/webinterface/source/panels/Ultravisor-CardSettingsPanel.js +902 -0
  199. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +20 -8
  200. package/webinterface/source/views/PictView-Ultravisor-BeaconList.js +817 -0
  201. package/webinterface/source/views/PictView-Ultravisor-BottomBar.js +5 -5
  202. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +25 -25
  203. package/webinterface/source/views/PictView-Ultravisor-Documentation.js +856 -0
  204. package/webinterface/source/views/PictView-Ultravisor-FlowEditor.js +777 -0
  205. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +321 -58
  206. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +36 -91
  207. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +388 -16
  208. package/webinterface/source/views/PictView-Ultravisor-PendingInput.js +314 -0
  209. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +521 -65
  210. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +333 -71
  211. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +257 -21
  212. package/webinterface/theme-sampler.html +645 -0
  213. package/debug/Harness.js +0 -6
  214. package/source/services/Ultravisor-Operation-Manifest.cjs +0 -160
  215. package/source/services/Ultravisor-Operation.cjs +0 -200
  216. package/source/services/Ultravisor-Task.cjs +0 -349
  217. package/source/services/events/Ultravisor-Hypervisor-Event-Solver.cjs +0 -11
  218. package/source/services/tasks/Ultravisor-Task-Base.cjs +0 -264
  219. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +0 -188
  220. package/source/services/tasks/Ultravisor-Task-Command.cjs +0 -65
  221. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +0 -190
  222. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +0 -104
  223. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +0 -72
  224. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +0 -336
  225. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +0 -143
  226. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +0 -146
  227. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +0 -158
  228. package/source/services/tasks/Ultravisor-Task-Request.cjs +0 -56
  229. package/source/services/tasks/Ultravisor-Task-Solver.cjs +0 -89
  230. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +0 -93
  231. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +0 -127
  232. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +0 -119
  233. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +0 -109
  234. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +0 -112
  235. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +0 -499
  236. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +0 -150
  237. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +0 -110
  238. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +0 -89
  239. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +0 -87
  240. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +0 -67
  241. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +0 -66
  242. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +0 -69
  243. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +0 -95
  244. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +0 -96
  245. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +0 -99
  246. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +0 -102
  247. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +0 -220
  248. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +0 -248
@@ -0,0 +1,817 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const _ViewConfiguration =
4
+ {
5
+ ViewIdentifier: "Ultravisor-BeaconList",
6
+
7
+ DefaultRenderable: "Ultravisor-BeaconList-Content",
8
+ DefaultDestinationAddress: "#Ultravisor-Content-Container",
9
+
10
+ AutoRender: false,
11
+
12
+ CSS: /*css*/`
13
+ .ultravisor-beaconlist {
14
+ padding: 2em;
15
+ max-width: 1400px;
16
+ margin: 0 auto;
17
+ }
18
+ .ultravisor-beaconlist-header {
19
+ display: flex;
20
+ justify-content: space-between;
21
+ align-items: center;
22
+ margin-bottom: 1.5em;
23
+ padding-bottom: 1em;
24
+ border-bottom: 1px solid var(--uv-border-subtle);
25
+ }
26
+ .ultravisor-beaconlist-header h1 {
27
+ margin: 0;
28
+ font-size: 2em;
29
+ font-weight: 300;
30
+ color: var(--uv-text);
31
+ }
32
+
33
+ /* Summary bar */
34
+ .ultravisor-beacon-summary {
35
+ display: flex;
36
+ gap: 1em;
37
+ margin-bottom: 1.5em;
38
+ }
39
+ .ultravisor-beacon-summary-card {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 0.5em;
43
+ background: var(--uv-bg-surface);
44
+ border: 1px solid var(--uv-border-subtle);
45
+ border-radius: 8px;
46
+ padding: 0.6em 1.2em;
47
+ }
48
+ .ultravisor-beacon-summary-count {
49
+ font-size: 1.5em;
50
+ font-weight: 700;
51
+ color: var(--uv-text);
52
+ }
53
+ .ultravisor-beacon-summary-label {
54
+ font-size: 0.8em;
55
+ color: var(--uv-text-secondary);
56
+ text-transform: uppercase;
57
+ letter-spacing: 0.5px;
58
+ }
59
+ .ultravisor-beacon-summary-card.online .ultravisor-beacon-summary-count {
60
+ color: var(--uv-success);
61
+ }
62
+ .ultravisor-beacon-summary-card.busy .ultravisor-beacon-summary-count {
63
+ color: var(--uv-info);
64
+ }
65
+ .ultravisor-beacon-summary-card.offline .ultravisor-beacon-summary-count {
66
+ color: var(--uv-text-tertiary);
67
+ }
68
+
69
+ /* Beacon table */
70
+ .ultravisor-beacon-table {
71
+ width: 100%;
72
+ border-collapse: collapse;
73
+ }
74
+ .ultravisor-beacon-table th {
75
+ background-color: var(--uv-bg-surface);
76
+ text-align: left;
77
+ padding: 0.6em 0.75em;
78
+ font-size: 0.8em;
79
+ text-transform: uppercase;
80
+ letter-spacing: 0.5px;
81
+ color: var(--uv-text-secondary);
82
+ border-bottom: 1px solid var(--uv-border-subtle);
83
+ }
84
+ .ultravisor-beacon-table td {
85
+ padding: 0.6em 0.75em;
86
+ border-bottom: 1px solid var(--uv-border-subtle);
87
+ font-size: 0.9em;
88
+ }
89
+ .ultravisor-beacon-table tr:hover td {
90
+ background-color: var(--uv-table-row-hover);
91
+ }
92
+
93
+ /* Status badge */
94
+ .ultravisor-beacon-status {
95
+ display: inline-flex;
96
+ align-items: center;
97
+ gap: 0.4em;
98
+ padding: 0.15em 0.6em;
99
+ border-radius: 3px;
100
+ font-size: 0.8em;
101
+ font-weight: 600;
102
+ }
103
+ .ultravisor-beacon-status-dot {
104
+ width: 7px;
105
+ height: 7px;
106
+ border-radius: 50%;
107
+ display: inline-block;
108
+ }
109
+ .ultravisor-beacon-status.online {
110
+ background-color: #1b5e20;
111
+ color: #a5d6a7;
112
+ }
113
+ .ultravisor-beacon-status.online .ultravisor-beacon-status-dot {
114
+ background-color: #66bb6a;
115
+ }
116
+ .ultravisor-beacon-status.busy {
117
+ background-color: #0d47a1;
118
+ color: #90caf9;
119
+ }
120
+ .ultravisor-beacon-status.busy .ultravisor-beacon-status-dot {
121
+ background-color: #42a5f5;
122
+ }
123
+ .ultravisor-beacon-status.offline {
124
+ background-color: #424242;
125
+ color: #9e9e9e;
126
+ }
127
+ .ultravisor-beacon-status.offline .ultravisor-beacon-status-dot {
128
+ background-color: #757575;
129
+ }
130
+
131
+ /* Capability tags */
132
+ .ultravisor-beacon-cap-tag {
133
+ display: inline-block;
134
+ padding: 0.1em 0.5em;
135
+ border-radius: 10px;
136
+ font-size: 0.75em;
137
+ font-weight: 600;
138
+ background-color: var(--uv-bg-elevated);
139
+ color: var(--uv-brand);
140
+ border: 1px solid var(--uv-border-subtle);
141
+ margin-right: 0.3em;
142
+ margin-bottom: 0.2em;
143
+ }
144
+
145
+ /* Detail panel */
146
+ .ultravisor-beacon-detail {
147
+ background: var(--uv-bg-surface);
148
+ border: 1px solid var(--uv-border-subtle);
149
+ border-radius: 8px;
150
+ padding: 1.5em;
151
+ margin-top: 1em;
152
+ display: none;
153
+ }
154
+ .ultravisor-beacon-detail.visible {
155
+ display: block;
156
+ }
157
+ .ultravisor-beacon-detail h3 {
158
+ margin: 0 0 0.75em 0;
159
+ color: var(--uv-text-secondary);
160
+ font-weight: 400;
161
+ }
162
+ .ultravisor-beacon-detail-grid {
163
+ display: grid;
164
+ grid-template-columns: 1fr 1fr 1fr;
165
+ gap: 1em;
166
+ margin-bottom: 1em;
167
+ }
168
+ .ultravisor-beacon-detail-field {
169
+ background: var(--uv-bg-base);
170
+ border-radius: 4px;
171
+ padding: 0.6em 0.8em;
172
+ }
173
+ .ultravisor-beacon-detail-field-label {
174
+ font-size: 0.7em;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.5px;
177
+ color: var(--uv-text-tertiary);
178
+ margin-bottom: 0.2em;
179
+ }
180
+ .ultravisor-beacon-detail-field-value {
181
+ font-size: 0.9em;
182
+ color: var(--uv-text);
183
+ word-break: break-all;
184
+ }
185
+ .ultravisor-beacon-detail-field-value code {
186
+ color: var(--uv-brand);
187
+ font-size: 0.85em;
188
+ }
189
+
190
+ /* Section headers */
191
+ .ultravisor-beacon-section {
192
+ margin-top: 2em;
193
+ }
194
+ .ultravisor-beacon-section-header {
195
+ display: flex;
196
+ justify-content: space-between;
197
+ align-items: center;
198
+ margin-bottom: 1em;
199
+ padding-bottom: 0.5em;
200
+ border-bottom: 1px solid var(--uv-border-subtle);
201
+ }
202
+ .ultravisor-beacon-section-header h2 {
203
+ margin: 0;
204
+ font-size: 1.4em;
205
+ font-weight: 300;
206
+ color: var(--uv-text);
207
+ }
208
+
209
+ /* Work item status */
210
+ .ultravisor-workitem-status {
211
+ display: inline-block;
212
+ padding: 0.15em 0.5em;
213
+ border-radius: 3px;
214
+ font-size: 0.8em;
215
+ font-weight: 600;
216
+ }
217
+ .ultravisor-workitem-status.pending {
218
+ background-color: #e65100;
219
+ color: #ffe0b2;
220
+ }
221
+ .ultravisor-workitem-status.assigned {
222
+ background-color: #4527a0;
223
+ color: #d1c4e9;
224
+ }
225
+ .ultravisor-workitem-status.running {
226
+ background-color: var(--uv-info);
227
+ color: var(--uv-text-heading);
228
+ }
229
+ .ultravisor-workitem-status.complete {
230
+ background-color: #2e7d32;
231
+ color: #c8e6c9;
232
+ }
233
+ .ultravisor-workitem-status.error {
234
+ background-color: #c62828;
235
+ color: #ffcdd2;
236
+ }
237
+ .ultravisor-workitem-status.timeout {
238
+ background-color: #f57f17;
239
+ color: #fff9c4;
240
+ }
241
+
242
+ /* Progress bar */
243
+ .ultravisor-workitem-progress {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 0.5em;
247
+ }
248
+ .ultravisor-workitem-progress-bar {
249
+ width: 60px;
250
+ height: 6px;
251
+ background-color: var(--uv-bg-base);
252
+ border-radius: 3px;
253
+ overflow: hidden;
254
+ }
255
+ .ultravisor-workitem-progress-fill {
256
+ height: 100%;
257
+ background-color: var(--uv-brand);
258
+ border-radius: 3px;
259
+ transition: width 0.3s;
260
+ }
261
+ .ultravisor-workitem-progress-text {
262
+ font-size: 0.75em;
263
+ color: var(--uv-text-secondary);
264
+ }
265
+
266
+ /* Empty state */
267
+ .ultravisor-beacon-empty {
268
+ text-align: center;
269
+ padding: 3em;
270
+ color: var(--uv-text-tertiary);
271
+ font-size: 0.95em;
272
+ }
273
+
274
+ /* Auto-refresh indicator */
275
+ .ultravisor-beacon-refresh-indicator {
276
+ font-size: 0.75em;
277
+ color: var(--uv-text-tertiary);
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 0.4em;
281
+ }
282
+ .ultravisor-beacon-refresh-dot {
283
+ width: 6px;
284
+ height: 6px;
285
+ border-radius: 50%;
286
+ background-color: var(--uv-success);
287
+ animation: ultravisor-pulse 2s ease-in-out infinite;
288
+ }
289
+ @keyframes ultravisor-pulse {
290
+ 0%, 100% { opacity: 1; }
291
+ 50% { opacity: 0.3; }
292
+ }
293
+ `,
294
+
295
+ Templates:
296
+ [
297
+ {
298
+ Hash: "Ultravisor-BeaconList-Template",
299
+ Template: /*html*/`
300
+ <div class="ultravisor-beaconlist">
301
+ <div class="ultravisor-beaconlist-header">
302
+ <h1>Beacons</h1>
303
+ <div style="display:flex; align-items:center; gap:1em;">
304
+ <div class="ultravisor-beacon-refresh-indicator">
305
+ <span class="ultravisor-beacon-refresh-dot"></span>
306
+ <span>Auto-refresh</span>
307
+ </div>
308
+ <button class="ultravisor-btn ultravisor-btn-secondary" onclick="{~P~}.views['Ultravisor-BeaconList'].refreshAll()">Refresh</button>
309
+ </div>
310
+ </div>
311
+ <div id="Ultravisor-BeaconList-Summary"></div>
312
+ <div id="Ultravisor-BeaconList-Body"></div>
313
+ <div id="Ultravisor-BeaconList-Detail"></div>
314
+ <div class="ultravisor-beacon-section" id="Ultravisor-BeaconList-WorkQueue-Section">
315
+ <div class="ultravisor-beacon-section-header">
316
+ <h2>Work Queue</h2>
317
+ </div>
318
+ <div id="Ultravisor-BeaconList-WorkQueue"></div>
319
+ </div>
320
+ <div class="ultravisor-beacon-section" id="Ultravisor-BeaconList-Affinity-Section">
321
+ <div class="ultravisor-beacon-section-header">
322
+ <h2>Affinity Bindings</h2>
323
+ </div>
324
+ <div id="Ultravisor-BeaconList-Affinity"></div>
325
+ </div>
326
+ </div>
327
+ `
328
+ }
329
+ ],
330
+
331
+ Renderables:
332
+ [
333
+ {
334
+ RenderableHash: "Ultravisor-BeaconList-Content",
335
+ TemplateHash: "Ultravisor-BeaconList-Template",
336
+ DestinationAddress: "#Ultravisor-Content-Container",
337
+ RenderMethod: "replace"
338
+ }
339
+ ]
340
+ };
341
+
342
+ class UltravisorBeaconListView extends libPictView
343
+ {
344
+ constructor(pFable, pOptions, pServiceHash)
345
+ {
346
+ super(pFable, pOptions, pServiceHash);
347
+
348
+ this._refreshTimer = null;
349
+ }
350
+
351
+ onBeforeRender(pRenderable, pRenderDestinationAddress, pRecord)
352
+ {
353
+ // Stop auto-refresh when leaving this view
354
+ if (this._refreshTimer)
355
+ {
356
+ clearInterval(this._refreshTimer);
357
+ this._refreshTimer = null;
358
+ }
359
+
360
+ return super.onBeforeRender(pRenderable, pRenderDestinationAddress, pRecord);
361
+ }
362
+
363
+ onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
364
+ {
365
+ this.refreshAll();
366
+
367
+ // Start auto-refresh every 10 seconds
368
+ if (this._refreshTimer)
369
+ {
370
+ clearInterval(this._refreshTimer);
371
+ }
372
+ this._refreshTimer = setInterval(
373
+ function ()
374
+ {
375
+ this._silentRefresh();
376
+ }.bind(this), 10000);
377
+
378
+ return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
379
+ }
380
+
381
+ refreshAll()
382
+ {
383
+ let tmpPendingCalls = 3;
384
+ let tmpDone = function ()
385
+ {
386
+ tmpPendingCalls--;
387
+ if (tmpPendingCalls <= 0)
388
+ {
389
+ this.renderBeaconSummary();
390
+ this.renderBeaconTable();
391
+ this.renderWorkQueue();
392
+ this.renderAffinityTable();
393
+ }
394
+ }.bind(this);
395
+
396
+ this.pict.PictApplication.loadBeacons(tmpDone);
397
+ this.pict.PictApplication.loadWorkItems(tmpDone);
398
+ this.pict.PictApplication.loadAffinityBindings(tmpDone);
399
+ }
400
+
401
+ _silentRefresh()
402
+ {
403
+ // Same as refreshAll but doesn't clear the detail panel
404
+ let tmpPendingCalls = 3;
405
+ let tmpDone = function ()
406
+ {
407
+ tmpPendingCalls--;
408
+ if (tmpPendingCalls <= 0)
409
+ {
410
+ this.renderBeaconSummary();
411
+ this.renderBeaconTable();
412
+ this.renderWorkQueue();
413
+ this.renderAffinityTable();
414
+ }
415
+ }.bind(this);
416
+
417
+ this.pict.PictApplication.loadBeacons(tmpDone);
418
+ this.pict.PictApplication.loadWorkItems(tmpDone);
419
+ this.pict.PictApplication.loadAffinityBindings(tmpDone);
420
+ }
421
+
422
+ renderBeaconSummary()
423
+ {
424
+ let tmpBeacons = this.pict.AppData.Ultravisor.Beacons || [];
425
+ let tmpOnline = 0;
426
+ let tmpBusy = 0;
427
+ let tmpOffline = 0;
428
+
429
+ for (let i = 0; i < tmpBeacons.length; i++)
430
+ {
431
+ let tmpStatus = (tmpBeacons[i].Status || '').toLowerCase();
432
+ if (tmpStatus === 'online')
433
+ {
434
+ tmpOnline++;
435
+ }
436
+ else if (tmpStatus === 'busy')
437
+ {
438
+ tmpBusy++;
439
+ }
440
+ else
441
+ {
442
+ tmpOffline++;
443
+ }
444
+ }
445
+
446
+ let tmpHTML = '<div class="ultravisor-beacon-summary">';
447
+ tmpHTML += '<div class="ultravisor-beacon-summary-card"><div class="ultravisor-beacon-summary-count">' + tmpBeacons.length + '</div><div class="ultravisor-beacon-summary-label">Total</div></div>';
448
+ tmpHTML += '<div class="ultravisor-beacon-summary-card online"><div class="ultravisor-beacon-summary-count">' + tmpOnline + '</div><div class="ultravisor-beacon-summary-label">Online</div></div>';
449
+ tmpHTML += '<div class="ultravisor-beacon-summary-card busy"><div class="ultravisor-beacon-summary-count">' + tmpBusy + '</div><div class="ultravisor-beacon-summary-label">Busy</div></div>';
450
+ tmpHTML += '<div class="ultravisor-beacon-summary-card offline"><div class="ultravisor-beacon-summary-count">' + tmpOffline + '</div><div class="ultravisor-beacon-summary-label">Offline</div></div>';
451
+ tmpHTML += '</div>';
452
+
453
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Summary', tmpHTML);
454
+ }
455
+
456
+ renderBeaconTable()
457
+ {
458
+ let tmpBeacons = this.pict.AppData.Ultravisor.Beacons || [];
459
+ let tmpViewRef = "_Pict.views['Ultravisor-BeaconList']";
460
+
461
+ if (tmpBeacons.length === 0)
462
+ {
463
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Body',
464
+ '<div class="ultravisor-beacon-empty">No beacons connected. Start a beacon-enabled application to see it appear here.</div>');
465
+ return;
466
+ }
467
+
468
+ let tmpHTML = '<table class="ultravisor-beacon-table">';
469
+ tmpHTML += '<thead><tr>';
470
+ tmpHTML += '<th>Name</th>';
471
+ tmpHTML += '<th>Status</th>';
472
+ tmpHTML += '<th>Capabilities</th>';
473
+ tmpHTML += '<th>Work Items</th>';
474
+ tmpHTML += '<th>Last Heartbeat</th>';
475
+ tmpHTML += '<th>Registered</th>';
476
+ tmpHTML += '<th>Actions</th>';
477
+ tmpHTML += '</tr></thead>';
478
+ tmpHTML += '<tbody>';
479
+
480
+ for (let i = 0; i < tmpBeacons.length; i++)
481
+ {
482
+ let tmpBeacon = tmpBeacons[i];
483
+ let tmpStatusClass = (tmpBeacon.Status || 'offline').toLowerCase();
484
+ if (tmpStatusClass !== 'online' && tmpStatusClass !== 'busy' && tmpStatusClass !== 'offline')
485
+ {
486
+ tmpStatusClass = 'offline';
487
+ }
488
+ let tmpEscID = this._escapeAttr(tmpBeacon.BeaconID || '');
489
+
490
+ // Capability tags
491
+ let tmpCaps = tmpBeacon.Capabilities || [];
492
+ let tmpCapsHTML = '';
493
+ for (let c = 0; c < tmpCaps.length; c++)
494
+ {
495
+ tmpCapsHTML += '<span class="ultravisor-beacon-cap-tag">' + this._escapeHTML(tmpCaps[c]) + '</span>';
496
+ }
497
+ if (tmpCaps.length === 0)
498
+ {
499
+ tmpCapsHTML = '<span style="color:var(--uv-text-tertiary);">none</span>';
500
+ }
501
+
502
+ // Work item count
503
+ let tmpWorkCount = (tmpBeacon.CurrentWorkItems || []).length;
504
+
505
+ tmpHTML += '<tr>';
506
+ tmpHTML += '<td><strong>' + this._escapeHTML(tmpBeacon.Name || tmpBeacon.BeaconID || '') + '</strong></td>';
507
+ tmpHTML += '<td><span class="ultravisor-beacon-status ' + tmpStatusClass + '"><span class="ultravisor-beacon-status-dot"></span>' + this._escapeHTML(tmpBeacon.Status || 'Offline') + '</span></td>';
508
+ tmpHTML += '<td>' + tmpCapsHTML + '</td>';
509
+ tmpHTML += '<td>' + tmpWorkCount + ' / ' + (tmpBeacon.MaxConcurrent || 1) + '</td>';
510
+ tmpHTML += '<td>' + this._formatRelativeTime(tmpBeacon.LastHeartbeat) + '</td>';
511
+ tmpHTML += '<td>' + this._formatRelativeTime(tmpBeacon.RegisteredAt) + '</td>';
512
+ tmpHTML += '<td>';
513
+ tmpHTML += '<button class="ultravisor-btn-sm ultravisor-btn-edit" onclick="' + tmpViewRef + '.showBeaconDetail(\'' + tmpEscID + '\')">Details</button> ';
514
+ tmpHTML += '<button class="ultravisor-btn-sm ultravisor-btn-danger" onclick="' + tmpViewRef + '.deregisterBeacon(\'' + tmpEscID + '\')">Deregister</button>';
515
+ tmpHTML += '</td>';
516
+ tmpHTML += '</tr>';
517
+ }
518
+
519
+ tmpHTML += '</tbody></table>';
520
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Body', tmpHTML);
521
+ }
522
+
523
+ showBeaconDetail(pBeaconID)
524
+ {
525
+ this.pict.PictApplication.loadBeacon(pBeaconID,
526
+ function (pError, pBeacon)
527
+ {
528
+ if (pError || !pBeacon)
529
+ {
530
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Detail',
531
+ '<div class="ultravisor-beacon-detail visible"><p style="color:var(--uv-error);">Error loading beacon details.</p></div>');
532
+ return;
533
+ }
534
+
535
+ let tmpHTML = '<div class="ultravisor-beacon-detail visible">';
536
+ tmpHTML += '<h3>Beacon: ' + this._escapeHTML(pBeacon.Name || pBeacon.BeaconID || '') + '</h3>';
537
+
538
+ // Detail grid
539
+ tmpHTML += '<div class="ultravisor-beacon-detail-grid">';
540
+ tmpHTML += this._renderDetailField('Beacon ID', '<code>' + this._escapeHTML(pBeacon.BeaconID || '') + '</code>');
541
+ tmpHTML += this._renderDetailField('Name', this._escapeHTML(pBeacon.Name || ''));
542
+ tmpHTML += this._renderDetailField('Status', this._escapeHTML(pBeacon.Status || 'Unknown'));
543
+ tmpHTML += this._renderDetailField('Max Concurrent', String(pBeacon.MaxConcurrent || 1));
544
+ tmpHTML += this._renderDetailField('Session ID', '<code>' + this._escapeHTML(pBeacon.SessionID || 'none') + '</code>');
545
+ tmpHTML += this._renderDetailField('Registered', this._escapeHTML(pBeacon.RegisteredAt || ''));
546
+ tmpHTML += this._renderDetailField('Last Heartbeat', this._escapeHTML(pBeacon.LastHeartbeat || '') + ' (' + this._formatRelativeTime(pBeacon.LastHeartbeat) + ')');
547
+ tmpHTML += '</div>';
548
+
549
+ // Capabilities
550
+ let tmpCaps = pBeacon.Capabilities || [];
551
+ if (tmpCaps.length > 0)
552
+ {
553
+ tmpHTML += '<h3>Capabilities</h3>';
554
+ tmpHTML += '<div style="margin-bottom:1em;">';
555
+ for (let c = 0; c < tmpCaps.length; c++)
556
+ {
557
+ tmpHTML += '<span class="ultravisor-beacon-cap-tag" style="font-size:0.85em;">' + this._escapeHTML(tmpCaps[c]) + '</span>';
558
+ }
559
+ tmpHTML += '</div>';
560
+ }
561
+
562
+ // Current work items
563
+ let tmpWorkItems = pBeacon.CurrentWorkItems || [];
564
+ if (tmpWorkItems.length > 0)
565
+ {
566
+ tmpHTML += '<h3>Current Work Items</h3>';
567
+ tmpHTML += '<div style="font-family:monospace; font-size:0.85em; color:var(--uv-text-secondary);">';
568
+ for (let w = 0; w < tmpWorkItems.length; w++)
569
+ {
570
+ tmpHTML += '<div>' + this._escapeHTML(tmpWorkItems[w]) + '</div>';
571
+ }
572
+ tmpHTML += '</div>';
573
+ }
574
+
575
+ // Tags
576
+ let tmpTags = pBeacon.Tags || {};
577
+ let tmpTagKeys = Object.keys(tmpTags);
578
+ if (tmpTagKeys.length > 0)
579
+ {
580
+ tmpHTML += '<h3>Tags</h3>';
581
+ tmpHTML += '<div class="ultravisor-beacon-detail-grid">';
582
+ for (let t = 0; t < tmpTagKeys.length; t++)
583
+ {
584
+ tmpHTML += this._renderDetailField(this._escapeHTML(tmpTagKeys[t]), this._escapeHTML(String(tmpTags[tmpTagKeys[t]])));
585
+ }
586
+ tmpHTML += '</div>';
587
+ }
588
+
589
+ tmpHTML += '</div>';
590
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Detail', tmpHTML);
591
+ }.bind(this));
592
+ }
593
+
594
+ deregisterBeacon(pBeaconID)
595
+ {
596
+ if (!confirm('Deregister beacon ' + pBeaconID + '? Any assigned work items will be released.'))
597
+ {
598
+ return;
599
+ }
600
+
601
+ this.pict.PictApplication.deregisterBeacon(pBeaconID,
602
+ function (pError)
603
+ {
604
+ if (pError)
605
+ {
606
+ this.pict.log.error('Failed to deregister beacon: ' + pBeaconID, pError);
607
+ }
608
+ // Clear detail panel and refresh
609
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Detail', '');
610
+ this.refreshAll();
611
+ }.bind(this));
612
+ }
613
+
614
+ renderWorkQueue()
615
+ {
616
+ let tmpWorkItems = this.pict.AppData.Ultravisor.WorkItems || [];
617
+
618
+ if (tmpWorkItems.length === 0)
619
+ {
620
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-WorkQueue',
621
+ '<div class="ultravisor-beacon-empty" style="padding:1.5em;">No work items in the queue.</div>');
622
+ return;
623
+ }
624
+
625
+ let tmpHTML = '<table class="ultravisor-beacon-table">';
626
+ tmpHTML += '<thead><tr>';
627
+ tmpHTML += '<th>Hash</th>';
628
+ tmpHTML += '<th>Status</th>';
629
+ tmpHTML += '<th>Capability</th>';
630
+ tmpHTML += '<th>Action</th>';
631
+ tmpHTML += '<th>Beacon</th>';
632
+ tmpHTML += '<th>Progress</th>';
633
+ tmpHTML += '<th>Created</th>';
634
+ tmpHTML += '<th>Completed</th>';
635
+ tmpHTML += '</tr></thead>';
636
+ tmpHTML += '<tbody>';
637
+
638
+ for (let i = 0; i < tmpWorkItems.length; i++)
639
+ {
640
+ let tmpItem = tmpWorkItems[i];
641
+ let tmpStatusClass = (tmpItem.Status || '').toLowerCase();
642
+ if (['pending', 'assigned', 'running', 'complete', 'error', 'timeout'].indexOf(tmpStatusClass) === -1)
643
+ {
644
+ tmpStatusClass = '';
645
+ }
646
+
647
+ // Progress
648
+ let tmpProgressHTML = '';
649
+ let tmpProgress = tmpItem.Progress || {};
650
+ if (tmpItem.Status === 'Running' && typeof tmpProgress.Percent === 'number')
651
+ {
652
+ tmpProgressHTML = '<div class="ultravisor-workitem-progress">';
653
+ tmpProgressHTML += '<div class="ultravisor-workitem-progress-bar"><div class="ultravisor-workitem-progress-fill" style="width:' + Math.min(tmpProgress.Percent, 100) + '%;"></div></div>';
654
+ tmpProgressHTML += '<span class="ultravisor-workitem-progress-text">' + Math.round(tmpProgress.Percent) + '%</span>';
655
+ tmpProgressHTML += '</div>';
656
+ }
657
+ else if (tmpProgress.Message)
658
+ {
659
+ tmpProgressHTML = '<span class="ultravisor-workitem-progress-text">' + this._escapeHTML(tmpProgress.Message) + '</span>';
660
+ }
661
+
662
+ tmpHTML += '<tr>';
663
+ tmpHTML += '<td><code style="font-size:0.75em;">' + this._escapeHTML((tmpItem.WorkItemHash || '').substring(0, 20)) + '</code></td>';
664
+ tmpHTML += '<td><span class="ultravisor-workitem-status ' + tmpStatusClass + '">' + this._escapeHTML(tmpItem.Status || '') + '</span></td>';
665
+ tmpHTML += '<td>' + this._escapeHTML(tmpItem.Capability || '') + '</td>';
666
+ tmpHTML += '<td>' + this._escapeHTML(tmpItem.Action || '') + '</td>';
667
+ tmpHTML += '<td>' + this._escapeHTML(tmpItem.AssignedBeaconID ? tmpItem.AssignedBeaconID.substring(0, 15) : 'unassigned') + '</td>';
668
+ tmpHTML += '<td>' + tmpProgressHTML + '</td>';
669
+ tmpHTML += '<td>' + this._formatRelativeTime(tmpItem.CreatedAt) + '</td>';
670
+ tmpHTML += '<td>' + (tmpItem.CompletedAt ? this._formatRelativeTime(tmpItem.CompletedAt) : '') + '</td>';
671
+ tmpHTML += '</tr>';
672
+ }
673
+
674
+ tmpHTML += '</tbody></table>';
675
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-WorkQueue', tmpHTML);
676
+ }
677
+
678
+ renderAffinityTable()
679
+ {
680
+ let tmpBindings = this.pict.AppData.Ultravisor.AffinityBindings || [];
681
+
682
+ if (tmpBindings.length === 0)
683
+ {
684
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Affinity',
685
+ '<div class="ultravisor-beacon-empty" style="padding:1.5em;">No active affinity bindings.</div>');
686
+ return;
687
+ }
688
+
689
+ let tmpHTML = '<table class="ultravisor-beacon-table">';
690
+ tmpHTML += '<thead><tr>';
691
+ tmpHTML += '<th>Affinity Key</th>';
692
+ tmpHTML += '<th>Beacon</th>';
693
+ tmpHTML += '<th>Run Hash</th>';
694
+ tmpHTML += '<th>Created</th>';
695
+ tmpHTML += '<th>Expires</th>';
696
+ tmpHTML += '</tr></thead>';
697
+ tmpHTML += '<tbody>';
698
+
699
+ for (let i = 0; i < tmpBindings.length; i++)
700
+ {
701
+ let tmpBinding = tmpBindings[i];
702
+ tmpHTML += '<tr>';
703
+ tmpHTML += '<td><code>' + this._escapeHTML(tmpBinding.AffinityKey || '') + '</code></td>';
704
+ tmpHTML += '<td>' + this._escapeHTML(tmpBinding.BeaconID || '') + '</td>';
705
+ tmpHTML += '<td><code style="font-size:0.8em;">' + this._escapeHTML(tmpBinding.RunHash || '') + '</code></td>';
706
+ tmpHTML += '<td>' + this._formatRelativeTime(tmpBinding.CreatedAt) + '</td>';
707
+ tmpHTML += '<td>' + this._formatRelativeTime(tmpBinding.ExpiresAt) + '</td>';
708
+ tmpHTML += '</tr>';
709
+ }
710
+
711
+ tmpHTML += '</tbody></table>';
712
+ this.pict.ContentAssignment.assignContent('#Ultravisor-BeaconList-Affinity', tmpHTML);
713
+ }
714
+
715
+ // --- Helpers ---
716
+
717
+ _renderDetailField(pLabel, pValue)
718
+ {
719
+ return '<div class="ultravisor-beacon-detail-field">'
720
+ + '<div class="ultravisor-beacon-detail-field-label">' + pLabel + '</div>'
721
+ + '<div class="ultravisor-beacon-detail-field-value">' + pValue + '</div>'
722
+ + '</div>';
723
+ }
724
+
725
+ _formatRelativeTime(pISO)
726
+ {
727
+ if (!pISO)
728
+ {
729
+ return '<span style="color:var(--uv-text-tertiary);">—</span>';
730
+ }
731
+
732
+ let tmpDate;
733
+ try
734
+ {
735
+ tmpDate = new Date(pISO);
736
+ }
737
+ catch (pError)
738
+ {
739
+ return this._escapeHTML(String(pISO));
740
+ }
741
+
742
+ if (isNaN(tmpDate.getTime()))
743
+ {
744
+ return this._escapeHTML(String(pISO));
745
+ }
746
+
747
+ let tmpNow = Date.now();
748
+ let tmpDiffMs = tmpNow - tmpDate.getTime();
749
+ let tmpFuture = tmpDiffMs < 0;
750
+ let tmpAbsDiff = Math.abs(tmpDiffMs);
751
+
752
+ let tmpSeconds = Math.floor(tmpAbsDiff / 1000);
753
+ let tmpMinutes = Math.floor(tmpSeconds / 60);
754
+ let tmpHours = Math.floor(tmpMinutes / 60);
755
+ let tmpDays = Math.floor(tmpHours / 24);
756
+
757
+ let tmpText;
758
+ if (tmpSeconds < 10)
759
+ {
760
+ tmpText = 'just now';
761
+ }
762
+ else if (tmpSeconds < 60)
763
+ {
764
+ tmpText = tmpSeconds + 's ago';
765
+ }
766
+ else if (tmpMinutes < 60)
767
+ {
768
+ tmpText = tmpMinutes + 'm ago';
769
+ }
770
+ else if (tmpHours < 24)
771
+ {
772
+ tmpText = tmpHours + 'h ago';
773
+ }
774
+ else
775
+ {
776
+ tmpText = tmpDays + 'd ago';
777
+ }
778
+
779
+ if (tmpFuture && tmpText !== 'just now')
780
+ {
781
+ tmpText = 'in ' + tmpText.replace(' ago', '');
782
+ }
783
+
784
+ return '<span title="' + this._escapeAttr(pISO) + '">' + tmpText + '</span>';
785
+ }
786
+
787
+ _escapeHTML(pString)
788
+ {
789
+ if (typeof pString !== 'string')
790
+ {
791
+ return '';
792
+ }
793
+ return pString
794
+ .replace(/&/g, '&amp;')
795
+ .replace(/</g, '&lt;')
796
+ .replace(/>/g, '&gt;')
797
+ .replace(/"/g, '&quot;');
798
+ }
799
+
800
+ _escapeAttr(pString)
801
+ {
802
+ if (typeof pString !== 'string')
803
+ {
804
+ return '';
805
+ }
806
+ return pString
807
+ .replace(/&/g, '&amp;')
808
+ .replace(/'/g, '&#39;')
809
+ .replace(/"/g, '&quot;')
810
+ .replace(/</g, '&lt;')
811
+ .replace(/>/g, '&gt;');
812
+ }
813
+ }
814
+
815
+ module.exports = UltravisorBeaconListView;
816
+
817
+ module.exports.default_configuration = _ViewConfiguration;