nodejs-poolcontroller 7.2.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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/Changelog +13 -0
- package/Dockerfile +1 -0
- package/README.md +5 -5
- package/app.ts +11 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +165 -9
- package/controller/Equipment.ts +186 -65
- package/controller/Errors.ts +22 -1
- package/controller/State.ts +273 -57
- package/controller/boards/EasyTouchBoard.ts +194 -95
- package/controller/boards/IntelliCenterBoard.ts +115 -42
- package/controller/boards/IntelliTouchBoard.ts +104 -30
- package/controller/boards/NixieBoard.ts +155 -53
- package/controller/boards/SystemBoard.ts +1529 -514
- package/controller/comms/Comms.ts +219 -42
- package/controller/comms/messages/Messages.ts +16 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CircuitMessage.ts +1 -1
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +43 -25
- package/controller/comms/messages/config/FeatureMessage.ts +8 -1
- package/controller/comms/messages/config/GeneralMessage.ts +8 -0
- package/controller/comms/messages/config/HeaterMessage.ts +15 -9
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +13 -1
- package/controller/comms/messages/config/PumpMessage.ts +4 -20
- package/controller/comms/messages/config/RemoteMessage.ts +4 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +12 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
- package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
- package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/NixieEquipment.ts +6 -6
- package/controller/nixie/bodies/Body.ts +7 -4
- package/controller/nixie/bodies/Filter.ts +7 -4
- package/controller/nixie/chemistry/ChemController.ts +800 -283
- package/controller/nixie/chemistry/Chlorinator.ts +22 -14
- package/controller/nixie/circuits/Circuit.ts +42 -7
- package/controller/nixie/heaters/Heater.ts +303 -30
- package/controller/nixie/pumps/Pump.ts +57 -30
- package/controller/nixie/schedules/Schedule.ts +10 -7
- package/controller/nixie/valves/Valve.ts +7 -5
- package/defaultConfig.json +32 -1
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +20 -18
- package/web/Server.ts +529 -31
- package/web/bindings/influxDB.json +157 -5
- package/web/bindings/mqtt.json +112 -13
- package/web/bindings/mqttAlt.json +109 -11
- package/web/interfaces/baseInterface.ts +2 -1
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +103 -54
- package/web/interfaces/mqttInterface.ts +16 -5
- package/web/services/config/Config.ts +179 -43
- package/web/services/state/State.ts +51 -5
- package/web/services/state/StateSocket.ts +19 -2
|
@@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
|
|
18
18
|
import extend = require("extend");
|
|
19
19
|
import { ClientOptions, InfluxDB, Point, WriteApi, WritePrecisionType } from '@influxdata/influxdb-client';
|
|
20
|
-
import { utils } from '../../controller/Constants';
|
|
20
|
+
import { utils, Timestamp } from '../../controller/Constants';
|
|
21
21
|
import { logger } from "../../logger/Logger";
|
|
22
22
|
import { BaseInterfaceBindings, InterfaceContext, InterfaceEvent } from "./baseInterface";
|
|
23
23
|
export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
@@ -25,33 +25,46 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
25
25
|
super(cfg);
|
|
26
26
|
}
|
|
27
27
|
private writeApi: WriteApi;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
declare context: InterfaceContext;
|
|
29
|
+
declare cfg;
|
|
30
|
+
declare events: InfluxInterfaceEvent[];
|
|
31
31
|
private init = () => {
|
|
32
32
|
let baseOpts = extend(true, this.cfg.options, this.context.options);
|
|
33
|
+
let url = 'http://';
|
|
34
|
+
if (typeof baseOpts.protocol !== 'undefined' && baseOpts.protocol) url = baseOpts.protocol;
|
|
35
|
+
if (!url.endsWith('://')) url += '://';
|
|
36
|
+
url = `${url}${baseOpts.host}:${baseOpts.port}`;
|
|
37
|
+
let influxDB: InfluxDB;
|
|
38
|
+
let bucket;
|
|
39
|
+
let org;
|
|
33
40
|
if (typeof baseOpts.host === 'undefined' || !baseOpts.host) {
|
|
34
41
|
logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid host.`);
|
|
35
42
|
return;
|
|
36
43
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
if (baseOpts.version === 1) {
|
|
45
|
+
if (typeof baseOpts.database === 'undefined' || !baseOpts.database) {
|
|
46
|
+
logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid database.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
bucket = `${baseOpts.database}/${baseOpts.retentionPolicy}`;
|
|
50
|
+
const clientOptions: ClientOptions = {
|
|
51
|
+
url,
|
|
52
|
+
token: `${baseOpts.username}:${baseOpts.password}`,
|
|
53
|
+
}
|
|
54
|
+
influxDB = new InfluxDB(clientOptions);
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
token: `${baseOpts.username}:${baseOpts.password}`,
|
|
56
|
+
else if (baseOpts.version === 2) {
|
|
57
|
+
org = baseOpts.org;
|
|
58
|
+
bucket = baseOpts.bucket;
|
|
59
|
+
const clientOptions: ClientOptions = {
|
|
60
|
+
url,
|
|
61
|
+
token: baseOpts.token,
|
|
62
|
+
}
|
|
63
|
+
influxDB = new InfluxDB(clientOptions);
|
|
50
64
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
this.writeApi = influxDB.getWriteApi(org, bucket, 'ms');
|
|
66
|
+
|
|
67
|
+
|
|
55
68
|
// set global tags from context
|
|
56
69
|
let baseTags = {}
|
|
57
70
|
baseOpts.tags.forEach(tag => {
|
|
@@ -79,7 +92,7 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
79
92
|
this.buildTokens(e.filter, evt, toks, e, data[0]);
|
|
80
93
|
if (eval(this.replaceTokens(e.filter, toks)) === false) continue;
|
|
81
94
|
}
|
|
82
|
-
for (let j = 0; j < e.points.length; j++){
|
|
95
|
+
for (let j = 0; j < e.points.length; j++) {
|
|
83
96
|
let _point = e.points[j];
|
|
84
97
|
// Figure out whether we need to check the filter for each point.
|
|
85
98
|
if (typeof _point.filter !== 'undefined') {
|
|
@@ -105,40 +118,68 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
105
118
|
}
|
|
106
119
|
})
|
|
107
120
|
_point.fields.forEach(_field => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
121
|
+
try {
|
|
122
|
+
let sname = _field.name;
|
|
123
|
+
this.buildTokens(sname, evt, toks, e, data[0]);
|
|
124
|
+
//console.log(toks);
|
|
125
|
+
sname = this.replaceTokens(sname, toks);
|
|
126
|
+
let svalue = _field.value;
|
|
127
|
+
this.buildTokens(svalue, evt, toks, e, data[0]);
|
|
128
|
+
svalue = this.replaceTokens(svalue, toks);
|
|
129
|
+
if (typeof sname !== 'undefined' && typeof svalue !== 'undefined' && !sname.includes('@bind') && !svalue.includes('@bind') && svalue !== null)
|
|
130
|
+
switch (_field.type) {
|
|
131
|
+
case 'int':
|
|
132
|
+
case 'integer':
|
|
133
|
+
let int = parseInt(svalue, 10);
|
|
134
|
+
if (!isNaN(int)) point.intField(sname, int);
|
|
135
|
+
// if (!isNaN(int) && typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.intField(sname, int);
|
|
136
|
+
break;
|
|
137
|
+
case 'string':
|
|
138
|
+
point.stringField(sname, svalue);
|
|
139
|
+
// if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.stringField(sname, svalue);
|
|
140
|
+
break;
|
|
141
|
+
case 'boolean':
|
|
142
|
+
point.booleanField(sname, utils.makeBool(svalue));
|
|
143
|
+
if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.booleanField(sname, !utils.makeBool(svalue));
|
|
144
|
+
break;
|
|
145
|
+
case 'float':
|
|
146
|
+
let float = parseFloat(svalue);
|
|
147
|
+
if (!isNaN(float)) point.floatField(sname, float);
|
|
148
|
+
// if (!isNaN(float) && typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.intField(sname, int);
|
|
149
|
+
break;
|
|
150
|
+
case 'timestamp':
|
|
151
|
+
case 'datetime':
|
|
152
|
+
case 'date':
|
|
153
|
+
//let dt = Date.parse(svalue.replace(/^["'](.+(?=["']$))["']$/, '$1'));
|
|
154
|
+
// RKS: 07-06-21 - I think this is missing the eval function around all of this. The strings still have the quotes around them. I think
|
|
155
|
+
// maybe we need to create a closure and execute it as a code segment for variable data.
|
|
156
|
+
let sdt = eval(svalue);
|
|
157
|
+
if (sdt !== null && typeof sdt !== 'undefined') {
|
|
158
|
+
let dt = Date.parse(sdt);
|
|
159
|
+
if (!isNaN(dt)) point.intField(sname, dt);
|
|
160
|
+
else if (svalue !== '') logger.warn(`Influx error parsing date from ${sname}: ${svalue}`);
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
logger.error(`InfluxDB point binding failure on ${evt}:${_field.name}/${_field.value} --> ${svalue || 'undefined'}`);
|
|
136
166
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
} catch (err) { logger.error(`Error binding InfluxDB point fields ${err.message}`); }
|
|
168
|
+
});
|
|
169
|
+
if (typeof _point.series !== 'undefined') {
|
|
170
|
+
try {
|
|
171
|
+
this.buildTokens(_point.series.value, evt, toks, e, data[0]);
|
|
172
|
+
let ser = eval(this.replaceTokens(_point.series.value, toks));
|
|
173
|
+
let ts = Date.parse(ser);
|
|
174
|
+
if (isNaN(ts)) {
|
|
175
|
+
logger.error(`Influx series timestamp is invalid ${ser}`);
|
|
176
|
+
}
|
|
177
|
+
else
|
|
178
|
+
point.timestamp(new Date(ts));
|
|
179
|
+
} catch (err) { logger.error(`Error parsing Influx point series for ${evt} - ${_point.series.value}`); }
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
point.timestamp(new Date());
|
|
142
183
|
try {
|
|
143
184
|
|
|
144
185
|
if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) {
|
|
@@ -147,10 +188,14 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
147
188
|
let sec = ts.getSeconds() - 1;
|
|
148
189
|
ts.setSeconds(sec);
|
|
149
190
|
point2.timestamp(ts);
|
|
191
|
+
logger.silly(`Writing influx ${e.name} inverse data point ${point2.toString()})`)
|
|
150
192
|
this.writeApi.writePoint(point2);
|
|
151
193
|
}
|
|
152
194
|
if (typeof point.toLineProtocol() !== 'undefined') {
|
|
195
|
+
logger.silly(`Writing influx ${e.name} data point ${point.toString()}`)
|
|
153
196
|
this.writeApi.writePoint(point);
|
|
197
|
+
this.writeApi.flush()
|
|
198
|
+
.catch(error => { logger.error(error); });
|
|
154
199
|
//logger.info(`INFLUX: ${point.toLineProtocol()}`)
|
|
155
200
|
}
|
|
156
201
|
else {
|
|
@@ -176,6 +221,7 @@ class InfluxInterfaceEvent extends InterfaceEvent {
|
|
|
176
221
|
|
|
177
222
|
export interface IPoint {
|
|
178
223
|
measurement: string;
|
|
224
|
+
series?: ISeries;
|
|
179
225
|
tags: ITag[];
|
|
180
226
|
fields: IFields[];
|
|
181
227
|
storePrevState?: boolean;
|
|
@@ -190,3 +236,6 @@ export interface IFields {
|
|
|
190
236
|
value: string;
|
|
191
237
|
type: string;
|
|
192
238
|
}
|
|
239
|
+
export interface ISeries {
|
|
240
|
+
value: string;
|
|
241
|
+
}
|
|
@@ -36,7 +36,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
36
36
|
}
|
|
37
37
|
private client: MqttClient;
|
|
38
38
|
private topics: string[] = [];
|
|
39
|
-
|
|
39
|
+
declare events: MqttInterfaceEvent[];
|
|
40
40
|
private subscribed: boolean; // subscribed to events or not
|
|
41
41
|
private sentInitialMessages = false;
|
|
42
42
|
private init = () => {
|
|
@@ -260,7 +260,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
260
260
|
if (typeof t.lastSent === 'undefined') t.lastSent = [];
|
|
261
261
|
let lm = t.lastSent.find(elem => elem.topic === topic);
|
|
262
262
|
if (typeof lm === 'undefined' || lm.message !== message) {
|
|
263
|
-
this.client.publish(topic, message, publishOptions);
|
|
263
|
+
setImmediate(() => { this.client.publish(topic, message, publishOptions); });
|
|
264
264
|
logger.silly(`MQTT send:\ntopic: ${topic}\nmessage: ${message}\nopts:${JSON.stringify(publishOptions)}`);
|
|
265
265
|
}
|
|
266
266
|
if (typeof lm === 'undefined') t.lastSent.push({ topic: topic, message: message });
|
|
@@ -269,7 +269,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
269
269
|
}
|
|
270
270
|
else {
|
|
271
271
|
logger.silly(`MQTT send:\ntopic: ${topic}\nmessage: ${message}\nopts:${JSON.stringify(publishOptions)}`);
|
|
272
|
-
this.client.publish(topic, message, publishOptions);
|
|
272
|
+
setImmediate(() => { this.client.publish(topic, message, publishOptions); });
|
|
273
273
|
if (typeof t.lastSent !== 'undefined') t.lastSent = undefined;
|
|
274
274
|
}
|
|
275
275
|
|
|
@@ -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
|
-
|
|
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); }
|
|
@@ -412,7 +423,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
412
423
|
break;
|
|
413
424
|
case 'settheme':
|
|
414
425
|
try {
|
|
415
|
-
let theme = await state.circuits.setLightThemeAsync(parseInt(msg.id, 10),
|
|
426
|
+
let theme = await state.circuits.setLightThemeAsync(parseInt(msg.id, 10), sys.board.valueMaps.lightThemes.encode(msg.theme));
|
|
416
427
|
}
|
|
417
428
|
catch (err) { logger.error(err); }
|
|
418
429
|
break;
|
|
@@ -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,14 +57,15 @@ 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
|
});
|
|
59
65
|
app.get('/config/options/rs485', (req, res) => {
|
|
60
66
|
let opts = {
|
|
61
|
-
port: config.getSection('comms', { enabled: false, netConnect: false }),
|
|
62
|
-
|
|
67
|
+
port: config.getSection('controller.comms', { enabled: false, netConnect: false }),
|
|
68
|
+
stats: conn.buffer.counter
|
|
63
69
|
};
|
|
64
70
|
return res.status(200).send(opts);
|
|
65
71
|
});
|
|
@@ -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
|
});
|
|
@@ -239,6 +246,7 @@ export class ConfigRoute {
|
|
|
239
246
|
phSupplyTypes: sys.board.valueMaps.phSupplyTypes.toArray(),
|
|
240
247
|
volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
|
|
241
248
|
dosingMethods: sys.board.valueMaps.chemDosingMethods.toArray(),
|
|
249
|
+
chlorDosingMethods: sys.board.valueMaps.chemChlorDosingMethods.toArray(),
|
|
242
250
|
orpProbeTypes: sys.board.valueMaps.chemORPProbeTypes.toArray(),
|
|
243
251
|
phProbeTypes: sys.board.valueMaps.chemPhProbeTypes.toArray(),
|
|
244
252
|
flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
|
|
@@ -280,7 +288,9 @@ export class ConfigRoute {
|
|
|
280
288
|
types: sys.board.valueMaps.chlorinatorType.toArray(),
|
|
281
289
|
bodies: sys.board.bodies.getBodyAssociations(),
|
|
282
290
|
chlorinators: sys.chlorinators.get(),
|
|
283
|
-
maxChlorinators: sys.equipment.maxChlorinators
|
|
291
|
+
maxChlorinators: sys.equipment.maxChlorinators,
|
|
292
|
+
models: sys.board.valueMaps.chlorinatorModel.toArray(),
|
|
293
|
+
equipmentMasters: sys.board.valueMaps.equipmentMaster.toArray()
|
|
284
294
|
};
|
|
285
295
|
return res.status(200).send(opts);
|
|
286
296
|
});
|
|
@@ -305,13 +315,16 @@ export class ConfigRoute {
|
|
|
305
315
|
let opts = {
|
|
306
316
|
interfaces: config.getSection('web.interfaces'),
|
|
307
317
|
types: [
|
|
308
|
-
{name: '
|
|
309
|
-
{name: '
|
|
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' }
|
|
310
323
|
],
|
|
311
324
|
protocols: [
|
|
312
325
|
{ val: 0, name: 'http://', desc: 'http://' },
|
|
313
326
|
{ val: 1, name: 'https://', desc: 'https://' },
|
|
314
|
-
{ val: 2, name: 'mqtt://', desc: 'mqtt://' }
|
|
327
|
+
{ val: 2, name: 'mqtt://', desc: 'mqtt://' }
|
|
315
328
|
]
|
|
316
329
|
}
|
|
317
330
|
return res.status(200).send(opts);
|
|
@@ -330,6 +343,8 @@ export class ConfigRoute {
|
|
|
330
343
|
bodies: sys.board.bodies.getBodyAssociations(),
|
|
331
344
|
filters: sys.filters.get(),
|
|
332
345
|
areaUnits: sys.board.valueMaps.areaUnits.toArray(),
|
|
346
|
+
pressureUnits: sys.board.valueMaps.pressureUnits.toArray(),
|
|
347
|
+
circuits: sys.board.circuits.getCircuitReferences(true, true, true, false),
|
|
333
348
|
servers: []
|
|
334
349
|
};
|
|
335
350
|
if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
|
|
@@ -338,14 +353,14 @@ export class ConfigRoute {
|
|
|
338
353
|
});
|
|
339
354
|
/******* END OF CONFIGURATION PICK LISTS/REFERENCES AND VALIDATION ***********/
|
|
340
355
|
/******* ENDPOINTS FOR MODIFYING THE OUTDOOR CONTROL PANEL SETTINGS **********/
|
|
341
|
-
app.put('/config/rem', async (req, res, next)=>{
|
|
356
|
+
app.put('/config/rem', async (req, res, next) => {
|
|
342
357
|
try {
|
|
343
358
|
// RSG: this is problematic because we now enable multiple rem type interfaces that may not be called REM.
|
|
344
359
|
// This is now also a dupe of PUT /app/interface and should be consolidated
|
|
345
360
|
// config.setSection('web.interfaces.rem', req.body);
|
|
346
361
|
config.setInterface(req.body);
|
|
347
362
|
}
|
|
348
|
-
catch (err) {next(err);}
|
|
363
|
+
catch (err) { next(err); }
|
|
349
364
|
})
|
|
350
365
|
app.put('/config/tempSensors', async (req, res, next) => {
|
|
351
366
|
try {
|
|
@@ -360,7 +375,7 @@ export class ConfigRoute {
|
|
|
360
375
|
});
|
|
361
376
|
app.put('/config/filter', async (req, res, next) => {
|
|
362
377
|
try {
|
|
363
|
-
let sfilter = sys.board.filters.
|
|
378
|
+
let sfilter = await sys.board.filters.setFilterAsync(req.body);
|
|
364
379
|
return res.status(200).send(sfilter.get(true));
|
|
365
380
|
}
|
|
366
381
|
catch (err) { next(err); }
|
|
@@ -373,7 +388,7 @@ export class ConfigRoute {
|
|
|
373
388
|
});
|
|
374
389
|
app.delete('/config/filter', async (req, res, next) => {
|
|
375
390
|
try {
|
|
376
|
-
let sfilter = sys.board.filters.
|
|
391
|
+
let sfilter = await sys.board.filters.deleteFilterAsync(req.body);
|
|
377
392
|
return res.status(200).send(sfilter.get(true));
|
|
378
393
|
}
|
|
379
394
|
catch (err) { next(err); }
|
|
@@ -587,7 +602,7 @@ export class ConfigRoute {
|
|
|
587
602
|
});
|
|
588
603
|
app.get('/config/circuit/:id/lightThemes', (req, res) => {
|
|
589
604
|
let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
|
|
590
|
-
let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes() : [];
|
|
605
|
+
let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes(circuit.type) : [];
|
|
591
606
|
return res.status(200).send(themes);
|
|
592
607
|
});
|
|
593
608
|
app.get('/config/chlorinator/:id', (req, res) => {
|
|
@@ -725,7 +740,7 @@ export class ConfigRoute {
|
|
|
725
740
|
// RSG: is this and /config/circuit/:id/lightThemes both needed?
|
|
726
741
|
|
|
727
742
|
// if (sys.controllerType === ControllerType.IntelliCenter) {
|
|
728
|
-
let grp = sys.lightGroups.getItemById(parseInt(req.
|
|
743
|
+
let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
|
|
729
744
|
return res.status(200).send(grp.getLightThemes());
|
|
730
745
|
// }
|
|
731
746
|
// else
|
|
@@ -758,16 +773,16 @@ export class ConfigRoute {
|
|
|
758
773
|
let grp = sys.circuitGroups.getItemById(parseInt(req.params.id, 10));
|
|
759
774
|
return res.status(200).send(grp.getExtended());
|
|
760
775
|
});
|
|
761
|
-
/* app.get('/config/chemController/search', async (req, res, next) => {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
+
}); */
|
|
771
786
|
app.put('/config/chemController', async (req, res, next) => {
|
|
772
787
|
try {
|
|
773
788
|
let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
|
|
@@ -790,17 +805,17 @@ export class ConfigRoute {
|
|
|
790
805
|
catch (err) { next(err); }
|
|
791
806
|
|
|
792
807
|
});
|
|
793
|
-
/* app.get('/config/intellibrite', (req, res) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
+
}); */
|
|
804
819
|
app.get('/config', (req, res) => {
|
|
805
820
|
return res.status(200).send(sys.getSection('all'));
|
|
806
821
|
});
|
|
@@ -826,11 +841,18 @@ export class ConfigRoute {
|
|
|
826
841
|
return res.status(200).send('OK');
|
|
827
842
|
});
|
|
828
843
|
app.put('/app/interface', async (req, res, next) => {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
844
|
+
try {
|
|
845
|
+
let iface = await webApp.updateServerInterface(req.body);
|
|
846
|
+
return res.status(200).send(iface);
|
|
847
|
+
}
|
|
848
|
+
catch (err) { next(err); }
|
|
849
|
+
});
|
|
850
|
+
app.put('/app/rs485Port', async (req, res, next) => {
|
|
851
|
+
try {
|
|
852
|
+
let port = await conn.setPortAsync(req.body);
|
|
853
|
+
return res.status(200).send(port);
|
|
854
|
+
}
|
|
855
|
+
catch (err) { next(err); }
|
|
834
856
|
});
|
|
835
857
|
app.get('/app/config/startPacketCapture', (req, res) => {
|
|
836
858
|
startPacketCapture(true);
|
|
@@ -840,15 +862,129 @@ export class ConfigRoute {
|
|
|
840
862
|
startPacketCapture(false);
|
|
841
863
|
return res.status(200).send('OK');
|
|
842
864
|
});
|
|
843
|
-
app.get('/app/config/stopPacketCapture', async (req, res,next) => {
|
|
865
|
+
app.get('/app/config/stopPacketCapture', async (req, res, next) => {
|
|
844
866
|
try {
|
|
845
867
|
let file = await stopPacketCaptureAsync();
|
|
846
868
|
res.download(file);
|
|
847
869
|
}
|
|
848
|
-
catch (err) {next(err);}
|
|
870
|
+
catch (err) { next(err); }
|
|
849
871
|
});
|
|
850
872
|
app.get('/app/config/:section', (req, res) => {
|
|
851
873
|
return res.status(200).send(config.getSection(req.params.section));
|
|
852
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
|
+
});
|
|
853
989
|
}
|
|
854
990
|
}
|