ultravisor 1.0.0 → 1.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.
Files changed (96) hide show
  1. package/.babelrc +6 -0
  2. package/.browserslistrc +1 -0
  3. package/.browserslistrc-BACKUP +1 -0
  4. package/.gulpfile-quackage-config.json +7 -0
  5. package/.gulpfile-quackage.js +2 -0
  6. package/CONTRIBUTING.md +50 -0
  7. package/README.md +34 -0
  8. package/debug/Harness.js +2 -1
  9. package/docs/.nojekyll +0 -0
  10. package/docs/_sidebar.md +18 -0
  11. package/docs/_topbar.md +7 -0
  12. package/docs/architecture.md +103 -0
  13. package/docs/cover.md +15 -0
  14. package/docs/features/api.md +230 -0
  15. package/docs/features/cli.md +182 -0
  16. package/docs/features/configuration.md +245 -0
  17. package/docs/features/manifests.md +177 -0
  18. package/docs/features/operations.md +292 -0
  19. package/docs/features/scheduling.md +179 -0
  20. package/docs/features/tasks.md +1857 -0
  21. package/docs/index.html +39 -0
  22. package/docs/overview.md +75 -0
  23. package/docs/quickstart.md +167 -0
  24. package/docs/retold-catalog.json +24 -0
  25. package/docs/retold-keyword-index.json +19 -0
  26. package/package.json +5 -2
  27. package/source/Ultravisor.cjs +2 -2
  28. package/source/cli/Ultravisor-CLIProgram.cjs +38 -0
  29. package/source/cli/commands/Ultravisor-Command-ScheduleOperation.cjs +26 -2
  30. package/source/cli/commands/Ultravisor-Command-ScheduleTask.cjs +26 -2
  31. package/source/cli/commands/Ultravisor-Command-ScheduleView.cjs +22 -0
  32. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +49 -1
  33. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +51 -1
  34. package/source/cli/commands/Ultravisor-Command-Stop.cjs +4 -0
  35. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +91 -0
  36. package/source/config/Ultravisor-Default-Command-Configuration.cjs +6 -1
  37. package/source/services/Ultravisor-Hypervisor-Event-Base.cjs +18 -1
  38. package/source/services/Ultravisor-Hypervisor-State.cjs +213 -0
  39. package/source/services/Ultravisor-Hypervisor.cjs +225 -1
  40. package/source/services/Ultravisor-Operation-Manifest.cjs +150 -1
  41. package/source/services/Ultravisor-Operation.cjs +190 -1
  42. package/source/services/Ultravisor-Task.cjs +339 -1
  43. package/source/services/events/Ultravisor-Hypervisor-Event-Cron.cjs +71 -1
  44. package/source/services/tasks/Ultravisor-Task-Base.cjs +264 -0
  45. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +188 -0
  46. package/source/services/tasks/Ultravisor-Task-Command.cjs +65 -0
  47. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +190 -0
  48. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +104 -0
  49. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +72 -0
  50. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +336 -0
  51. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +143 -0
  52. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +146 -0
  53. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +158 -0
  54. package/source/services/tasks/Ultravisor-Task-Request.cjs +56 -0
  55. package/source/services/tasks/Ultravisor-Task-Solver.cjs +89 -0
  56. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +93 -0
  57. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +127 -0
  58. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +119 -0
  59. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +109 -0
  60. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +112 -0
  61. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +499 -0
  62. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +150 -0
  63. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +110 -0
  64. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +89 -0
  65. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +87 -0
  66. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +67 -0
  67. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +66 -0
  68. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +69 -0
  69. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +95 -0
  70. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +96 -0
  71. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +99 -0
  72. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +102 -0
  73. package/source/web_server/Ultravisor-API-Server.cjs +463 -3
  74. package/test/Ultravisor_tests.js +6097 -1
  75. package/webinterface/.babelrc +6 -0
  76. package/webinterface/.browserslistrc +1 -0
  77. package/webinterface/.browserslistrc-BACKUP +1 -0
  78. package/webinterface/.gulpfile-quackage-config.json +7 -0
  79. package/webinterface/.gulpfile-quackage.js +2 -0
  80. package/webinterface/css/ultravisor.css +121 -0
  81. package/webinterface/html/index.html +32 -0
  82. package/webinterface/package.json +39 -0
  83. package/webinterface/source/Pict-Application-Ultravisor-Configuration.json +15 -0
  84. package/webinterface/source/Pict-Application-Ultravisor.js +414 -0
  85. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +42 -0
  86. package/webinterface/source/views/PictView-Ultravisor-BottomBar.js +65 -0
  87. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +236 -0
  88. package/webinterface/source/views/PictView-Ultravisor-Layout.js +83 -0
  89. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +273 -0
  90. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +243 -0
  91. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +141 -0
  92. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +280 -0
  93. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +220 -0
  94. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +248 -0
  95. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +420 -0
  96. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +147 -0
@@ -0,0 +1,99 @@
1
+ const libUltravisorTaskBase = require('../Ultravisor-Task-Base.cjs');
2
+
3
+ const libFS = require('fs');
4
+ const libPath = require('path');
5
+
6
+ class UltravisorTaskWriteText extends libUltravisorTaskBase
7
+ {
8
+ constructor(pFable)
9
+ {
10
+ super(pFable);
11
+ }
12
+
13
+ /**
14
+ * Write text content to a file in the staging folder.
15
+ *
16
+ * Task definition fields:
17
+ * - File: relative file path inside the staging folder
18
+ * - Address (optional): dot-notation path into GlobalState to
19
+ * resolve the data to write (used instead of Data)
20
+ * - Data: the string to write
21
+ *
22
+ * Either Address or Data must be provided. When Address is set,
23
+ * the data is resolved from pContext.GlobalState (or NodeState).
24
+ */
25
+ execute(pTaskDefinition, pContext, pManifestEntry, fCallback)
26
+ {
27
+ let tmpStagingPath = this.resolveStagingPath(pContext);
28
+ let tmpFilePath = this.resolveStagingFilePath(tmpStagingPath, pTaskDefinition.File);
29
+
30
+ if (!tmpFilePath)
31
+ {
32
+ pManifestEntry.StopTime = new Date().toISOString();
33
+ pManifestEntry.Status = 'Error';
34
+ pManifestEntry.Log.push(`WriteText: missing or invalid File field.`);
35
+ return fCallback(null, pManifestEntry);
36
+ }
37
+
38
+ // Resolve data from Address or Data
39
+ let tmpData;
40
+
41
+ if (pTaskDefinition.Address && typeof(pTaskDefinition.Address) === 'string')
42
+ {
43
+ tmpData = this.resolveAddress(pTaskDefinition.Address, pContext);
44
+ pManifestEntry.Log.push(`WriteText: resolved data from Address "${pTaskDefinition.Address}".`);
45
+ }
46
+ else if (pTaskDefinition.hasOwnProperty('Data'))
47
+ {
48
+ tmpData = pTaskDefinition.Data;
49
+ }
50
+ else
51
+ {
52
+ pManifestEntry.StopTime = new Date().toISOString();
53
+ pManifestEntry.Status = 'Error';
54
+ pManifestEntry.Log.push(`WriteText: missing Data or Address field.`);
55
+ return fCallback(null, pManifestEntry);
56
+ }
57
+
58
+ if (tmpData === undefined)
59
+ {
60
+ pManifestEntry.StopTime = new Date().toISOString();
61
+ pManifestEntry.Status = 'Error';
62
+ pManifestEntry.Log.push(`WriteText: resolved data is undefined.`);
63
+ return fCallback(null, pManifestEntry);
64
+ }
65
+
66
+ let tmpContent = (typeof(tmpData) === 'string')
67
+ ? tmpData
68
+ : String(tmpData);
69
+
70
+ pManifestEntry.Log.push(`WriteText: writing to ${tmpFilePath}`);
71
+
72
+ try
73
+ {
74
+ let tmpDir = libPath.dirname(tmpFilePath);
75
+ if (!libFS.existsSync(tmpDir))
76
+ {
77
+ libFS.mkdirSync(tmpDir, { recursive: true });
78
+ }
79
+
80
+ libFS.writeFileSync(tmpFilePath, tmpContent, 'utf8');
81
+
82
+ pManifestEntry.StopTime = new Date().toISOString();
83
+ pManifestEntry.Status = 'Complete';
84
+ pManifestEntry.Success = true;
85
+ pManifestEntry.Output = `${tmpContent.length} bytes written`;
86
+ pManifestEntry.Log.push(`WriteText: wrote ${tmpContent.length} bytes.`);
87
+ }
88
+ catch (pError)
89
+ {
90
+ pManifestEntry.StopTime = new Date().toISOString();
91
+ pManifestEntry.Status = 'Error';
92
+ pManifestEntry.Log.push(`WriteText: ${pError.message}`);
93
+ }
94
+
95
+ return fCallback(null, pManifestEntry);
96
+ }
97
+ }
98
+
99
+ module.exports = UltravisorTaskWriteText;
@@ -0,0 +1,102 @@
1
+ const libUltravisorTaskBase = require('../Ultravisor-Task-Base.cjs');
2
+
3
+ const libFS = require('fs');
4
+ const libPath = require('path');
5
+
6
+ class UltravisorTaskWriteXML extends libUltravisorTaskBase
7
+ {
8
+ constructor(pFable)
9
+ {
10
+ super(pFable);
11
+ }
12
+
13
+ /**
14
+ * Write XML content to a file in the staging folder.
15
+ *
16
+ * The Data field should be a string containing well-formed XML.
17
+ * Creates intermediate directories automatically.
18
+ *
19
+ * Task definition fields:
20
+ * - File: relative file path inside the staging folder
21
+ * - Address (optional): dot-notation path into GlobalState to
22
+ * resolve the data to write (used instead of Data)
23
+ * - Data: XML string to write
24
+ *
25
+ * Either Address or Data must be provided. When Address is set,
26
+ * the data is resolved from pContext.GlobalState (or NodeState).
27
+ */
28
+ execute(pTaskDefinition, pContext, pManifestEntry, fCallback)
29
+ {
30
+ let tmpStagingPath = this.resolveStagingPath(pContext);
31
+ let tmpFilePath = this.resolveStagingFilePath(tmpStagingPath, pTaskDefinition.File);
32
+
33
+ if (!tmpFilePath)
34
+ {
35
+ pManifestEntry.StopTime = new Date().toISOString();
36
+ pManifestEntry.Status = 'Error';
37
+ pManifestEntry.Log.push(`WriteXML: missing or invalid File field.`);
38
+ return fCallback(null, pManifestEntry);
39
+ }
40
+
41
+ // Resolve data from Address or Data
42
+ let tmpData;
43
+
44
+ if (pTaskDefinition.Address && typeof(pTaskDefinition.Address) === 'string')
45
+ {
46
+ tmpData = this.resolveAddress(pTaskDefinition.Address, pContext);
47
+ pManifestEntry.Log.push(`WriteXML: resolved data from Address "${pTaskDefinition.Address}".`);
48
+ }
49
+ else if (pTaskDefinition.hasOwnProperty('Data'))
50
+ {
51
+ tmpData = pTaskDefinition.Data;
52
+ }
53
+ else
54
+ {
55
+ pManifestEntry.StopTime = new Date().toISOString();
56
+ pManifestEntry.Status = 'Error';
57
+ pManifestEntry.Log.push(`WriteXML: missing Data or Address field.`);
58
+ return fCallback(null, pManifestEntry);
59
+ }
60
+
61
+ if (tmpData === undefined)
62
+ {
63
+ pManifestEntry.StopTime = new Date().toISOString();
64
+ pManifestEntry.Status = 'Error';
65
+ pManifestEntry.Log.push(`WriteXML: resolved data is undefined.`);
66
+ return fCallback(null, pManifestEntry);
67
+ }
68
+
69
+ let tmpContent = (typeof(tmpData) === 'string')
70
+ ? tmpData
71
+ : String(tmpData);
72
+
73
+ pManifestEntry.Log.push(`WriteXML: writing to ${tmpFilePath}`);
74
+
75
+ try
76
+ {
77
+ let tmpDir = libPath.dirname(tmpFilePath);
78
+ if (!libFS.existsSync(tmpDir))
79
+ {
80
+ libFS.mkdirSync(tmpDir, { recursive: true });
81
+ }
82
+
83
+ libFS.writeFileSync(tmpFilePath, tmpContent, 'utf8');
84
+
85
+ pManifestEntry.StopTime = new Date().toISOString();
86
+ pManifestEntry.Status = 'Complete';
87
+ pManifestEntry.Success = true;
88
+ pManifestEntry.Output = `${tmpContent.length} bytes written`;
89
+ pManifestEntry.Log.push(`WriteXML: wrote ${tmpContent.length} bytes.`);
90
+ }
91
+ catch (pError)
92
+ {
93
+ pManifestEntry.StopTime = new Date().toISOString();
94
+ pManifestEntry.Status = 'Error';
95
+ pManifestEntry.Log.push(`WriteXML: ${pError.message}`);
96
+ }
97
+
98
+ return fCallback(null, pManifestEntry);
99
+ }
100
+ }
101
+
102
+ module.exports = UltravisorTaskWriteXML;
@@ -1,5 +1,6 @@
1
1
  const libPictService = require(`pict-serviceproviderbase`);
2
2
 
3
+ const libPath = require('path');
3
4
  const libOrator = require('orator');
4
5
  const libOratorServiceServerRestify = require(`orator-serviceserver-restify`);
5
6
 
@@ -24,15 +25,440 @@ class UltravisorAPIServer extends libPictService
24
25
  return fCallback(new Error(`Ultravisor API Server: Cannot wire endpoints; Orator service is not initialized.`));
25
26
  }
26
27
 
28
+ // --- Package / Status ---
27
29
  this._OratorServer.get
28
30
  (
29
31
  '/package',
30
- (pRequest, pResponse, fNext) =>
32
+ function (pRequest, pResponse, fNext)
31
33
  {
32
- // Send back the request parameters
33
34
  pResponse.send(this.pict.settings.Package);
34
35
  return fNext();
35
- }
36
+ }.bind(this)
37
+ );
38
+
39
+ this._OratorServer.get
40
+ (
41
+ '/status',
42
+ function (pRequest, pResponse, fNext)
43
+ {
44
+ let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
45
+ pResponse.send({
46
+ Status: 'Running',
47
+ ScheduleEntries: tmpHypervisor.getSchedule().length,
48
+ ScheduleRunning: tmpHypervisor._Running
49
+ });
50
+ return fNext();
51
+ }.bind(this)
52
+ );
53
+
54
+ this._OratorServer.get
55
+ (
56
+ '/stop',
57
+ function (pRequest, pResponse, fNext)
58
+ {
59
+ this.log.info(`Ultravisor API Server: Received stop request via API; stopping server.`);
60
+ let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
61
+ tmpHypervisor.stopSchedule();
62
+ pResponse.send({ "Status": "STOPPING" });
63
+ pResponse.end();
64
+ return this._Orator.stopService(fNext);
65
+ }.bind(this)
66
+ );
67
+
68
+ // --- Task CRUD ---
69
+ this._OratorServer.get
70
+ (
71
+ '/Task',
72
+ function (pRequest, pResponse, fNext)
73
+ {
74
+ this.fable['Ultravisor-Hypervisor-State'].getTaskList({},
75
+ function (pError, pTasks)
76
+ {
77
+ if (pError)
78
+ {
79
+ pResponse.send(500, { Error: pError.message });
80
+ return fNext();
81
+ }
82
+ pResponse.send(pTasks);
83
+ return fNext();
84
+ });
85
+ }.bind(this)
86
+ );
87
+
88
+ this._OratorServer.get
89
+ (
90
+ '/Task/:GUIDTask',
91
+ function (pRequest, pResponse, fNext)
92
+ {
93
+ this.fable['Ultravisor-Hypervisor-State'].getTask(pRequest.params.GUIDTask,
94
+ function (pError, pTask)
95
+ {
96
+ if (pError)
97
+ {
98
+ pResponse.send(404, { Error: pError.message });
99
+ return fNext();
100
+ }
101
+ pResponse.send(pTask);
102
+ return fNext();
103
+ });
104
+ }.bind(this)
105
+ );
106
+
107
+ this._OratorServer.post
108
+ (
109
+ '/Task',
110
+ function (pRequest, pResponse, fNext)
111
+ {
112
+ this.fable['Ultravisor-Hypervisor-State'].updateTask(pRequest.body,
113
+ function (pError, pTask)
114
+ {
115
+ if (pError)
116
+ {
117
+ pResponse.send(400, { Error: pError.message });
118
+ return fNext();
119
+ }
120
+ pResponse.send(pTask);
121
+ return fNext();
122
+ });
123
+ }.bind(this)
124
+ );
125
+
126
+ this._OratorServer.put
127
+ (
128
+ '/Task/:GUIDTask',
129
+ function (pRequest, pResponse, fNext)
130
+ {
131
+ let tmpTaskData = pRequest.body || {};
132
+ tmpTaskData.GUIDTask = pRequest.params.GUIDTask;
133
+ this.fable['Ultravisor-Hypervisor-State'].updateTask(tmpTaskData,
134
+ function (pError, pTask)
135
+ {
136
+ if (pError)
137
+ {
138
+ pResponse.send(400, { Error: pError.message });
139
+ return fNext();
140
+ }
141
+ pResponse.send(pTask);
142
+ return fNext();
143
+ });
144
+ }.bind(this)
145
+ );
146
+
147
+ this._OratorServer.del
148
+ (
149
+ '/Task/:GUIDTask',
150
+ function (pRequest, pResponse, fNext)
151
+ {
152
+ let tmpState = this.fable['Ultravisor-Hypervisor-State'];
153
+ if (tmpState._Tasks.hasOwnProperty(pRequest.params.GUIDTask))
154
+ {
155
+ delete tmpState._Tasks[pRequest.params.GUIDTask];
156
+ tmpState.persistState();
157
+ pResponse.send({ Status: 'Deleted', GUIDTask: pRequest.params.GUIDTask });
158
+ }
159
+ else
160
+ {
161
+ pResponse.send(404, { Error: `Task ${pRequest.params.GUIDTask} not found.` });
162
+ }
163
+ return fNext();
164
+ }.bind(this)
165
+ );
166
+
167
+ // --- Operation CRUD ---
168
+ this._OratorServer.get
169
+ (
170
+ '/Operation',
171
+ function (pRequest, pResponse, fNext)
172
+ {
173
+ this.fable['Ultravisor-Hypervisor-State'].getOperationList({},
174
+ function (pError, pOperations)
175
+ {
176
+ if (pError)
177
+ {
178
+ pResponse.send(500, { Error: pError.message });
179
+ return fNext();
180
+ }
181
+ pResponse.send(pOperations);
182
+ return fNext();
183
+ });
184
+ }.bind(this)
185
+ );
186
+
187
+ this._OratorServer.get
188
+ (
189
+ '/Operation/:GUIDOperation',
190
+ function (pRequest, pResponse, fNext)
191
+ {
192
+ this.fable['Ultravisor-Hypervisor-State'].getOperation(pRequest.params.GUIDOperation,
193
+ function (pError, pOperation)
194
+ {
195
+ if (pError)
196
+ {
197
+ pResponse.send(404, { Error: pError.message });
198
+ return fNext();
199
+ }
200
+ pResponse.send(pOperation);
201
+ return fNext();
202
+ });
203
+ }.bind(this)
204
+ );
205
+
206
+ this._OratorServer.post
207
+ (
208
+ '/Operation',
209
+ function (pRequest, pResponse, fNext)
210
+ {
211
+ this.fable['Ultravisor-Hypervisor-State'].updateOperation(pRequest.body,
212
+ function (pError, pOperation)
213
+ {
214
+ if (pError)
215
+ {
216
+ pResponse.send(400, { Error: pError.message });
217
+ return fNext();
218
+ }
219
+ pResponse.send(pOperation);
220
+ return fNext();
221
+ });
222
+ }.bind(this)
223
+ );
224
+
225
+ this._OratorServer.put
226
+ (
227
+ '/Operation/:GUIDOperation',
228
+ function (pRequest, pResponse, fNext)
229
+ {
230
+ let tmpOperationData = pRequest.body || {};
231
+ tmpOperationData.GUIDOperation = pRequest.params.GUIDOperation;
232
+ this.fable['Ultravisor-Hypervisor-State'].updateOperation(tmpOperationData,
233
+ function (pError, pOperation)
234
+ {
235
+ if (pError)
236
+ {
237
+ pResponse.send(400, { Error: pError.message });
238
+ return fNext();
239
+ }
240
+ pResponse.send(pOperation);
241
+ return fNext();
242
+ });
243
+ }.bind(this)
244
+ );
245
+
246
+ this._OratorServer.del
247
+ (
248
+ '/Operation/:GUIDOperation',
249
+ function (pRequest, pResponse, fNext)
250
+ {
251
+ let tmpState = this.fable['Ultravisor-Hypervisor-State'];
252
+ if (tmpState._Operations.hasOwnProperty(pRequest.params.GUIDOperation))
253
+ {
254
+ delete tmpState._Operations[pRequest.params.GUIDOperation];
255
+ tmpState.persistState();
256
+ pResponse.send({ Status: 'Deleted', GUIDOperation: pRequest.params.GUIDOperation });
257
+ }
258
+ else
259
+ {
260
+ pResponse.send(404, { Error: `Operation ${pRequest.params.GUIDOperation} not found.` });
261
+ }
262
+ return fNext();
263
+ }.bind(this)
264
+ );
265
+
266
+ // --- Task Execution ---
267
+ this._OratorServer.get
268
+ (
269
+ '/Task/:GUIDTask/Execute',
270
+ function (pRequest, pResponse, fNext)
271
+ {
272
+ let tmpState = this.fable['Ultravisor-Hypervisor-State'];
273
+ let tmpTaskService = this.fable['Ultravisor-Task'];
274
+
275
+ tmpState.getTask(pRequest.params.GUIDTask,
276
+ function (pError, pTask)
277
+ {
278
+ if (pError)
279
+ {
280
+ pResponse.send(404, { Error: pError.message });
281
+ return fNext();
282
+ }
283
+ tmpTaskService.executeTask(pTask, {},
284
+ function (pExecError, pManifestEntry)
285
+ {
286
+ if (pExecError)
287
+ {
288
+ pResponse.send(500, { Error: pExecError.message });
289
+ return fNext();
290
+ }
291
+ // Store the task result as a manifest so it appears in /Manifest
292
+ let tmpManifestService = this.fable['Ultravisor-Operation-Manifest'];
293
+ tmpManifestService.createTaskManifest(pManifestEntry);
294
+ pResponse.send(pManifestEntry);
295
+ return fNext();
296
+ }.bind(this));
297
+ }.bind(this));
298
+ }.bind(this)
299
+ );
300
+
301
+ // --- Operation Execution ---
302
+ this._OratorServer.get
303
+ (
304
+ '/Operation/:GUIDOperation/Execute',
305
+ function (pRequest, pResponse, fNext)
306
+ {
307
+ let tmpState = this.fable['Ultravisor-Hypervisor-State'];
308
+ let tmpOperationService = this.fable['Ultravisor-Operation'];
309
+
310
+ tmpState.getOperation(pRequest.params.GUIDOperation,
311
+ function (pError, pOperation)
312
+ {
313
+ if (pError)
314
+ {
315
+ pResponse.send(404, { Error: pError.message });
316
+ return fNext();
317
+ }
318
+ tmpOperationService.executeOperation(pOperation,
319
+ function (pExecError, pManifest)
320
+ {
321
+ if (pExecError)
322
+ {
323
+ pResponse.send(500, { Error: pExecError.message });
324
+ return fNext();
325
+ }
326
+ pResponse.send(pManifest);
327
+ return fNext();
328
+ });
329
+ });
330
+ }.bind(this)
331
+ );
332
+
333
+ // --- Schedule ---
334
+ this._OratorServer.get
335
+ (
336
+ '/Schedule',
337
+ function (pRequest, pResponse, fNext)
338
+ {
339
+ pResponse.send(this.fable['Ultravisor-Hypervisor'].getSchedule());
340
+ return fNext();
341
+ }.bind(this)
342
+ );
343
+
344
+ this._OratorServer.post
345
+ (
346
+ '/Schedule/Task',
347
+ function (pRequest, pResponse, fNext)
348
+ {
349
+ let tmpBody = pRequest.body || {};
350
+ let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
351
+
352
+ tmpHypervisor.scheduleTask(tmpBody.GUIDTask, tmpBody.ScheduleType, tmpBody.Parameters,
353
+ function (pError, pEntry)
354
+ {
355
+ if (pError)
356
+ {
357
+ pResponse.send(400, { Error: pError.message });
358
+ return fNext();
359
+ }
360
+ pResponse.send(pEntry);
361
+ return fNext();
362
+ });
363
+ }.bind(this)
364
+ );
365
+
366
+ this._OratorServer.post
367
+ (
368
+ '/Schedule/Operation',
369
+ function (pRequest, pResponse, fNext)
370
+ {
371
+ let tmpBody = pRequest.body || {};
372
+ let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
373
+
374
+ tmpHypervisor.scheduleOperation(tmpBody.GUIDOperation, tmpBody.ScheduleType, tmpBody.Parameters,
375
+ function (pError, pEntry)
376
+ {
377
+ if (pError)
378
+ {
379
+ pResponse.send(400, { Error: pError.message });
380
+ return fNext();
381
+ }
382
+ pResponse.send(pEntry);
383
+ return fNext();
384
+ });
385
+ }.bind(this)
386
+ );
387
+
388
+ this._OratorServer.del
389
+ (
390
+ '/Schedule/:GUID',
391
+ function (pRequest, pResponse, fNext)
392
+ {
393
+ this.fable['Ultravisor-Hypervisor'].removeScheduleEntry(pRequest.params.GUID,
394
+ function (pError, pResult)
395
+ {
396
+ if (pError)
397
+ {
398
+ pResponse.send(404, { Error: pError.message });
399
+ return fNext();
400
+ }
401
+ pResponse.send({ Status: 'Deleted', GUID: pRequest.params.GUID });
402
+ return fNext();
403
+ });
404
+ }.bind(this)
405
+ );
406
+
407
+ this._OratorServer.get
408
+ (
409
+ '/Schedule/Start',
410
+ function (pRequest, pResponse, fNext)
411
+ {
412
+ this.fable['Ultravisor-Hypervisor'].startSchedule(
413
+ function ()
414
+ {
415
+ pResponse.send({ Status: 'Schedule Started' });
416
+ return fNext();
417
+ });
418
+ }.bind(this)
419
+ );
420
+
421
+ this._OratorServer.get
422
+ (
423
+ '/Schedule/Stop',
424
+ function (pRequest, pResponse, fNext)
425
+ {
426
+ this.fable['Ultravisor-Hypervisor'].stopSchedule(
427
+ function ()
428
+ {
429
+ pResponse.send({ Status: 'Schedule Stopped' });
430
+ return fNext();
431
+ });
432
+ }.bind(this)
433
+ );
434
+
435
+ // --- Manifests ---
436
+ this._OratorServer.get
437
+ (
438
+ '/Manifest',
439
+ function (pRequest, pResponse, fNext)
440
+ {
441
+ pResponse.send(this.fable['Ultravisor-Operation-Manifest'].getManifestList());
442
+ return fNext();
443
+ }.bind(this)
444
+ );
445
+
446
+ this._OratorServer.get
447
+ (
448
+ '/Manifest/:GUIDRun',
449
+ function (pRequest, pResponse, fNext)
450
+ {
451
+ let tmpManifest = this.fable['Ultravisor-Operation-Manifest'].getManifest(pRequest.params.GUIDRun);
452
+ if (tmpManifest)
453
+ {
454
+ pResponse.send(tmpManifest);
455
+ }
456
+ else
457
+ {
458
+ pResponse.send(404, { Error: `Manifest ${pRequest.params.GUIDRun} not found.` });
459
+ }
460
+ return fNext();
461
+ }.bind(this)
36
462
  );
37
463
 
38
464
  return fCallback();
@@ -79,6 +505,14 @@ class UltravisorAPIServer extends libPictService
79
505
  }.bind(this));
80
506
  }.bind(this));
81
507
 
508
+ tmpAnticipate.anticipate(
509
+ function (fNext)
510
+ {
511
+ // Enable JSON body parsing for POST/PUT requests
512
+ this._OratorServer.server.use(this._OratorServer.bodyParser());
513
+ return fNext();
514
+ }.bind(this));
515
+
82
516
  tmpAnticipate.anticipate(
83
517
  function (fNext)
84
518
  {
@@ -95,6 +529,32 @@ class UltravisorAPIServer extends libPictService
95
529
  }.bind(this));
96
530
  }.bind(this));
97
531
 
532
+ tmpAnticipate.anticipate(
533
+ function (fNext)
534
+ {
535
+ let tmpWebInterfacePath = this.fable?.ProgramConfiguration?.UltravisorWebInterfacePath;
536
+ if (tmpWebInterfacePath && (typeof tmpWebInterfacePath === 'string'))
537
+ {
538
+ // Resolve relative paths against the current working directory
539
+ let tmpResolvedPath = libPath.resolve(process.cwd(), tmpWebInterfacePath);
540
+ this.log.info(`Ultravisor: Serving web interface from [${tmpResolvedPath}]`);
541
+
542
+ // Workaround: Orator's addStaticRoute serves bare "/" with
543
+ // Content-Type application/octet-stream because serve-static
544
+ // overwrites the MIME header that Orator sets. Add an
545
+ // explicit redirect so "/" always loads /index.html which is
546
+ // served with the correct text/html content type.
547
+ this._OratorServer.get('/',
548
+ function (pRequest, pResponse, fNext)
549
+ {
550
+ pResponse.redirect('/index.html', fNext);
551
+ });
552
+
553
+ this._Orator.addStaticRoute(tmpResolvedPath, 'index.html', '/*');
554
+ }
555
+ return fNext();
556
+ }.bind(this));
557
+
98
558
  tmpAnticipate.anticipate(
99
559
  function (fNext)
100
560
  {