nodejs-poolcontroller 7.4.0 → 7.5.1

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 (55) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +3 -0
  3. package/README.md +2 -2
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Equipment.ts +89 -29
  8. package/controller/Errors.ts +14 -1
  9. package/controller/State.ts +75 -31
  10. package/controller/boards/EasyTouchBoard.ts +81 -36
  11. package/controller/boards/IntelliCenterBoard.ts +96 -32
  12. package/controller/boards/IntelliTouchBoard.ts +103 -29
  13. package/controller/boards/NixieBoard.ts +79 -27
  14. package/controller/boards/SystemBoard.ts +1552 -822
  15. package/controller/comms/Comms.ts +84 -9
  16. package/controller/comms/messages/Messages.ts +10 -4
  17. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  18. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  19. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  20. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  21. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  22. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  23. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  24. package/controller/comms/messages/config/HeaterMessage.ts +10 -9
  25. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  26. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  27. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  28. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  29. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  30. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  31. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  32. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  33. package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
  34. package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
  35. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  36. package/controller/nixie/Nixie.ts +18 -16
  37. package/controller/nixie/chemistry/ChemController.ts +57 -37
  38. package/controller/nixie/chemistry/Chlorinator.ts +7 -8
  39. package/controller/nixie/circuits/Circuit.ts +17 -0
  40. package/controller/nixie/pumps/Pump.ts +49 -24
  41. package/controller/nixie/schedules/Schedule.ts +1 -1
  42. package/defaultConfig.json +15 -0
  43. package/issue_template.md +1 -1
  44. package/logger/DataLogger.ts +37 -22
  45. package/package.json +3 -1
  46. package/web/Server.ts +515 -27
  47. package/web/bindings/influxDB.json +35 -0
  48. package/web/bindings/mqtt.json +62 -3
  49. package/web/bindings/mqttAlt.json +57 -4
  50. package/web/interfaces/httpInterface.ts +2 -0
  51. package/web/interfaces/influxInterface.ts +3 -2
  52. package/web/interfaces/mqttInterface.ts +12 -1
  53. package/web/services/config/Config.ts +162 -37
  54. package/web/services/state/State.ts +47 -3
  55. package/web/services/state/StateSocket.ts +1 -1
@@ -879,6 +879,11 @@
879
879
  "name": "spaSetpoint",
880
880
  "value": "@bind=data.spaSetpoint;",
881
881
  "type": "int"
882
+ },
883
+ {
884
+ "name": "target output",
885
+ "value": "@bind=data.targetOutput;",
886
+ "type": "int"
882
887
  }
883
888
  ]
884
889
  }
@@ -911,6 +916,10 @@
911
916
  {
912
917
  "name": "name",
913
918
  "value": "@bind=data.name;"
919
+ },
920
+ {
921
+ "name": "id",
922
+ "value": "@bind=data.id;"
914
923
  }
915
924
  ],
916
925
  "fields": [
@@ -942,6 +951,32 @@
942
951
  ]
943
952
  }
944
953
  ]
954
+ },
955
+ {
956
+ "name": "cover",
957
+ "description": "Bind cover to measurements",
958
+ "points": [
959
+ {
960
+ "measurement": "cover",
961
+ "tags": [
962
+ {
963
+ "name": "name",
964
+ "value": "@bind=data.name;"
965
+ },
966
+ {
967
+ "name": "id",
968
+ "value": "@bind=data.id;"
969
+ }
970
+ ],
971
+ "fields": [
972
+ {
973
+ "name": "isClosed",
974
+ "value": "@bind=data.isClosed;",
975
+ "type": "boolean"
976
+ }
977
+ ]
978
+ }
979
+ ]
945
980
  }
946
981
  ]
947
982
  }
@@ -64,6 +64,10 @@
64
64
  {
65
65
  "topic": "state/mode",
66
66
  "message": "@bind=data.mode;"
67
+ },
68
+ {
69
+ "topic": "state/startTime",
70
+ "message": "@bind=data.startTime;"
67
71
  }
68
72
  ]
69
73
  },
@@ -73,7 +77,7 @@
73
77
  "topics": [
74
78
  {
75
79
  "topic": "state/circuits/@bind=data.id;/@bind=data.name;",
76
- "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
80
+ "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';,\"endTime\":\"@bind=data.endTime;\"}",
77
81
  "description": "Bind 'on'/'off' as a message to the state topic."
78
82
  },
79
83
  {
@@ -116,6 +120,11 @@
116
120
  "options": { "qos": 2 },
117
121
  "enabled": false
118
122
  },
123
+ {
124
+ "topic": "state/circuits/@bind=data.id;/@bind=data.name;/endTime",
125
+ "message": "{\"id\":@bind=data.id;,\"endTime\":@bind=data.endTime;}",
126
+ "description": "Bind endTime as a message to the state topic."
127
+ },
119
128
  {
120
129
  "topic": "state/circuits/@bind=data.id;/@bind=data.name;/object",
121
130
  "message": "@bind=data;",
@@ -146,15 +155,19 @@
146
155
  }
147
156
  ]
148
157
  },
149
-
150
158
  {
151
159
  "name": "feature",
152
160
  "description": "Populate the features topics",
153
161
  "topics": [
154
162
  {
155
163
  "topic": "state/features/@bind=data.id;/@bind=data.name;",
156
- "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
164
+ "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';,\"endTime\":\"@bind=data.endTime;\"}",
157
165
  "description": "Bind 'on'/'off' as a message to the state topic."
166
+ },
167
+ {
168
+ "topic": "state/features/@bind=data.id;/@bind=data.name;/endTime",
169
+ "message": "{\"id\":@bind=data.id;,\"endTime\":@bind=data.endTime;}",
170
+ "description": "Bind endTime as a message to the state topic."
158
171
  }
159
172
  ]
160
173
  },
@@ -179,6 +192,32 @@
179
192
  }
180
193
  ]
181
194
  },
195
+ {
196
+ "name": "circuitGroup",
197
+ "description": "Populate the circuitGroup topic",
198
+ "topics": [
199
+ {
200
+ "topic": "state/circuitGroups/@bind=data.id;/name",
201
+ "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
202
+ "description": "Bind name to the state topic."
203
+ },
204
+ {
205
+ "topic": "state/circuitGroups/@bind=data.id;/isOn",
206
+ "message": "{\"id\":@bind=data.id;,\"isOn\":@bind=data.isOn?'\"on\"':'\"off\"';}",
207
+ "description": "Bind the on/off status to the topic."
208
+ },
209
+ {
210
+ "topic": "state/circuitGroups/@bind=data.id;/type",
211
+ "message": "{\"type\":@bind=data.type;}"
212
+ },
213
+ {
214
+ "topic": "state/circuitGroups/@bind=data.id;/showInFeatures",
215
+ "message": "{\"showInFeatures\":@bind=data.showInFeatures;}",
216
+ "description": "Indicates wether the item should show in features."
217
+ }
218
+
219
+ ]
220
+ },
182
221
  {
183
222
  "name": "body",
184
223
  "description": "Populate the body topic",
@@ -203,6 +242,11 @@
203
242
  "message": "{\"setPoint\":@bind=data.setPoint;}",
204
243
  "description": "Send set point."
205
244
  },
245
+ {
246
+ "topic": "state/temps/bodies/@bind=data.id;/@bind=data.name;/coolSetpoint",
247
+ "message": "{\"coolSetpoint\":@bind=data.coolSetpoint;}",
248
+ "description": "Send cool set point."
249
+ },
206
250
  {
207
251
  "topic": "state/temps/bodies/@bind=data.id;/@bind=data.name;/temp",
208
252
  "message": "{\"temp\":@bind=data.temp;}",
@@ -286,6 +330,10 @@
286
330
  {
287
331
  "topic": "state/lightgroups/@bind=data.id;/@bind=data.name;/type",
288
332
  "message": "{\"type\":@bind=data.type;}"
333
+ },
334
+ {
335
+ "topic": "state/lightgroups/@bind=data.id;/@bind=data.name;/endTime",
336
+ "message": "{\"endTime\":@bind=data.endTime;}"
289
337
  }
290
338
  ]
291
339
  },
@@ -575,6 +623,17 @@
575
623
  }
576
624
  ]
577
625
  },
626
+ {
627
+ "name": "cover",
628
+ "description": "Populate the cover topic",
629
+ "topics": [
630
+ {
631
+ "topic": "state/covers/@bind=data.id;/@bind=data.name;",
632
+ "message": "{\"id\":@bind=data.id;,\"isClosed\":@bind=data.isClosed?'\"true\"':'\"false\"';}",
633
+ "description": "Bind isClosed as a message to the state topic."
634
+ }
635
+ ]
636
+ },
578
637
  {
579
638
  "name": "*",
580
639
  "description": "DEFAULT: Sends the entire emitted response.",
@@ -63,8 +63,11 @@
63
63
  {
64
64
  "topic": "state/mode",
65
65
  "message": "@bind=data.mode;"
66
+ },
67
+ {
68
+ "topic": "state/startTime",
69
+ "message": "@bind=data.startTime;"
66
70
  }
67
-
68
71
  ]
69
72
  },
70
73
  {
@@ -86,6 +89,11 @@
86
89
  "message": "@bind=data.type;",
87
90
  "description": "The type of circuit we are dealing with."
88
91
  },
92
+ {
93
+ "topic": "state/circuits/@bind=data.id;/endTime",
94
+ "message": "@bind=data.endTime;",
95
+ "description": "The end time for the circuit."
96
+ },
89
97
  {
90
98
  "topic": "state/circuits/@bind=data.id;/lightingTheme",
91
99
  "message": "@bind=data.lightingTheme;",
@@ -95,7 +103,12 @@
95
103
  {
96
104
  "topic": "state/circuits/@bind=data.id;/showInFeatures",
97
105
  "message": "@bind=data.showInFeatures;",
98
- "description": "Indicates wether the item should show in features."
106
+ "description": "Indicates whether the item should show in features."
107
+ },
108
+ {
109
+ "topic": "state/circuits/@bind=data.id;/endTime",
110
+ "message": "@bind=data.endTime;",
111
+ "description": "Indicates end time for a circuit (when it will turn off)."
99
112
  }
100
113
  ]
101
114
  },
@@ -113,6 +126,11 @@
113
126
  "message": "@bind=data.isOn;",
114
127
  "description": "Bind the on/off status to the topic."
115
128
  },
129
+ {
130
+ "topic": "state/features/@bind=data.id;/endTime",
131
+ "message": "@bind=data.endTime;",
132
+ "description": "The end time for the feature."
133
+ },
116
134
  {
117
135
  "topic": "state/features/@bind=data.id;/type",
118
136
  "message": "@bind=data.type;",
@@ -121,7 +139,12 @@
121
139
  {
122
140
  "topic": "state/features/@bind=data.id;/showInFeatures",
123
141
  "message": "@bind=data.showInFeatures;",
124
- "description": "Indicates wether the item should show in features."
142
+ "description": "Indicates whether the item should show in features."
143
+ },
144
+ {
145
+ "topic": "state/features/@bind=data.id;/endTime",
146
+ "message": "@bind=data.endTime;",
147
+ "description": "Indicates end time for a features (when it will turn off)."
125
148
  }
126
149
  ]
127
150
  },
@@ -150,6 +173,10 @@
150
173
  {
151
174
  "topic": "state/lightGroups/@bind=data.id;/type",
152
175
  "message": "@bind=data.type;"
176
+ },
177
+ {
178
+ "topic": "state/lightGroups/@bind=data.id;/endTime",
179
+ "message": "@bind=data.endTime;"
153
180
  }
154
181
  ]
155
182
  },
@@ -174,7 +201,12 @@
174
201
  {
175
202
  "topic": "state/circuitGroups/@bind=data.id;/showInFeatures",
176
203
  "message": "@bind=data.showInFeatures;",
177
- "description": "Indicates wether the item should show in features."
204
+ "description": "Indicates whether the item should show in features."
205
+ },
206
+ {
207
+ "topic": "state/circuitGroups/@bind=data.id;/endTime",
208
+ "message": "@bind=data.showInFeatures;",
209
+ "description": "Indicates end time for the circuitGroup (when it will turn off)."
178
210
  }
179
211
 
180
212
  ]
@@ -309,6 +341,11 @@
309
341
  "message": "@bind=data.setPoint;",
310
342
  "description": "Setpoint."
311
343
  },
344
+ {
345
+ "topic": "state/temps/bodies/@bind=data.id;/coolSetpoint",
346
+ "message": "@bind=data.coolSetpoint;",
347
+ "description": "Cool setpoint."
348
+ },
312
349
  {
313
350
  "topic": "state/temps/bodies/@bind=data.id;/temp",
314
351
  "message": "@bind=data.temp;",
@@ -611,6 +648,22 @@
611
648
  }
612
649
  ]
613
650
  },
651
+ {
652
+ "name": "cover",
653
+ "description": "Populate the cover topic",
654
+ "topics": [
655
+ {
656
+ "topic": "state/covers/@bind=data.id;/name",
657
+ "message": "@bind=data.name;",
658
+ "description": "Bind the name topic."
659
+ },
660
+ {
661
+ "topic": "state/covers/@bind=data.id;/isClosed",
662
+ "message": "@bind=data.isClosed;",
663
+ "description": "Bind isClosed as a message to the state topic."
664
+ }
665
+ ]
666
+ },
614
667
  {
615
668
  "name": "*",
616
669
  "description": "DEFAULT: Sends the entire emitted response.",
@@ -100,11 +100,13 @@ export class HttpInterfaceBindings extends BaseInterfaceBindings {
100
100
  opts.headers["CONTENT-LENGTH"] = Buffer.byteLength(sbody || '');
101
101
  }
102
102
  if (opts.port === 443 || (opts.protocol || '').startsWith('https')) {
103
+ opts.protocol = 'https:';
103
104
  req = https.request(opts, (response: http.IncomingMessage) => {
104
105
  //console.log(response);
105
106
  });
106
107
  }
107
108
  else {
109
+ opts.protocol = 'http:';
108
110
  req = http.request(opts, (response: http.IncomingMessage) => {
109
111
  //console.log(response.statusCode);
110
112
  });
@@ -30,9 +30,10 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
30
30
  declare events: InfluxInterfaceEvent[];
31
31
  private init = () => {
32
32
  let baseOpts = extend(true, this.cfg.options, this.context.options);
33
- let url = 'http';
33
+ let url = 'http://';
34
34
  if (typeof baseOpts.protocol !== 'undefined' && baseOpts.protocol) url = baseOpts.protocol;
35
- url = `${url}://${baseOpts.host}:${baseOpts.port}`;
35
+ if (!url.endsWith('://')) url += '://';
36
+ url = `${url}${baseOpts.host}:${baseOpts.port}`;
36
37
  let influxDB: InfluxDB;
37
38
  let bucket;
38
39
  let org;
@@ -370,7 +370,18 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
370
370
  logger.error(new ServiceParameterError(`Cannot set body setPoint. You must supply a valid id, circuit, name, or type for the body`, 'body', 'id', msg.id));
371
371
  return;
372
372
  }
373
- let tbody = await sys.board.bodies.setHeatSetpointAsync(body, parseInt(msg.setPoint, 10));
373
+ if (typeof msg.setPoint !== 'undefined' || typeof msg.heatSetpoint !== 'undefined') {
374
+ let setPoint = parseInt(msg.setPoint, 10) || parseInt(msg.heatSetpoint, 10);
375
+ if (!isNaN(setPoint)) {
376
+ await sys.board.bodies.setHeatSetpointAsync(body, setPoint);
377
+ }
378
+ }
379
+ if (typeof msg.coolSetpoint !== 'undefined') {
380
+ let setPoint = parseInt(msg.coolSetpoint, 10);
381
+ if (!isNaN(setPoint)) {
382
+ await sys.board.bodies.setCoolSetpointAsync(body, setPoint);
383
+ }
384
+ }
374
385
  }
375
386
  }
376
387
  catch (err) { logger.error(err); }
@@ -14,16 +14,21 @@ GNU Affero General Public License for more details.
14
14
  You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
+ import * as fs from "fs";
17
18
  import * as express from "express";
18
19
  import * as extend from 'extend';
20
+ import * as multer from 'multer';
21
+ import * as path from "path";
19
22
  import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater } from "../../../controller/Equipment";
20
23
  import { config } from "../../../config/Config";
21
24
  import { logger } from "../../../logger/Logger";
22
25
  import { utils } from "../../../controller/Constants";
26
+ import { ServiceProcessError } from "../../../controller/Errors";
23
27
  import { state } from "../../../controller/State";
24
28
  import { stopPacketCaptureAsync, startPacketCapture } from '../../../app';
25
29
  import { conn } from "../../../controller/comms/Comms";
26
- import { webApp } from "../../Server";
30
+ import { webApp, BackupFile, RestoreFile } from "../../Server";
31
+ import { release } from "os";
27
32
 
28
33
  export class ConfigRoute {
29
34
  public static initRoutes(app: express.Application) {
@@ -52,7 +57,8 @@ export class ConfigRoute {
52
57
  clockSources: sys.board.valueMaps.clockSources.toArray(),
53
58
  clockModes: sys.board.valueMaps.clockModes.toArray(),
54
59
  pool: sys.general.get(true),
55
- sensors: sys.board.system.getSensors()
60
+ sensors: sys.board.system.getSensors(),
61
+ systemUnits: sys.board.valueMaps.systemUnits.toArray()
56
62
  };
57
63
  return res.status(200).send(opts);
58
64
  });
@@ -115,7 +121,8 @@ export class ConfigRoute {
115
121
  let opts = {
116
122
  maxBodies: sys.equipment.maxBodies,
117
123
  bodyTypes: sys.board.valueMaps.bodies.toArray(),
118
- bodies: sys.bodies.get()
124
+ bodies: sys.bodies.get(),
125
+ capacityUnits: sys.board.valueMaps.volumeUnits.toArray()
119
126
  };
120
127
  return res.status(200).send(opts);
121
128
  });
@@ -282,7 +289,8 @@ export class ConfigRoute {
282
289
  bodies: sys.board.bodies.getBodyAssociations(),
283
290
  chlorinators: sys.chlorinators.get(),
284
291
  maxChlorinators: sys.equipment.maxChlorinators,
285
- models: sys.board.valueMaps.chlorinatorModel.toArray()
292
+ models: sys.board.valueMaps.chlorinatorModel.toArray(),
293
+ equipmentMasters: sys.board.valueMaps.equipmentMaster.toArray()
286
294
  };
287
295
  return res.status(200).send(opts);
288
296
  });
@@ -307,13 +315,16 @@ export class ConfigRoute {
307
315
  let opts = {
308
316
  interfaces: config.getSection('web.interfaces'),
309
317
  types: [
310
- {name: 'rem', desc: 'Relay Equipment Manager'},
311
- {name: 'mqtt', desc: 'MQTT'}
318
+ { name: 'rest', desc: 'Rest' },
319
+ { name: 'http', desc: 'Http' },
320
+ { name: 'rem', desc: 'Relay Equipment Manager' },
321
+ { name: 'mqtt', desc: 'MQTT' },
322
+ { name: 'influx', desc: 'InfluxDB' }
312
323
  ],
313
324
  protocols: [
314
325
  { val: 0, name: 'http://', desc: 'http://' },
315
326
  { val: 1, name: 'https://', desc: 'https://' },
316
- { val: 2, name: 'mqtt://', desc: 'mqtt://' },
327
+ { val: 2, name: 'mqtt://', desc: 'mqtt://' }
317
328
  ]
318
329
  }
319
330
  return res.status(200).send(opts);
@@ -342,14 +353,14 @@ export class ConfigRoute {
342
353
  });
343
354
  /******* END OF CONFIGURATION PICK LISTS/REFERENCES AND VALIDATION ***********/
344
355
  /******* ENDPOINTS FOR MODIFYING THE OUTDOOR CONTROL PANEL SETTINGS **********/
345
- app.put('/config/rem', async (req, res, next)=>{
356
+ app.put('/config/rem', async (req, res, next) => {
346
357
  try {
347
358
  // RSG: this is problematic because we now enable multiple rem type interfaces that may not be called REM.
348
359
  // This is now also a dupe of PUT /app/interface and should be consolidated
349
360
  // config.setSection('web.interfaces.rem', req.body);
350
361
  config.setInterface(req.body);
351
362
  }
352
- catch (err) {next(err);}
363
+ catch (err) { next(err); }
353
364
  })
354
365
  app.put('/config/tempSensors', async (req, res, next) => {
355
366
  try {
@@ -762,16 +773,16 @@ export class ConfigRoute {
762
773
  let grp = sys.circuitGroups.getItemById(parseInt(req.params.id, 10));
763
774
  return res.status(200).send(grp.getExtended());
764
775
  });
765
- /* app.get('/config/chemController/search', async (req, res, next) => {
766
- // Change the options for the pool.
767
- try {
768
- let result = await sys.board.virtualChemControllers.search();
769
- return res.status(200).send(result);
770
- }
771
- catch (err) {
772
- next(err);
773
- }
774
- }); */
776
+ /* app.get('/config/chemController/search', async (req, res, next) => {
777
+ // Change the options for the pool.
778
+ try {
779
+ let result = await sys.board.virtualChemControllers.search();
780
+ return res.status(200).send(result);
781
+ }
782
+ catch (err) {
783
+ next(err);
784
+ }
785
+ }); */
775
786
  app.put('/config/chemController', async (req, res, next) => {
776
787
  try {
777
788
  let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
@@ -794,17 +805,17 @@ export class ConfigRoute {
794
805
  catch (err) { next(err); }
795
806
 
796
807
  });
797
- /* app.get('/config/intellibrite', (req, res) => {
798
- return res.status(200).send(sys.intellibrite.getExtended());
799
- });
800
- app.get('/config/intellibrite/colors', (req, res) => {
801
- return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
802
- });
803
- app.put('/config/intellibrite/setColors', (req, res) => {
804
- let grp = extend(true, { id: 0 }, req.body);
805
- sys.board.circuits.setIntelliBriteColors(new LightGroup(grp));
806
- return res.status(200).send('OK');
807
- }); */
808
+ /* app.get('/config/intellibrite', (req, res) => {
809
+ return res.status(200).send(sys.intellibrite.getExtended());
810
+ });
811
+ app.get('/config/intellibrite/colors', (req, res) => {
812
+ return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
813
+ });
814
+ app.put('/config/intellibrite/setColors', (req, res) => {
815
+ let grp = extend(true, { id: 0 }, req.body);
816
+ sys.board.circuits.setIntelliBriteColors(new LightGroup(grp));
817
+ return res.status(200).send('OK');
818
+ }); */
808
819
  app.get('/config', (req, res) => {
809
820
  return res.status(200).send(sys.getSection('all'));
810
821
  });
@@ -830,11 +841,11 @@ export class ConfigRoute {
830
841
  return res.status(200).send('OK');
831
842
  });
832
843
  app.put('/app/interface', async (req, res, next) => {
833
- try{
834
- await webApp.updateServerInterface(req.body);
835
- return res.status(200).send('OK');
836
- }
837
- catch (err) {next(err);}
844
+ try {
845
+ let iface = await webApp.updateServerInterface(req.body);
846
+ return res.status(200).send(iface);
847
+ }
848
+ catch (err) { next(err); }
838
849
  });
839
850
  app.put('/app/rs485Port', async (req, res, next) => {
840
851
  try {
@@ -851,15 +862,129 @@ export class ConfigRoute {
851
862
  startPacketCapture(false);
852
863
  return res.status(200).send('OK');
853
864
  });
854
- app.get('/app/config/stopPacketCapture', async (req, res,next) => {
865
+ app.get('/app/config/stopPacketCapture', async (req, res, next) => {
855
866
  try {
856
867
  let file = await stopPacketCaptureAsync();
857
868
  res.download(file);
858
869
  }
859
- catch (err) {next(err);}
870
+ catch (err) { next(err); }
860
871
  });
861
872
  app.get('/app/config/:section', (req, res) => {
862
873
  return res.status(200).send(config.getSection(req.params.section));
863
874
  });
875
+ app.get('/app/config/options/backup', async (req, res, next) => {
876
+ try {
877
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0, keepCount: 5, servers: [] } });
878
+ let servers = await sys.ncp.getREMServers();
879
+ if (typeof servers !== 'undefined') {
880
+ // Just in case somebody deletes the backup section and doesn't put it back properly.
881
+ for (let i = 0; i < servers.length; i++) {
882
+ let srv = servers[i];
883
+ if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false, host: srv.interface.options.host });
884
+ }
885
+ for (let i = opts.servers.length - 1; i >= 0; i--) {
886
+ let srv = opts.servers[i];
887
+ if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
888
+ }
889
+ }
890
+ if (typeof opts.servers === 'undefined') opts.servers = [];
891
+ return res.status(200).send(opts);
892
+ } catch (err) { next(err); }
893
+ });
894
+ app.get('/app/config/options/restore', async (req, res, next) => {
895
+ try {
896
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0, keepCount: 5, servers: [], backupFiles: [] } });
897
+ let servers = await sys.ncp.getREMServers();
898
+ if (typeof servers !== 'undefined') {
899
+ for (let i = 0; i < servers.length; i++) {
900
+ let srv = servers[i];
901
+ if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false });
902
+ }
903
+ for (let i = opts.servers.length - 1; i >= 0; i--) {
904
+ let srv = opts.servers[i];
905
+ if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
906
+ }
907
+ }
908
+ if (typeof opts.servers === 'undefined') opts.servers = [];
909
+ opts.backupFiles = await webApp.readBackupFiles();
910
+ return res.status(200).send(opts);
911
+ } catch (err) { next(err); }
912
+
913
+ });
914
+ app.put('/app/config/options/backup', async (req, res, next) => {
915
+ try {
916
+ config.setSection('controller.backups', req.body);
917
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0, keepCount: 5, servers: [] } });
918
+ webApp.autoBackup = utils.makeBool(opts.automatic);
919
+ await webApp.checkAutoBackup();
920
+ return res.status(200).send(opts);
921
+ } catch (err) { next(err); }
922
+
923
+ });
924
+ app.put('/app/config/createBackup', async (req, res, next) => {
925
+ try {
926
+ let ret = await webApp.backupServer(req.body);
927
+ res.download(ret.filePath);
928
+ }
929
+ catch (err) { next(err); }
930
+ });
931
+ app.delete('/app/backup/file', async (req, res, next) => {
932
+ try {
933
+ let opts = req.body;
934
+ fs.unlinkSync(opts.filePath);
935
+ return res.status(200).send(opts);
936
+ }
937
+ catch (err) { next(err); }
938
+ });
939
+ app.post('/app/backup/file', async (req, res, next) => {
940
+ try {
941
+ let file = multer({
942
+ limits: { fileSize: 1000000 },
943
+ storage: multer.memoryStorage()
944
+ }).single('backupFile');
945
+ file(req, res, async (err) => {
946
+ try {
947
+ if (err) { next(err); }
948
+ else {
949
+ // Validate the incoming data and save it off only if it is valid.
950
+ let bf = await BackupFile.fromBuffer(req.file.originalname, req.file.buffer);
951
+ if (typeof bf === 'undefined') {
952
+ err = new ServiceProcessError(`Invalid backup file: ${req.file.originalname}`, 'POST: app/backup/file', 'extractBackupOptions');
953
+ next(err);
954
+ }
955
+ else {
956
+ if (fs.existsSync(bf.filePath))
957
+ return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/backup/file', 'writeFile'));
958
+ else {
959
+ try {
960
+ fs.writeFileSync(bf.filePath, req.file.buffer);
961
+ } catch (e) { logger.error(`Error writing backup file ${e.message}`); }
962
+ }
963
+ return res.status(200).send(bf);
964
+ }
965
+ }
966
+ } catch (e) {
967
+ err = new ServiceProcessError(`Error uploading file: ${e.message}`, 'POST: app/backup/file', 'uploadFile');
968
+ next(err);
969
+ logger.error(e);
970
+ }
971
+ });
972
+ } catch (err) { next(err); }
973
+ });
974
+ app.put('/app/restore/validate', async (req, res, next) => {
975
+ try {
976
+ // Validate all the restore options.
977
+ let opts = req.body;
978
+ let ctx = await webApp.validateRestore(opts);
979
+ return res.status(200).send(ctx);
980
+ } catch (err) { next(err); }
981
+ });
982
+ app.put('/app/restore/file', async (req, res, next) => {
983
+ try {
984
+ let opts = req.body;
985
+ let results = await webApp.restoreServers(opts);
986
+ return res.status(200).send(results);
987
+ } catch (err) { next(err); }
988
+ });
864
989
  }
865
990
  }