Skip to content

Commit 42e4759

Browse files
committed
feat(settings): implement hot settings reload
1 parent 19d161b commit 42e4759

File tree

9 files changed

+123
-54
lines changed

9 files changed

+123
-54
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/dist
55
/tmp
66
/out-tsc
7+
/bin
78

89
# dependencies
910
/node_modules

lib/api.js

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,82 @@
1-
const os = require('os');
2-
const ip = require('ip');
3-
const async = require('async');
4-
const express = require('express');
5-
const bacnet = require('bacstack');
6-
const debug = require('debug')('bacstack-browser');
1+
const os = require('os');
2+
const ip = require('ip');
3+
const async = require('async');
4+
const express = require('express');
5+
const bodyParser = require('body-parser')
6+
const bacnet = require('bacstack');
7+
const debug = require('debug')('bacstack-browser');
78

89
const utils = require('./utils');
910

1011
const app = express();
1112

1213
// Local stores
14+
const options = utils.getSettings();
1315
const settings = {
14-
port: 47808,
15-
nic: 12,
16-
timeout: 4000,
17-
language: 'en',
18-
analytics: true
16+
port: options.port || 47808,
17+
nic: options.nic || 0,
18+
timeout: options.timeout || 4000,
19+
language: options.language || 'en',
20+
noAnalytics: options.noAnalytics
1921
};
2022

2123
const devices = {};
24+
const nics = [];
25+
26+
// NIC Stuff
27+
const getNics = () => {
28+
const osNics = os.networkInterfaces();
29+
nics.push({name: 'Default'});
30+
Object.keys(osNics).forEach((ifname) => {
31+
osNics[ifname].forEach((iface) => {
32+
if (iface.interal === true) return;
33+
if (iface.family !== 'IPv4') return;
34+
nics.push({
35+
name: ifname,
36+
address: iface.address,
37+
broadcast: ip.subnet(iface.address, iface.netmask).broadcastAddress
38+
});
39+
});
40+
});
41+
};
42+
getNics();
2243

2344
// BACNET Stuff
24-
const client = new bacnet({adpuTimeout: 6000});
45+
let client;
2546

26-
client.on('iAm', (device) => {
27-
const id = `${device.address}:${device.deviceId}`;
28-
devices[id] = device;
29-
devices[id].id = id;
30-
client.readPropertyMultiple(device.address, [
31-
{objectId: {type: 8, instance: 4194303}, properties: [{id: bacnet.enum.PropertyIds.PROP_OBJECT_NAME}, {id: bacnet.enum.PropertyIds.PROP_DESCRIPTION}]}
32-
], (err, value) => {
33-
if (err) return;
34-
if (value && value.values && value.values[0] && value.values[0].values) {
35-
const tmp = {};
36-
value.values[0].values.forEach(data => tmp[data.id] = data.value[0])
37-
devices[id].name = tmp[bacnet.enum.PropertyIds.PROP_OBJECT_NAME].value;
38-
devices[id].description = tmp[bacnet.enum.PropertyIds.PROP_DESCRIPTION].value;
39-
}
47+
const startBacnet = () => {
48+
client = new bacnet({
49+
port: settings.port,
50+
interface: (nics[settings.nic] || {}).address,
51+
broadcastAddress: (nics[settings.nic] || {}).broadcast,
52+
adpuTimeout: settings.timeout
4053
});
41-
});
54+
client.on('iAm', (device) => {
55+
const id = `${device.address}:${device.deviceId}`;
56+
devices[id] = device;
57+
devices[id].id = id;
58+
client.readPropertyMultiple(device.address, [
59+
{objectId: {type: 8, instance: 4194303}, properties: [{id: bacnet.enum.PropertyIds.PROP_OBJECT_NAME}, {id: bacnet.enum.PropertyIds.PROP_DESCRIPTION}]}
60+
], (err, value) => {
61+
if (err) return;
62+
if (value && value.values && value.values[0] && value.values[0].values) {
63+
const tmp = {};
64+
value.values[0].values.forEach(data => tmp[data.id] = data.value[0])
65+
devices[id].name = tmp[bacnet.enum.PropertyIds.PROP_OBJECT_NAME].value;
66+
devices[id].description = tmp[bacnet.enum.PropertyIds.PROP_DESCRIPTION].value;
67+
}
68+
});
69+
});
70+
}
71+
72+
const stopBacnet = () => {
73+
if (client) client.close();
74+
client = undefined;
75+
}
4276

4377
// Webserver Stuff
78+
app.use(bodyParser.json());
79+
4480
app.use((req, res, next) => {
4581
res.header('Access-Control-Allow-Origin', '*');
4682
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
@@ -54,23 +90,20 @@ app.get('/api/settings', (req, res) => {
5490
});
5591

5692
app.post('/api/settings', (req, res) => {
57-
res.send(req.body);
93+
settings.port = req.body.port || settings.port;
94+
settings.nic = req.body.nic || settings.nic;
95+
settings.timeout = req.body.timeout || settings.timeout;
96+
settings.language = req.body.language || settings.language;
97+
settings.noAnalytics = req.body.noAnalytics || settings.noAnalytics;
98+
utils.setSettings(settings, (err) => {
99+
if (err) return res.sendStatus(400);
100+
stopBacnet();
101+
startBacnet();
102+
res.send(settings);
103+
});
58104
});
59105

60106
app.get('/api/settings/interfaces', (req, res) => {
61-
const nics = [];
62-
const osNics = os.networkInterfaces();
63-
Object.keys(osNics).forEach((ifname) => {
64-
osNics[ifname].forEach((iface) => {
65-
if (iface.interal === true) return;
66-
if (iface.family !== 'IPv4') return;
67-
nics.push({
68-
name: ifname,
69-
address: iface.address,
70-
broadcast: ip.subnet(iface.address, iface.netmask).broadcastAddress
71-
});
72-
});
73-
});
74107
res.send(nics);
75108
});
76109

@@ -141,3 +174,5 @@ app.get('/api/devices/:id/objects/:oid', (req, res) => {
141174
app.listen(3000, '127.0.0.1', () => {
142175
console.log('Example app listening on port 3000!');
143176
});
177+
178+
startBacnet();

lib/utils.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1+
const fs = require('fs');
2+
const os = require('os');
3+
const path = require('path');
14
const _ = require('lodash');
25
const bacnet = require('bacstack');
36

7+
const configFile = '.nodebacstackbrowser';
8+
49
module.exports.getPropertyName = (id) => {
510
let name = Object.keys(bacnet.enum.PropertyIds).find(key => bacnet.enum.PropertyIds[key] === id);
611
if (!name) return;
712
name = _.startCase(_.lowerCase(name.substring(5)));
813
return name;
914
};
15+
16+
module.exports.getSettings = () => {
17+
try {
18+
return JSON.parse(fs.readFileSync(path.join(os.homedir(), configFile), {encoding: 'utf8'}));
19+
} catch (err) {
20+
return {};
21+
}
22+
};
23+
24+
module.exports.setSettings = (settings, next) => {
25+
fs.writeFile(path.join(os.homedir(), configFile), JSON.stringify(settings), next);
26+
};

main.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
const { app, BrowserWindow } = require('electron');
22

3+
const path = require('path');
4+
const url = require('url');
5+
36
const api = require('./lib/api');
47

58
let win;
69

7-
app.on('ready', () => {
10+
const createWindow = () => {
811
win = new BrowserWindow({width: 1000, height: 600});
912

10-
win.loadURL('http://localhost:4200');
13+
//win.loadURL('http://localhost:4200');
1114

12-
win.webContents.openDevTools();
15+
win.loadURL(url.format({
16+
pathname: path.join(__dirname, 'dist' , 'index.html'),
17+
protocol: 'file:',
18+
slashes: true
19+
}));
1320

1421
win.on('closed', () => {
1522
win = null;
1623
});
17-
});
24+
};
25+
26+
app.on('ready', createWindow);
1827

1928
app.on('activate', () => {
2029
if (!win) createWindow();

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"build": "ng build --prod",
1111
"test": "ng test",
1212
"lint": "ng lint",
13-
"e2e": "ng e2e"
13+
"e2e": "ng e2e",
14+
"pack": "npm run build && electron-packager . --platform=darwin --arch=x64 --overwrite --out=bin"
1415
},
1516
"repository": {
1617
"type": "git",
@@ -51,6 +52,7 @@
5152
"@angular/router": "^5.0.0",
5253
"async": "^2.6.0",
5354
"bacstack": "0.0.1-beta.13",
55+
"body-parser": "^1.18.2",
5456
"core-js": "^2.4.1",
5557
"debug": "^3.1.0",
5658
"express": "^4.16.2",
@@ -72,6 +74,7 @@
7274
"@types/node": "~6.0.60",
7375
"codelyzer": "^4.0.1",
7476
"electron": "^1.7.10",
77+
"electron-packager": "^10.1.0",
7578
"jasmine-core": "~2.6.2",
7679
"jasmine-spec-reporter": "~4.1.0",
7780
"karma": "~1.7.0",

src/app/pages/settings/settings.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ <h1>Settings</h1>
2424
<a tabindex="0" role="button" data-toggle="popover" data-trigger="focus" data-html="true" title="" data-content="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et <a href='#'>dolore magna aliqua</a>." data-placement="top"><span class="fa fa-info-circle"></span></a></label>
2525
<div class="col-sm-9">
2626
<select id="help-2" class="combobox form-control" [(ngModel)]="settings.nic" (change)="saveSettings()">
27-
<option *ngFor="let nic of nics" value="{{ nic.id }}">{{ nic.name }}</option>
27+
<option *ngFor="let nic of nics; let i = index" value="{{ i }}">{{ nic.name }} - {{ nic.address || '1.1.1.1' }}</option>
2828
</select>
2929
</div>
3030
</div>

src/app/pages/settings/settings.component.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import { ApiService } from '../../services/api.service';
1010
export class SettingsComponent implements OnInit {
1111
constructor(private api: ApiService) { }
1212

13-
nics = [
14-
{name: 'en0 - 192.168.178.3', id: '12'},
15-
{name: 'lo0 - 127.0.0.1', id: '13'}
16-
];
13+
nics = [];
1714

1815
ports = [
1916
{name: 'BAC0', value: 47808},
@@ -49,8 +46,11 @@ export class SettingsComponent implements OnInit {
4946
statusMessage: any;
5047

5148
ngOnInit() {
52-
this.api.getSettings().subscribe(res => {
53-
this.settings = res;
49+
this.api.getNics().subscribe(res1 => {
50+
this.nics = res1;
51+
this.api.getSettings().subscribe(res2 => {
52+
this.settings = res2;
53+
});
5454
});
5555
}
5656

src/app/services/api.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export class ApiService {
1515
return this.http.get(`${this.baseUrl}/devices/search`);
1616
}
1717

18+
getNics(): any {
19+
return this.http.get(`${this.baseUrl}/settings/interfaces`);
20+
}
21+
1822
getSettings(): any {
1923
return this.http.get(`${this.baseUrl}/settings`);
2024
}

src/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="utf-8">
55
<title>Node BACStack Browser</title>
6-
<base href="/">
6+
<base href="">
77

88
<meta name="viewport" content="width=device-width, initial-scale=1">
99
</head>

0 commit comments

Comments
 (0)