Removing ng-di on the server.
This commit is contained in:
parent
ddb2f47a9d
commit
8697d79ba5
|
@ -33,7 +33,6 @@
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"ng-admin": "^1.0.13",
|
"ng-admin": "^1.0.13",
|
||||||
"ng-di": "^0.2.1",
|
|
||||||
"node-cron": "^2.0.1",
|
"node-cron": "^2.0.1",
|
||||||
"nodemailer": "^4.6.8",
|
"nodemailer": "^4.6.8",
|
||||||
"nodemailer-html-to-text": "^3.0.0",
|
"nodemailer-html-to-text": "^3.0.0",
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('app', function (fs, config, _) {
|
const _ = require('lodash')
|
||||||
var express = require('express');
|
const auth = require('http-auth');
|
||||||
var auth = require('http-auth');
|
const bodyParser = require('body-parser');
|
||||||
var bodyParser = require('body-parser');
|
const compress = require('compression');
|
||||||
var compress = require('compression');
|
const express = require('express');
|
||||||
|
const fs = require('graceful-fs')
|
||||||
|
|
||||||
var app = express();
|
const config = require('./config').config
|
||||||
var router = express.Router();
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
module.exports = (() => {
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
// urls beneath /internal are protected
|
// urls beneath /internal are protected
|
||||||
var internalAuth = auth.basic(
|
const internalAuth = auth.basic(
|
||||||
{
|
{
|
||||||
realm: 'Knotenformular - Intern'
|
realm: 'Knotenformular - Intern'
|
||||||
},
|
},
|
||||||
|
@ -27,24 +32,24 @@ angular.module('ffffng').factory('app', function (fs, config, _) {
|
||||||
router.use(bodyParser.json());
|
router.use(bodyParser.json());
|
||||||
router.use(bodyParser.urlencoded({ extended: true }));
|
router.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
var adminDir = __dirname + '/../admin';
|
const adminDir = __dirname + '/../admin';
|
||||||
var clientDir = __dirname + '/../client';
|
const clientDir = __dirname + '/../client';
|
||||||
var templateDir = __dirname + '/templates';
|
const templateDir = __dirname + '/templates';
|
||||||
|
|
||||||
var jsTemplateFiles = [
|
const jsTemplateFiles = [
|
||||||
'/config.js'
|
'/config.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
router.use(compress());
|
router.use(compress());
|
||||||
|
|
||||||
function serveTemplate(mimeType, req, res, next) {
|
function serveTemplate (mimeType, req, res, next) {
|
||||||
return fs.readFile(templateDir + '/' + req.path, 'utf8', function (err, body) {
|
return fs.readFile(templateDir + '/' + req.path, 'utf8', function (err, body) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeHead(200, {'Content-Type': mimeType});
|
res.writeHead(200, { 'Content-Type': mimeType });
|
||||||
res.end(_.template(body)( { config: config.client }));
|
res.end(_.template(body)({ config: config.client }));
|
||||||
|
|
||||||
return null; // to suppress warning
|
return null; // to suppress warning
|
||||||
});
|
});
|
||||||
|
@ -63,4 +68,4 @@ angular.module('ffffng').factory('app', function (fs, config, _) {
|
||||||
app.use(config.server.rootPath, router);
|
app.use(config.server.rootPath, router);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
});
|
})()
|
||||||
|
|
258
server/config.js
258
server/config.js
|
@ -1,156 +1,158 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var commandLineArgs = require('command-line-args');
|
module.exports = (() => {
|
||||||
var commandLineUsage = require('command-line-usage');
|
const commandLineArgs = require('command-line-args');
|
||||||
|
const commandLineUsage = require('command-line-usage');
|
||||||
|
|
||||||
var commandLineDefs = [
|
const commandLineDefs = [
|
||||||
{ name: 'help', alias: 'h', type: Boolean, description: 'Show this help' },
|
{ name: 'help', alias: 'h', type: Boolean, description: 'Show this help' },
|
||||||
{ name: 'config', alias: 'c', type: String, description: 'Location of config.json' },
|
{ name: 'config', alias: 'c', type: String, description: 'Location of config.json' },
|
||||||
{ name: 'version', alias: 'v', type: Boolean, description: 'Show ffffng version' }
|
{ name: 'version', alias: 'v', type: Boolean, description: 'Show ffffng version' }
|
||||||
];
|
];
|
||||||
|
|
||||||
var commandLineOptions;
|
let commandLineOptions;
|
||||||
try {
|
try {
|
||||||
commandLineOptions = commandLineArgs(commandLineDefs);
|
commandLineOptions = commandLineArgs(commandLineDefs);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
console.error(e.message);
|
||||||
console.error('Try \'--help\' for more information.');
|
console.error('Try \'--help\' for more information.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
|
|
||||||
var packageJsonFile = __dirname + '/../package.json';
|
const packageJsonFile = __dirname + '/../package.json';
|
||||||
var version = 'unknown';
|
let version = 'unknown';
|
||||||
if (fs.existsSync(packageJsonFile)) {
|
if (fs.existsSync(packageJsonFile)) {
|
||||||
version = JSON.parse(fs.readFileSync(packageJsonFile, 'utf8')).version;
|
version = JSON.parse(fs.readFileSync(packageJsonFile, 'utf8')).version;
|
||||||
}
|
}
|
||||||
|
|
||||||
function usage() {
|
function usage () {
|
||||||
console.log(commandLineUsage([
|
console.log(commandLineUsage([
|
||||||
{
|
{
|
||||||
header: 'ffffng - ' + version + ' - Freifunk node management form',
|
header: 'ffffng - ' + version + ' - Freifunk node management form',
|
||||||
optionList: commandLineDefs
|
optionList: commandLineDefs
|
||||||
}
|
}
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commandLineOptions.help) {
|
if (commandLineOptions.help) {
|
||||||
usage();
|
usage();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commandLineOptions.version) {
|
if (commandLineOptions.version) {
|
||||||
console.log('ffffng - ' + version);
|
console.log('ffffng - ' + version);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commandLineOptions.config) {
|
if (!commandLineOptions.config) {
|
||||||
usage();
|
usage();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var deepExtend = require('deep-extend');
|
const deepExtend = require('deep-extend');
|
||||||
|
|
||||||
var defaultConfig = {
|
const defaultConfig = {
|
||||||
server: {
|
server: {
|
||||||
baseUrl: 'http://localhost:8080',
|
baseUrl: 'http://localhost:8080',
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
|
||||||
databaseFile: '/tmp/ffffng.sqlite',
|
databaseFile: '/tmp/ffffng.sqlite',
|
||||||
peersPath: '/tmp/peers',
|
peersPath: '/tmp/peers',
|
||||||
|
|
||||||
logging: {
|
logging: {
|
||||||
directory: '/tmp/logs',
|
directory: '/tmp/logs',
|
||||||
debug: false,
|
debug: false,
|
||||||
profile: false,
|
profile: false,
|
||||||
logRequests: false
|
logRequests: false
|
||||||
},
|
},
|
||||||
|
|
||||||
internal: {
|
internal: {
|
||||||
active: false,
|
active: false,
|
||||||
user: 'admin',
|
user: 'admin',
|
||||||
password: 'secret'
|
password: 'secret'
|
||||||
},
|
},
|
||||||
|
|
||||||
email: {
|
email: {
|
||||||
from: 'Freifunk Knotenformular <no-reply@musterstadt.freifunk.net>',
|
from: 'Freifunk Knotenformular <no-reply@musterstadt.freifunk.net>',
|
||||||
|
|
||||||
// For details see: https://nodemailer.com/2-0-0-beta/setup-smtp/
|
// For details see: https://nodemailer.com/2-0-0-beta/setup-smtp/
|
||||||
smtp: {
|
smtp: {
|
||||||
host: 'mail.example.com',
|
host: 'mail.example.com',
|
||||||
port: '465',
|
port: '465',
|
||||||
secure: true,
|
secure: true,
|
||||||
auth: {
|
auth: {
|
||||||
user: 'user@example.com',
|
user: 'user@example.com',
|
||||||
pass: 'pass'
|
pass: 'pass'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
map: {
|
||||||
|
nodesJsonUrl: ['http://map.musterstadt.freifunk.net/nodes.json']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
client: {
|
||||||
map: {
|
community: {
|
||||||
nodesJsonUrl: ['http://map.musterstadt.freifunk.net/nodes.json']
|
name: 'Freifunk Musterstadt',
|
||||||
|
domain: 'musterstadt.freifunk.net',
|
||||||
|
contactEmail: 'kontakt@musterstadt.freifunk.net',
|
||||||
|
sites: [],
|
||||||
|
domains: []
|
||||||
|
},
|
||||||
|
legal: {
|
||||||
|
privacyUrl: null,
|
||||||
|
imprintUrl: null
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
mapUrl: 'http://map.musterstadt.freifunk.net'
|
||||||
|
},
|
||||||
|
monitoring: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
coordsSelector: {
|
||||||
|
showInfo: false,
|
||||||
|
showBorderForDebugging: false,
|
||||||
|
localCommunityPolygon: [],
|
||||||
|
lat: 53.565278,
|
||||||
|
lng: 10.001389,
|
||||||
|
defaultZoom: 10,
|
||||||
|
layers: {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
client: {
|
|
||||||
community: {
|
const configJSONFile = commandLineOptions.config;
|
||||||
name: 'Freifunk Musterstadt',
|
let configJSON = {};
|
||||||
domain: 'musterstadt.freifunk.net',
|
|
||||||
contactEmail: 'kontakt@musterstadt.freifunk.net',
|
if (fs.existsSync(configJSONFile)) {
|
||||||
sites: [],
|
configJSON = JSON.parse(fs.readFileSync(configJSONFile, 'utf8'));
|
||||||
domains: []
|
} else {
|
||||||
},
|
console.error('config.json not found: ' + configJSONFile);
|
||||||
legal: {
|
process.exit(1);
|
||||||
privacyUrl: null,
|
}
|
||||||
imprintUrl: null
|
|
||||||
},
|
const _ = require('lodash');
|
||||||
map: {
|
|
||||||
mapUrl: 'http://map.musterstadt.freifunk.net'
|
function stripTrailingSlash (obj, field) {
|
||||||
},
|
const url = obj[field];
|
||||||
monitoring: {
|
if (_.isString(url) && _.last(url) === '/') {
|
||||||
enabled: false
|
obj[field] = url.substr(0, url.length - 1);
|
||||||
},
|
|
||||||
coordsSelector: {
|
|
||||||
showInfo: false,
|
|
||||||
showBorderForDebugging: false,
|
|
||||||
localCommunityPolygon: [],
|
|
||||||
lat: 53.565278,
|
|
||||||
lng: 10.001389,
|
|
||||||
defaultZoom: 10,
|
|
||||||
layers: {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var configJSONFile = commandLineOptions.config;
|
const config = deepExtend({}, defaultConfig, configJSON);
|
||||||
var configJSON = {};
|
|
||||||
|
|
||||||
if (fs.existsSync(configJSONFile)) {
|
stripTrailingSlash(config.server, 'baseUrl');
|
||||||
configJSON = JSON.parse(fs.readFileSync(configJSONFile, 'utf8'));
|
stripTrailingSlash(config.client.map, 'mapUrl');
|
||||||
} else {
|
|
||||||
console.error('config.json not found: ' + configJSONFile);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
const url = require('url');
|
||||||
|
config.server.rootPath = url.parse(config.server.baseUrl).pathname;
|
||||||
|
config.client.rootPath = config.server.rootPath;
|
||||||
|
|
||||||
function stripTrailingSlash(obj, field) {
|
return {
|
||||||
var url = obj[field];
|
config,
|
||||||
if (_.isString(url) && _.last(url) === '/') {
|
version
|
||||||
obj[field] = url.substr(0, url.length - 1);
|
|
||||||
}
|
}
|
||||||
}
|
})()
|
||||||
|
|
||||||
var config = deepExtend({}, defaultConfig, configJSON);
|
|
||||||
|
|
||||||
stripTrailingSlash(config.server, 'baseUrl');
|
|
||||||
stripTrailingSlash(config.client.map, 'mapUrl');
|
|
||||||
|
|
||||||
var url = require('url');
|
|
||||||
config.server.rootPath = url.parse(config.server.baseUrl).pathname;
|
|
||||||
config.client.rootPath = config.server.rootPath;
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
|
|
||||||
angular.module('ffffng').constant('config', config);
|
|
||||||
angular.module('ffffng').constant('version', version);
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const fs = require('graceful-fs');
|
||||||
const glob = util.promisify(require('glob'));
|
const glob = util.promisify(require('glob'));
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const config = require('../config');
|
const config = require('../config').config;
|
||||||
const Logger = require('../logger');
|
const Logger = require('../logger');
|
||||||
|
|
||||||
async function applyPatch(db, file) {
|
async function applyPatch(db, file) {
|
||||||
|
@ -72,10 +72,7 @@ async function init() {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: We have to use funtion() syntax here, to satisfy ng-di. m(
|
module.exports.db = db;
|
||||||
return angular.module('ffffng').factory('Database', function () {
|
|
||||||
return db;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
18
server/jobs/FixNodeFilenamesJob.js
Normal file
18
server/jobs/FixNodeFilenamesJob.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const NodeService = require('../services/nodeService')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
description: 'Makes sure node files (holding fastd key, name, etc.) are correctly named.',
|
||||||
|
|
||||||
|
run: function (callback) {
|
||||||
|
NodeService.fixNodeFilenames(function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('nodes', 'fix-filenames').error('Error fixing filenames:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
18
server/jobs/MailQueueJob.js
Normal file
18
server/jobs/MailQueueJob.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const MailService = require('../services/mailService')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
description: 'Send pending emails (up to 5 attempts in case of failures).',
|
||||||
|
|
||||||
|
run: function (callback) {
|
||||||
|
MailService.sendPendingMails(function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('mail', 'queue').error('Error sending pending mails:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
18
server/jobs/MonitoringMailsSendingJob.js
Normal file
18
server/jobs/MonitoringMailsSendingJob.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const MonitoringService = require('../services/monitoringService')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
description: 'Sends monitoring emails depending on the monitoring state of nodes retrieved by the NodeInformationRetrievalJob.',
|
||||||
|
|
||||||
|
run: function (callback) {
|
||||||
|
MonitoringService.sendMonitoringMails(function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('monitoring', 'mail-sending').error('Error sending monitoring mails:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
18
server/jobs/NodeInformationRetrievalJob.js
Normal file
18
server/jobs/NodeInformationRetrievalJob.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const MonitoringService = require('../services/monitoringService')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
description: 'Fetches the nodes.json and calculates and stores the monitoring / online status for registered nodes.',
|
||||||
|
|
||||||
|
run: function (callback) {
|
||||||
|
MonitoringService.retrieveNodeInformation(function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('monitoring', 'information-retrieval').error('Error retrieving node data:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
18
server/jobs/OfflineNodesDeletionJob.js
Normal file
18
server/jobs/OfflineNodesDeletionJob.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const MonitoringService = require('../services/monitoringService')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
description: 'Delete nodes that are offline for more than 100 days.',
|
||||||
|
|
||||||
|
run: function (callback) {
|
||||||
|
MonitoringService.deleteOfflineNodes(function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('nodes', 'delete-offline').error('Error deleting offline nodes:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('ffffng').factory('FixNodeFilenamesJob', function (NodeService, Logger) {
|
|
||||||
return {
|
|
||||||
description: 'Makes sure node files (holding fastd key, name, etc.) are correctly named.',
|
|
||||||
|
|
||||||
run: function (callback) {
|
|
||||||
NodeService.fixNodeFilenames(function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('nodes', 'fix-filenames').error('Error fixing filenames:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('ffffng').factory('MailQueueJob', function (MailService, Logger) {
|
|
||||||
return {
|
|
||||||
description: 'Send pending emails (up to 5 attempts in case of failures).',
|
|
||||||
|
|
||||||
run: function (callback) {
|
|
||||||
MailService.sendPendingMails(function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('mail', 'queue').error('Error sending pending mails:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('ffffng').factory('MonitoringMailsSendingJob', function (MonitoringService, Logger) {
|
|
||||||
return {
|
|
||||||
description: 'Sends monitoring emails depending on the monitoring state of nodes retrieved by the NodeInformationRetrievalJob.',
|
|
||||||
|
|
||||||
run: function (callback) {
|
|
||||||
MonitoringService.sendMonitoringMails(function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('monitoring', 'mail-sending').error('Error sending monitoring mails:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('ffffng').factory('NodeInformationRetrievalJob', function (MonitoringService, Logger) {
|
|
||||||
return {
|
|
||||||
description: 'Fetches the nodes.json and calculates and stores the monitoring / online status for registered nodes.',
|
|
||||||
|
|
||||||
run: function (callback) {
|
|
||||||
MonitoringService.retrieveNodeInformation(function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('monitoring', 'information-retrieval').error('Error retrieving node data:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('ffffng').factory('OfflineNodesDeletionJob', function (MonitoringService, Logger) {
|
|
||||||
return {
|
|
||||||
description: 'Delete nodes that are offline for more than 100 days.',
|
|
||||||
|
|
||||||
run: function (callback) {
|
|
||||||
MonitoringService.deleteOfflineNodes(function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('nodes', 'delete-offline').error('Error deleting offline nodes:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,98 +1,99 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var glob = require('glob');
|
const _ = require('lodash');
|
||||||
var _ = require('lodash');
|
const cron = require('node-cron');
|
||||||
|
const glob = require('glob');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
var jobFiles = glob.sync(__dirname + '/*Job.js');
|
const config = require('../config').config
|
||||||
|
const Logger = require('../logger')
|
||||||
|
|
||||||
|
const jobFiles = glob.sync(__dirname + '/*Job.js');
|
||||||
_.each(jobFiles, function (jobFile) {
|
_.each(jobFiles, function (jobFile) {
|
||||||
require(jobFile);
|
require(jobFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.module('ffffng').factory('Scheduler', function ($injector, Logger, config, moment) {
|
const tasks = {};
|
||||||
var cron = require('node-cron');
|
|
||||||
|
|
||||||
var tasks = {};
|
let taskId = 1;
|
||||||
|
function nextTaskId() {
|
||||||
|
const id = taskId;
|
||||||
|
taskId += 1;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
var taskId = 1;
|
function schedule(expr, jobName) {
|
||||||
function nextTaskId() {
|
Logger.tag('jobs').info('Scheduling job: %s %s', expr, jobName);
|
||||||
var id = taskId;
|
|
||||||
taskId += 1;
|
var job = require(`../jobs/${jobName}`);
|
||||||
return id;
|
|
||||||
|
if (!_.isFunction(job.run)) {
|
||||||
|
throw new Error('The job ' + jobName + ' does not provide a "run" function.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function schedule(expr, jobName) {
|
var id = nextTaskId();
|
||||||
Logger.tag('jobs').info('Scheduling job: %s %s', expr, jobName);
|
var task = {
|
||||||
|
id: id,
|
||||||
var job = $injector.get(jobName);
|
name: jobName,
|
||||||
|
description: job.description,
|
||||||
if (!_.isFunction(job.run)) {
|
schedule: expr,
|
||||||
throw new Error('The job ' + jobName + ' does not provide a "run" function.');
|
job: job,
|
||||||
}
|
runningSince: false,
|
||||||
|
lastRunStarted: false,
|
||||||
var id = nextTaskId();
|
lastRunDuration: null,
|
||||||
var task = {
|
state: 'idle',
|
||||||
id: id,
|
enabled: true
|
||||||
name: jobName,
|
|
||||||
description: job.description,
|
|
||||||
schedule: expr,
|
|
||||||
job: job,
|
|
||||||
runningSince: false,
|
|
||||||
lastRunStarted: false,
|
|
||||||
lastRunDuration: null,
|
|
||||||
state: 'idle',
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
|
|
||||||
task.run = function () {
|
|
||||||
if (task.runningSince || !task.enabled) {
|
|
||||||
// job is still running, skip execution
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
task.runningSince = moment();
|
|
||||||
task.lastRunStarted = task.runningSince;
|
|
||||||
task.state = 'running';
|
|
||||||
|
|
||||||
job.run(function () {
|
|
||||||
var now = moment();
|
|
||||||
var duration = now.diff(task.runningSince);
|
|
||||||
Logger.tag('jobs').profile('[%sms]\t%s', duration, task.name);
|
|
||||||
|
|
||||||
task.runningSince = false;
|
|
||||||
task.lastRunDuration = duration;
|
|
||||||
task.state = 'idle';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
cron.schedule(expr, task.run);
|
|
||||||
|
|
||||||
tasks['' + id] = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
Logger.tag('jobs').info('Scheduling background jobs...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
schedule('0 */1 * * * *', 'MailQueueJob');
|
|
||||||
schedule('15 */1 * * * *', 'FixNodeFilenamesJob');
|
|
||||||
|
|
||||||
if (config.client.monitoring.enabled) {
|
|
||||||
schedule('30 */15 * * * *', 'NodeInformationRetrievalJob');
|
|
||||||
schedule('45 */5 * * * *', 'MonitoringMailsSendingJob');
|
|
||||||
schedule('0 0 3 * * *', 'OfflineNodesDeletionJob'); // every night at 3:00
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
Logger.tag('jobs').error('Error during scheduling of background jobs:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.tag('jobs').info('Scheduling of background jobs done.');
|
|
||||||
},
|
|
||||||
|
|
||||||
getTasks: function () {
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
task.run = function () {
|
||||||
|
if (task.runningSince || !task.enabled) {
|
||||||
|
// job is still running, skip execution
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.runningSince = moment();
|
||||||
|
task.lastRunStarted = task.runningSince;
|
||||||
|
task.state = 'running';
|
||||||
|
|
||||||
|
job.run(function () {
|
||||||
|
var now = moment();
|
||||||
|
var duration = now.diff(task.runningSince);
|
||||||
|
Logger.tag('jobs').profile('[%sms]\t%s', duration, task.name);
|
||||||
|
|
||||||
|
task.runningSince = false;
|
||||||
|
task.lastRunDuration = duration;
|
||||||
|
task.state = 'idle';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cron.schedule(expr, task.run);
|
||||||
|
|
||||||
|
tasks['' + id] = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function () {
|
||||||
|
Logger.tag('jobs').info('Scheduling background jobs...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
schedule('0 */1 * * * *', 'MailQueueJob');
|
||||||
|
schedule('15 */1 * * * *', 'FixNodeFilenamesJob');
|
||||||
|
|
||||||
|
if (config.client.monitoring.enabled) {
|
||||||
|
schedule('30 */15 * * * *', 'NodeInformationRetrievalJob');
|
||||||
|
schedule('45 */5 * * * *', 'MonitoringMailsSendingJob');
|
||||||
|
schedule('0 0 3 * * *', 'OfflineNodesDeletionJob'); // every night at 3:00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
Logger.tag('jobs').error('Error during scheduling of background jobs:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.tag('jobs').info('Scheduling of background jobs done.');
|
||||||
|
},
|
||||||
|
|
||||||
|
getTasks: function () {
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
var module = angular.module('ffffng');
|
|
||||||
|
|
||||||
function lib(name, nodeModule) {
|
|
||||||
if (!nodeModule) {
|
|
||||||
nodeModule = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.factory(name, function () {
|
|
||||||
return require(nodeModule);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
lib('_', 'lodash');
|
|
||||||
lib('async');
|
|
||||||
lib('crypto');
|
|
||||||
lib('deepExtend', 'deep-extend');
|
|
||||||
lib('fs', 'graceful-fs');
|
|
||||||
lib('glob');
|
|
||||||
lib('moment');
|
|
||||||
lib('request');
|
|
||||||
})();
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var config = require('./config');
|
const app = require('./app');
|
||||||
|
const config = require('./config').config;
|
||||||
|
|
||||||
// Hack to allow proper logging of Error.
|
// Hack to allow proper logging of Error.
|
||||||
Object.defineProperty(Error.prototype, 'message', {
|
Object.defineProperty(Error.prototype, 'message', {
|
||||||
|
@ -14,7 +14,7 @@ Object.defineProperty(Error.prototype, 'stack', {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var scribe = require('scribe-js')({
|
const scribe = require('scribe-js')({
|
||||||
rootPath: config.server.logging.directory,
|
rootPath: config.server.logging.directory,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,30 +33,26 @@ function addLogger(name, color, active) {
|
||||||
addLogger('debug', 'grey', config.server.logging.debug);
|
addLogger('debug', 'grey', config.server.logging.debug);
|
||||||
addLogger('profile', 'blue', config.server.logging.profile);
|
addLogger('profile', 'blue', config.server.logging.profile);
|
||||||
|
|
||||||
angular.module('ffffng').factory('Logger', function (app) {
|
if (config.server.logging.logRequests) {
|
||||||
if (config.server.logging.logRequests) {
|
app.use(scribe.express.logger());
|
||||||
app.use(scribe.express.logger());
|
}
|
||||||
}
|
if (config.server.internal.active) {
|
||||||
if (config.server.internal.active) {
|
const prefix = config.server.rootPath === '/' ? '' : config.server.rootPath;
|
||||||
var prefix = config.server.rootPath === '/' ? '' : config.server.rootPath;
|
app.use(prefix + '/internal/logs', scribe.webPanel());
|
||||||
app.use(prefix + '/internal/logs', scribe.webPanel());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Hack to allow correct logging of node.js Error objects.
|
// Hack to allow correct logging of node.js Error objects.
|
||||||
// See: https://github.com/bluejamesbond/Scribe.js/issues/70
|
// See: https://github.com/bluejamesbond/Scribe.js/issues/70
|
||||||
Object.defineProperty(Error.prototype, 'toJSON', {
|
Object.defineProperty(Error.prototype, 'toJSON', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
value: function () {
|
value: function () {
|
||||||
var alt = {};
|
const alt = {};
|
||||||
var storeKey = function (key) {
|
const storeKey = function (key) {
|
||||||
alt[key] = this[key];
|
alt[key] = this[key];
|
||||||
};
|
};
|
||||||
Object.getOwnPropertyNames(this).forEach(storeKey, this);
|
Object.getOwnPropertyNames(this).forEach(storeKey, this);
|
||||||
return alt;
|
return alt;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return process.console;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = process.console;
|
module.exports = process.console;
|
||||||
|
|
|
@ -2,11 +2,6 @@
|
||||||
/*jslint node: true */
|
/*jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Dirty hack to allow usage of angular modules.
|
|
||||||
global.angular = require('ng-di');
|
|
||||||
|
|
||||||
angular.module('ffffng', []);
|
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
// Use graceful-fs instead of fs also in all libraries to have more robust fs handling.
|
// Use graceful-fs instead of fs also in all libraries to have more robust fs handling.
|
||||||
const realFs = require('fs');
|
const realFs = require('fs');
|
||||||
|
@ -14,52 +9,24 @@ angular.module('ffffng', []);
|
||||||
gracefulFs.gracefulify(realFs);
|
gracefulFs.gracefulify(realFs);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
require('./config');
|
const config = require('./config').config;
|
||||||
|
|
||||||
require('./logger').tag('main', 'startup').info('Server starting up...');
|
const Logger = require('./logger')
|
||||||
|
Logger.tag('main', 'startup').info('Server starting up...');
|
||||||
|
|
||||||
require('./app');
|
require('./db/database').init()
|
||||||
require('./router');
|
.then(() => {
|
||||||
require('./libs');
|
Logger.tag('main').info('Initializing...');
|
||||||
|
|
||||||
require('./utils/databaseUtil');
|
const app = require('./app');
|
||||||
require('./utils/errorTypes');
|
|
||||||
require('./utils/resources');
|
|
||||||
require('./utils/strings');
|
|
||||||
require('./utils/urlBuilder');
|
|
||||||
|
|
||||||
require('./resources/versionResource');
|
require('./jobs/scheduler').init();
|
||||||
require('./resources/statisticsResource');
|
require('./router').init();
|
||||||
require('./resources/frontendResource');
|
|
||||||
require('./resources/taskResource');
|
|
||||||
require('./resources/mailResource');
|
|
||||||
require('./resources/nodeResource');
|
|
||||||
require('./resources/monitoringResource');
|
|
||||||
|
|
||||||
require('./services/mailService');
|
app.listen(config.server.port, '::');
|
||||||
require('./services/mailTemplateService');
|
module.exports = app;
|
||||||
require('./services/nodeService');
|
})
|
||||||
require('./services/monitoringService');
|
.catch(error => {
|
||||||
|
|
||||||
require('../shared/validation/constraints');
|
|
||||||
require('./validation/validator');
|
|
||||||
|
|
||||||
require('./jobs/scheduler');
|
|
||||||
|
|
||||||
const db = require('./db/database');
|
|
||||||
|
|
||||||
db.init().then(() => {
|
|
||||||
// WARNING: We have to use funtion() syntax here, to satisfy ng-di. m(
|
|
||||||
angular.injector(['ffffng']).invoke(function (config, app, Logger, Scheduler, Router) {
|
|
||||||
Logger.tag('main').info('Initializing...');
|
|
||||||
|
|
||||||
Scheduler.init();
|
|
||||||
Router.init();
|
|
||||||
|
|
||||||
app.listen(config.server.port, '::');
|
|
||||||
module.exports = app;
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Could not init database: ', error);
|
console.error('Could not init database: ', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('FrontendResource', function (
|
const fs = require('graceful-fs')
|
||||||
Logger,
|
|
||||||
Resources,
|
|
||||||
ErrorTypes,
|
|
||||||
fs
|
|
||||||
) {
|
|
||||||
var indexHtml = __dirname + '/../../client/index.html';
|
|
||||||
|
|
||||||
return {
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
render: function (req, res) {
|
const Logger = require('../logger')
|
||||||
var data = Resources.getData(req);
|
const Resources = require('../utils/resources')
|
||||||
|
|
||||||
fs.readFile(indexHtml, 'utf8', function (err, body) {
|
const indexHtml = __dirname + '/../../client/index.html';
|
||||||
if (err) {
|
|
||||||
Logger.tag('frontend').error('Could not read file: ', indexHtml, err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Resources.successHtml(
|
module.exports = {
|
||||||
res,
|
render (req, res) {
|
||||||
body.replace(
|
const data = Resources.getData(req);
|
||||||
/<body/,
|
|
||||||
'<script>window.__nodeToken = \''+ data.token + '\';</script><body'
|
fs.readFile(indexHtml, 'utf8', function (err, body) {
|
||||||
)
|
if (err) {
|
||||||
);
|
Logger.tag('frontend').error('Could not read file: ', indexHtml, err);
|
||||||
});
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
return Resources.successHtml(
|
||||||
|
res,
|
||||||
|
body.replace(
|
||||||
|
/<body/,
|
||||||
|
'<script>window.__nodeToken = \''+ data.token + '\';</script><body'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,101 +1,99 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('MailResource', function (
|
const Constraints = require('../../shared/validation/constraints')
|
||||||
Constraints,
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
Validator,
|
const Logger = require('../logger')
|
||||||
MailService,
|
const MailService = require('../services/mailService')
|
||||||
Resources,
|
const Resources = require('../utils/resources')
|
||||||
Logger,
|
const Strings = require('../utils/strings')
|
||||||
ErrorTypes,
|
const Validator = require('../validation/validator')
|
||||||
Strings
|
|
||||||
) {
|
|
||||||
var isValidId = Validator.forConstraint(Constraints.id);
|
|
||||||
|
|
||||||
function withValidMailId(req, res, callback) {
|
const isValidId = Validator.forConstraint(Constraints.id);
|
||||||
var id = Strings.normalizeString(Resources.getData(req).id);
|
|
||||||
|
|
||||||
if (!isValidId(id)) {
|
function withValidMailId(req, res, callback) {
|
||||||
return callback({data: 'Invalid mail id.', type: ErrorTypes.badRequest});
|
const id = Strings.normalizeString(Resources.getData(req).id);
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, id);
|
if (!isValidId(id)) {
|
||||||
|
return callback({data: 'Invalid mail id.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
callback(null, id);
|
||||||
get: function (req, res) {
|
}
|
||||||
withValidMailId(req, res, function (err, id) {
|
|
||||||
|
module.exports = {
|
||||||
|
get (req, res) {
|
||||||
|
withValidMailId(req, res, function (err, id) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
MailService.getMail(id, function (err, mail) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
Logger.tag('mails', 'admin').error('Error getting mail:', err);
|
||||||
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
MailService.getMail(id, function (err, mail) {
|
if (!mail) {
|
||||||
|
return Resources.error(res, {data: 'Mail not found.', type: ErrorTypes.notFound});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resources.success(res, mail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAll (req, res) {
|
||||||
|
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MailService.getPendingMails(
|
||||||
|
restParams,
|
||||||
|
function (err, mails, total) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Logger.tag('mails', 'admin').error('Error getting mail:', err);
|
Logger.tag('mails', 'admin').error('Could not get pending mails:', err);
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mail) {
|
res.set('X-Total-Count', total);
|
||||||
return Resources.error(res, {data: 'Mail not found.', type: ErrorTypes.notFound});
|
return Resources.success(res, mails);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
return Resources.success(res, mail);
|
delete (req, res) {
|
||||||
});
|
withValidMailId(req, res, function (err, id) {
|
||||||
});
|
if (err) {
|
||||||
},
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
getAll: function (req, res) {
|
MailService.deleteMail(id, function (err) {
|
||||||
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
Logger.tag('mails', 'admin').error('Error deleting mail:', err);
|
||||||
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
return MailService.getPendingMails(
|
return Resources.success(res);
|
||||||
restParams,
|
|
||||||
function (err, mails, total) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('mails', 'admin').error('Could not get pending mails:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set('X-Total-Count', total);
|
|
||||||
return Resources.success(res, mails);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
|
},
|
||||||
|
|
||||||
delete: function (req, res) {
|
resetFailures (req, res) {
|
||||||
withValidMailId(req, res, function (err, id) {
|
withValidMailId(req, res, function (err, id) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
MailService.resetFailures(id, function (err, mail) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
Logger.tag('mails', 'admin').error('Error resetting failure count:', err);
|
||||||
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
MailService.deleteMail(id, function (err) {
|
return Resources.success(res, mail);
|
||||||
if (err) {
|
|
||||||
Logger.tag('mails', 'admin').error('Error deleting mail:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Resources.success(res);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
|
}
|
||||||
resetFailures: function (req, res) {
|
}
|
||||||
withValidMailId(req, res, function (err, id) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
MailService.resetFailures(id, function (err, mail) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('mails', 'admin').error('Error resetting failure count:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Resources.success(res, mail);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,83 +1,82 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('MonitoringResource', function (
|
const _ = require('lodash')
|
||||||
Constraints,
|
|
||||||
Validator,
|
|
||||||
MonitoringService,
|
|
||||||
Logger,
|
|
||||||
_,
|
|
||||||
Strings,
|
|
||||||
Resources,
|
|
||||||
ErrorTypes
|
|
||||||
) {
|
|
||||||
var isValidToken = Validator.forConstraint(Constraints.token);
|
|
||||||
|
|
||||||
return {
|
const Constraints = require('../../shared/validation/constraints')
|
||||||
getAll: function (req, res) {
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
const Logger = require('../logger')
|
||||||
if (err) {
|
const MonitoringService = require('../services/monitoringService')
|
||||||
return Resources.error(res, err);
|
const Resources = require('../utils/resources')
|
||||||
}
|
const Strings = require('../utils/strings')
|
||||||
|
const Validator = require('../validation/validator')
|
||||||
|
|
||||||
return MonitoringService.getAll(
|
const isValidToken = Validator.forConstraint(Constraints.token);
|
||||||
restParams,
|
|
||||||
function (err, monitoringStates, total) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('monitoring', 'admin').error('Could not get monitoring states:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set('X-Total-Count', total);
|
module.exports = {
|
||||||
return Resources.success(res, _.map(monitoringStates, function (state) {
|
getAll (req, res) {
|
||||||
state.mapId = _.toLower(state.mac).replace(/:/g, '');
|
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
||||||
return state;
|
if (err) {
|
||||||
}));
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MonitoringService.getAll(
|
||||||
|
restParams,
|
||||||
|
function (err, monitoringStates, total) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('monitoring', 'admin').error('Could not get monitoring states:', err);
|
||||||
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
confirm: function (req, res) {
|
res.set('X-Total-Count', total);
|
||||||
var data = Resources.getData(req);
|
return Resources.success(res, _.map(monitoringStates, function (state) {
|
||||||
|
state.mapId = _.toLower(state.mac).replace(/:/g, '');
|
||||||
var token = Strings.normalizeString(data.token);
|
return state;
|
||||||
if (!isValidToken(token)) {
|
}));
|
||||||
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
return MonitoringService.confirm(token, function (err, node) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
}
|
||||||
return Resources.success(res, {
|
);
|
||||||
hostname: node.hostname,
|
});
|
||||||
mac: node.mac,
|
},
|
||||||
email: node.email,
|
|
||||||
monitoring: node.monitoring,
|
|
||||||
monitoringConfirmed: node.monitoringConfirmed
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function (req, res) {
|
confirm (req, res) {
|
||||||
var data = Resources.getData(req);
|
const data = Resources.getData(req);
|
||||||
|
|
||||||
var token = Strings.normalizeString(data.token);
|
const token = Strings.normalizeString(data.token);
|
||||||
if (!isValidToken(token)) {
|
if (!isValidToken(token)) {
|
||||||
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
}
|
|
||||||
|
|
||||||
return MonitoringService.disable(token, function (err, node) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
return Resources.success(res, {
|
|
||||||
hostname: node.hostname,
|
|
||||||
mac: node.mac,
|
|
||||||
email: node.email,
|
|
||||||
monitoring: node.monitoring
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
return MonitoringService.confirm(token, function (err, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, {
|
||||||
|
hostname: node.hostname,
|
||||||
|
mac: node.mac,
|
||||||
|
email: node.email,
|
||||||
|
monitoring: node.monitoring,
|
||||||
|
monitoringConfirmed: node.monitoringConfirmed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
disable (req, res) {
|
||||||
|
const data = Resources.getData(req);
|
||||||
|
|
||||||
|
const token = Strings.normalizeString(data.token);
|
||||||
|
if (!isValidToken(token)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return MonitoringService.disable(token, function (err, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, {
|
||||||
|
hostname: node.hostname,
|
||||||
|
mac: node.mac,
|
||||||
|
email: node.email,
|
||||||
|
monitoring: node.monitoring
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,182 +1,181 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('NodeResource', function (
|
const _ = require('lodash')
|
||||||
Constraints,
|
const deepExtend = require('deep-extend')
|
||||||
Validator,
|
|
||||||
Logger,
|
|
||||||
MonitoringService,
|
|
||||||
NodeService,
|
|
||||||
_,
|
|
||||||
deepExtend,
|
|
||||||
Strings,
|
|
||||||
Resources,
|
|
||||||
ErrorTypes
|
|
||||||
) {
|
|
||||||
var nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring'];
|
|
||||||
|
|
||||||
function getNormalizedNodeData(reqData) {
|
const Constraints = require('../../shared/validation/constraints')
|
||||||
var node = {};
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
_.each(nodeFields, function (field) {
|
const Logger = require('../logger')
|
||||||
var value = Strings.normalizeString(reqData[field]);
|
const MonitoringService = require('../services/monitoringService')
|
||||||
if (field === 'mac') {
|
const NodeService = require('../services/nodeService')
|
||||||
value = Strings.normalizeMac(value);
|
const Strings = require('../utils/strings')
|
||||||
|
const Validator = require('../validation/validator')
|
||||||
|
const Resources = require('../utils/resources')
|
||||||
|
|
||||||
|
const nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring'];
|
||||||
|
|
||||||
|
function getNormalizedNodeData(reqData) {
|
||||||
|
const node = {};
|
||||||
|
_.each(nodeFields, function (field) {
|
||||||
|
let value = Strings.normalizeString(reqData[field]);
|
||||||
|
if (field === 'mac') {
|
||||||
|
value = Strings.normalizeMac(value);
|
||||||
|
}
|
||||||
|
node[field] = value;
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidNode = Validator.forConstraints(Constraints.node);
|
||||||
|
const isValidToken = Validator.forConstraint(Constraints.token);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
create: function (req, res) {
|
||||||
|
const data = Resources.getData(req);
|
||||||
|
|
||||||
|
const node = getNormalizedNodeData(data);
|
||||||
|
if (!isValidNode(node)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeService.createNode(node, function (err, token, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
node[field] = value;
|
return Resources.success(res, {token: token, node: node});
|
||||||
});
|
});
|
||||||
return node;
|
},
|
||||||
}
|
|
||||||
|
|
||||||
var isValidNode = Validator.forConstraints(Constraints.node);
|
update: function (req, res) {
|
||||||
var isValidToken = Validator.forConstraint(Constraints.token);
|
const data = Resources.getData(req);
|
||||||
|
|
||||||
return {
|
const token = Strings.normalizeString(data.token);
|
||||||
create: function (req, res) {
|
if (!isValidToken(token)) {
|
||||||
var data = Resources.getData(req);
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
var node = getNormalizedNodeData(data);
|
const node = getNormalizedNodeData(data);
|
||||||
if (!isValidNode(node)) {
|
if (!isValidNode(node)) {
|
||||||
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeService.updateNode(token, node, function (err, token, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, {token: token, node: node});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: function (req, res) {
|
||||||
|
const data = Resources.getData(req);
|
||||||
|
|
||||||
|
const token = Strings.normalizeString(data.token);
|
||||||
|
if (!isValidToken(token)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeService.deleteNode(token, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function (req, res) {
|
||||||
|
const token = Strings.normalizeString(Resources.getData(req).token);
|
||||||
|
if (!isValidToken(token)) {
|
||||||
|
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeService.getNodeDataByToken(token, function (err, node) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
return Resources.success(res, node);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getAll: function (req, res) {
|
||||||
|
Resources.getValidRestParams('list', 'node', req, function (err, restParams) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.createNode(node, function (err, token, node) {
|
return NodeService.getAllNodes(function (err, nodes) {
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
return Resources.success(res, {token: token, node: node});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function (req, res) {
|
|
||||||
var data = Resources.getData(req);
|
|
||||||
|
|
||||||
var token = Strings.normalizeString(data.token);
|
|
||||||
if (!isValidToken(token)) {
|
|
||||||
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
var node = getNormalizedNodeData(data);
|
|
||||||
if (!isValidNode(node)) {
|
|
||||||
return Resources.error(res, {data: 'Invalid node data.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
return NodeService.updateNode(token, node, function (err, token, node) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
return Resources.success(res, {token: token, node: node});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
delete: function (req, res) {
|
|
||||||
var data = Resources.getData(req);
|
|
||||||
|
|
||||||
var token = Strings.normalizeString(data.token);
|
|
||||||
if (!isValidToken(token)) {
|
|
||||||
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
return NodeService.deleteNode(token, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
return Resources.success(res, {});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function (req, res) {
|
|
||||||
var token = Strings.normalizeString(Resources.getData(req).token);
|
|
||||||
if (!isValidToken(token)) {
|
|
||||||
return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
return NodeService.getNodeDataByToken(token, function (err, node) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
return Resources.success(res, node);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getAll: function (req, res) {
|
|
||||||
Resources.getValidRestParams('list', 'node', req, function (err, restParams) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeService.getAllNodes(function (err, nodes) {
|
const realNodes = _.filter(nodes, function (node) {
|
||||||
|
// We ignore nodes without tokens as those are only manually added ones like gateways.
|
||||||
|
return node.token;
|
||||||
|
});
|
||||||
|
|
||||||
|
const macs = _.map(realNodes, function (node) {
|
||||||
|
return node.mac;
|
||||||
|
});
|
||||||
|
|
||||||
|
MonitoringService.getByMacs(macs, function (err, nodeStateByMac) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
Logger.tag('nodes', 'admin').error('Error getting nodes by MACs:', err);
|
||||||
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
var realNodes = _.filter(nodes, function (node) {
|
const enhancedNodes = _.map(realNodes, function (node) {
|
||||||
// We ignore nodes without tokens as those are only manually added ones like gateways.
|
const nodeState = nodeStateByMac[node.mac];
|
||||||
return node.token;
|
if (nodeState) {
|
||||||
});
|
return deepExtend({}, node, {
|
||||||
|
site: nodeState.site,
|
||||||
var macs = _.map(realNodes, function (node) {
|
domain: nodeState.domain,
|
||||||
return node.mac;
|
onlineState: nodeState.state
|
||||||
});
|
});
|
||||||
|
|
||||||
MonitoringService.getByMacs(macs, function (err, nodeStateByMac) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('nodes', 'admin').error('Error getting nodes by MACs:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var enhancedNodes = _.map(realNodes, function (node) {
|
return node;
|
||||||
var nodeState = nodeStateByMac[node.mac];
|
|
||||||
if (nodeState) {
|
|
||||||
return deepExtend({}, node, {
|
|
||||||
site: nodeState.site,
|
|
||||||
domain: nodeState.domain,
|
|
||||||
onlineState: nodeState.state
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
});
|
|
||||||
|
|
||||||
var filteredNodes = Resources.filter(
|
|
||||||
enhancedNodes,
|
|
||||||
[
|
|
||||||
'hostname',
|
|
||||||
'nickname',
|
|
||||||
'email',
|
|
||||||
'token',
|
|
||||||
'mac',
|
|
||||||
'site',
|
|
||||||
'domain',
|
|
||||||
'key',
|
|
||||||
'onlineState'
|
|
||||||
],
|
|
||||||
restParams
|
|
||||||
);
|
|
||||||
var total = filteredNodes.length;
|
|
||||||
|
|
||||||
var sortedNodes = Resources.sort(
|
|
||||||
filteredNodes,
|
|
||||||
[
|
|
||||||
'hostname',
|
|
||||||
'nickname',
|
|
||||||
'email',
|
|
||||||
'token',
|
|
||||||
'mac',
|
|
||||||
'key',
|
|
||||||
'site',
|
|
||||||
'domain',
|
|
||||||
'coords',
|
|
||||||
'onlineState',
|
|
||||||
'monitoringState'
|
|
||||||
],
|
|
||||||
restParams
|
|
||||||
);
|
|
||||||
var pageNodes = Resources.getPageEntities(sortedNodes, restParams);
|
|
||||||
|
|
||||||
res.set('X-Total-Count', total);
|
|
||||||
return Resources.success(res, pageNodes);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredNodes = Resources.filter(
|
||||||
|
enhancedNodes,
|
||||||
|
[
|
||||||
|
'hostname',
|
||||||
|
'nickname',
|
||||||
|
'email',
|
||||||
|
'token',
|
||||||
|
'mac',
|
||||||
|
'site',
|
||||||
|
'domain',
|
||||||
|
'key',
|
||||||
|
'onlineState'
|
||||||
|
],
|
||||||
|
restParams
|
||||||
|
);
|
||||||
|
const total = filteredNodes.length;
|
||||||
|
|
||||||
|
const sortedNodes = Resources.sort(
|
||||||
|
filteredNodes,
|
||||||
|
[
|
||||||
|
'hostname',
|
||||||
|
'nickname',
|
||||||
|
'email',
|
||||||
|
'token',
|
||||||
|
'mac',
|
||||||
|
'key',
|
||||||
|
'site',
|
||||||
|
'domain',
|
||||||
|
'coords',
|
||||||
|
'onlineState',
|
||||||
|
'monitoringState'
|
||||||
|
],
|
||||||
|
restParams
|
||||||
|
);
|
||||||
|
const pageNodes = Resources.getPageEntities(sortedNodes, restParams);
|
||||||
|
|
||||||
|
res.set('X-Total-Count', total);
|
||||||
|
return Resources.success(res, pageNodes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('StatisticsResource', function (
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
Logger,
|
const Logger = require('../logger')
|
||||||
NodeService,
|
const NodeService = require('../services/nodeService')
|
||||||
Resources,
|
const Resources = require('../utils/resources')
|
||||||
ErrorTypes
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
get: function (req, res) {
|
|
||||||
NodeService.getNodeStatistics(function (err, nodeStatistics) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('statistics').error('Error getting statistics:', err);
|
|
||||||
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Resources.success(
|
module.exports = {
|
||||||
res,
|
get (req, res) {
|
||||||
{
|
NodeService.getNodeStatistics((err, nodeStatistics) => {
|
||||||
nodes: nodeStatistics
|
if (err) {
|
||||||
}
|
Logger.tag('statistics').error('Error getting statistics:', err);
|
||||||
);
|
return Resources.error(res, {data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
};
|
return Resources.success(
|
||||||
});
|
res,
|
||||||
|
{
|
||||||
|
nodes: nodeStatistics
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,127 +1,126 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('TaskResource', function (
|
const _ = require('lodash')
|
||||||
Constraints,
|
|
||||||
Validator,
|
|
||||||
_,
|
|
||||||
Strings,
|
|
||||||
Resources,
|
|
||||||
ErrorTypes,
|
|
||||||
Scheduler
|
|
||||||
) {
|
|
||||||
var isValidId = Validator.forConstraint(Constraints.id);
|
|
||||||
|
|
||||||
function toExternalTask(task) {
|
const Constraints = require('../../shared/validation/constraints')
|
||||||
return {
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
id: task.id,
|
const Resources = require('../utils/resources')
|
||||||
name: task.name,
|
const Scheduler = require('../jobs/scheduler')
|
||||||
description: task.description,
|
const Strings = require('../utils/strings')
|
||||||
schedule: task.schedule,
|
const Validator = require('../validation/validator')
|
||||||
runningSince: task.runningSince && task.runningSince.unix(),
|
|
||||||
lastRunStarted: task.lastRunStarted && task.lastRunStarted.unix(),
|
const isValidId = Validator.forConstraint(Constraints.id);
|
||||||
lastRunDuration: task.lastRunDuration || undefined,
|
|
||||||
state: task.state,
|
function toExternalTask(task) {
|
||||||
enabled: task.enabled
|
return {
|
||||||
};
|
id: task.id,
|
||||||
|
name: task.name,
|
||||||
|
description: task.description,
|
||||||
|
schedule: task.schedule,
|
||||||
|
runningSince: task.runningSince && task.runningSince.unix(),
|
||||||
|
lastRunStarted: task.lastRunStarted && task.lastRunStarted.unix(),
|
||||||
|
lastRunDuration: task.lastRunDuration || undefined,
|
||||||
|
state: task.state,
|
||||||
|
enabled: task.enabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function withValidTaskId(req, res, callback) {
|
||||||
|
const id = Strings.normalizeString(Resources.getData(req).id);
|
||||||
|
|
||||||
|
if (!isValidId(id)) {
|
||||||
|
return callback({data: 'Invalid task id.', type: ErrorTypes.badRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
function withValidTaskId(req, res, callback) {
|
callback(null, id);
|
||||||
var id = Strings.normalizeString(Resources.getData(req).id);
|
}
|
||||||
|
|
||||||
if (!isValidId(id)) {
|
function getTask(id, callback) {
|
||||||
return callback({data: 'Invalid task id.', type: ErrorTypes.badRequest});
|
const tasks = Scheduler.getTasks();
|
||||||
|
const task = tasks[id];
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
return callback({data: 'Task not found.', type: ErrorTypes.notFound});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withTask(req, res, callback) {
|
||||||
|
withValidTaskId(req, res, function (err, id) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(null, id);
|
getTask(id, function (err, task) {
|
||||||
}
|
|
||||||
|
|
||||||
function getTask(id, callback) {
|
|
||||||
var tasks = Scheduler.getTasks();
|
|
||||||
var task = tasks[id];
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return callback({data: 'Task not found.', type: ErrorTypes.notFound});
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
function withTask(req, res, callback) {
|
|
||||||
withValidTaskId(req, res, function (err, id) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTask(id, function (err, task) {
|
callback(null, task);
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, task);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setTaskEnabled(req, res, enable) {
|
function setTaskEnabled(req, res, enable) {
|
||||||
|
withTask(req, res, function (err, task) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.enabled = !!enable; // ensure boolean
|
||||||
|
|
||||||
|
return Resources.success(res, toExternalTask(task));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAll (req, res) {
|
||||||
|
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
||||||
|
if (err) {
|
||||||
|
return Resources.error(res, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tasks = Resources.sort(
|
||||||
|
_.values(Scheduler.getTasks()),
|
||||||
|
['id', 'name', 'schedule', 'state', 'runningSince', 'lastRunStarted'],
|
||||||
|
restParams
|
||||||
|
);
|
||||||
|
const filteredTasks = Resources.filter(
|
||||||
|
tasks,
|
||||||
|
['id', 'name', 'schedule', 'state'],
|
||||||
|
restParams
|
||||||
|
);
|
||||||
|
const total = filteredTasks.length;
|
||||||
|
|
||||||
|
const pageTasks = Resources.getPageEntities(filteredTasks, restParams);
|
||||||
|
|
||||||
|
res.set('X-Total-Count', total);
|
||||||
|
return Resources.success(res, _.map(pageTasks, toExternalTask));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
run (req, res) {
|
||||||
withTask(req, res, function (err, task) {
|
withTask(req, res, function (err, task) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return Resources.error(res, err);
|
return Resources.error(res, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
task.enabled = !!enable; // ensure boolean
|
if (task.runningSince) {
|
||||||
|
return Resources.error(res, {data: 'Task already running.', type: ErrorTypes.conflict});
|
||||||
|
}
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
|
||||||
return Resources.success(res, toExternalTask(task));
|
return Resources.success(res, toExternalTask(task));
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
enable (req, res) {
|
||||||
|
setTaskEnabled(req, res, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
disable (req, res) {
|
||||||
|
setTaskEnabled(req, res, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
|
||||||
getAll: function (req, res) {
|
|
||||||
Resources.getValidRestParams('list', null, req, function (err, restParams) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tasks = Resources.sort(
|
|
||||||
_.values(Scheduler.getTasks()),
|
|
||||||
['id', 'name', 'schedule', 'state', 'runningSince', 'lastRunStarted'],
|
|
||||||
restParams
|
|
||||||
);
|
|
||||||
var filteredTasks = Resources.filter(
|
|
||||||
tasks,
|
|
||||||
['id', 'name', 'schedule', 'state'],
|
|
||||||
restParams
|
|
||||||
);
|
|
||||||
var total = filteredTasks.length;
|
|
||||||
|
|
||||||
var pageTasks = Resources.getPageEntities(filteredTasks, restParams);
|
|
||||||
|
|
||||||
res.set('X-Total-Count', total);
|
|
||||||
return Resources.success(res, _.map(pageTasks, toExternalTask));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
run: function (req, res) {
|
|
||||||
withTask(req, res, function (err, task) {
|
|
||||||
if (err) {
|
|
||||||
return Resources.error(res, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task.runningSince) {
|
|
||||||
return Resources.error(res, {data: 'Task already running.', type: ErrorTypes.conflict});
|
|
||||||
}
|
|
||||||
|
|
||||||
task.run();
|
|
||||||
|
|
||||||
return Resources.success(res, toExternalTask(task));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function (req, res) {
|
|
||||||
setTaskEnabled(req, res, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function (req, res) {
|
|
||||||
setTaskEnabled(req, res, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('VersionResource', function (
|
const Resources = require('../utils/resources')
|
||||||
version,
|
const version = require('../config').version
|
||||||
Resources
|
|
||||||
) {
|
module.exports = {
|
||||||
return {
|
get (req, res) {
|
||||||
get: function (req, res) {
|
return Resources.success(
|
||||||
return Resources.success(
|
res,
|
||||||
res,
|
{
|
||||||
{
|
version
|
||||||
version: version
|
}
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,53 +1,52 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('Router', function (
|
const express = require('express');
|
||||||
app,
|
|
||||||
VersionResource,
|
|
||||||
StatisticsResource,
|
|
||||||
FrontendResource,
|
|
||||||
NodeResource,
|
|
||||||
MonitoringResource,
|
|
||||||
TaskResource,
|
|
||||||
MailResource,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
var express = require('express');
|
|
||||||
var router = express.Router();
|
|
||||||
|
|
||||||
router.post('/', FrontendResource.render);
|
const app = require('./app')
|
||||||
|
const config = require('./config').config
|
||||||
|
const VersionResource = require('./resources/versionResource')
|
||||||
|
const StatisticsResource = require('./resources/statisticsResource')
|
||||||
|
const FrontendResource = require('./resources/frontendResource')
|
||||||
|
const NodeResource = require('./resources/nodeResource')
|
||||||
|
const MonitoringResource = require('./resources/monitoringResource')
|
||||||
|
const TaskResource = require('./resources/taskResource')
|
||||||
|
const MailResource = require('./resources/mailResource')
|
||||||
|
|
||||||
router.get('/api/version', VersionResource.get);
|
module.exports = {
|
||||||
|
init () {
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/api/node', NodeResource.create);
|
router.post('/', FrontendResource.render);
|
||||||
router.put('/api/node/:token', NodeResource.update);
|
|
||||||
router.delete('/api/node/:token', NodeResource.delete);
|
|
||||||
router.get('/api/node/:token', NodeResource.get);
|
|
||||||
|
|
||||||
router.put('/api/monitoring/confirm/:token', MonitoringResource.confirm);
|
router.get('/api/version', VersionResource.get);
|
||||||
router.put('/api/monitoring/disable/:token', MonitoringResource.disable);
|
|
||||||
|
|
||||||
router.get('/internal/api/statistics', StatisticsResource.get);
|
router.post('/api/node', NodeResource.create);
|
||||||
|
router.put('/api/node/:token', NodeResource.update);
|
||||||
|
router.delete('/api/node/:token', NodeResource.delete);
|
||||||
|
router.get('/api/node/:token', NodeResource.get);
|
||||||
|
|
||||||
router.get('/internal/api/tasks', TaskResource.getAll);
|
router.put('/api/monitoring/confirm/:token', MonitoringResource.confirm);
|
||||||
router.put('/internal/api/tasks/run/:id', TaskResource.run);
|
router.put('/api/monitoring/disable/:token', MonitoringResource.disable);
|
||||||
router.put('/internal/api/tasks/enable/:id', TaskResource.enable);
|
|
||||||
router.put('/internal/api/tasks/disable/:id', TaskResource.disable);
|
|
||||||
|
|
||||||
router.get('/internal/api/monitoring', MonitoringResource.getAll);
|
router.get('/internal/api/statistics', StatisticsResource.get);
|
||||||
|
|
||||||
router.get('/internal/api/mails', MailResource.getAll);
|
router.get('/internal/api/tasks', TaskResource.getAll);
|
||||||
router.get('/internal/api/mails/:id', MailResource.get);
|
router.put('/internal/api/tasks/run/:id', TaskResource.run);
|
||||||
router.delete('/internal/api/mails/:id', MailResource.delete);
|
router.put('/internal/api/tasks/enable/:id', TaskResource.enable);
|
||||||
router.put('/internal/api/mails/reset/:id', MailResource.resetFailures);
|
router.put('/internal/api/tasks/disable/:id', TaskResource.disable);
|
||||||
|
|
||||||
router.put('/internal/api/nodes/:token', NodeResource.update);
|
router.get('/internal/api/monitoring', MonitoringResource.getAll);
|
||||||
router.delete('/internal/api/nodes/:token', NodeResource.delete);
|
|
||||||
router.get('/internal/api/nodes', NodeResource.getAll);
|
|
||||||
router.get('/internal/api/nodes/:token', NodeResource.get);
|
|
||||||
|
|
||||||
app.use(config.server.rootPath, router);
|
router.get('/internal/api/mails', MailResource.getAll);
|
||||||
}
|
router.get('/internal/api/mails/:id', MailResource.get);
|
||||||
};
|
router.delete('/internal/api/mails/:id', MailResource.delete);
|
||||||
});
|
router.put('/internal/api/mails/reset/:id', MailResource.resetFailures);
|
||||||
|
|
||||||
|
router.put('/internal/api/nodes/:token', NodeResource.update);
|
||||||
|
router.delete('/internal/api/nodes/:token', NodeResource.delete);
|
||||||
|
router.get('/internal/api/nodes', NodeResource.getAll);
|
||||||
|
router.get('/internal/api/nodes/:token', NodeResource.get);
|
||||||
|
|
||||||
|
app.use(config.server.rootPath, router);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,238 +1,235 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng')
|
const _ = require('lodash')
|
||||||
.service('MailService', function (
|
const async = require('async')
|
||||||
Database,
|
const deepExtend = require('deep-extend')
|
||||||
MailTemplateService,
|
const moment = require('moment')
|
||||||
config,
|
|
||||||
_,
|
|
||||||
async,
|
|
||||||
deepExtend,
|
|
||||||
fs,
|
|
||||||
moment,
|
|
||||||
Logger,
|
|
||||||
Resources
|
|
||||||
) {
|
|
||||||
var MAIL_QUEUE_DB_BATCH_SIZE = 50;
|
|
||||||
var MAIL_QUEUE_MAX_PARALLEL_SENDING = 3;
|
|
||||||
|
|
||||||
var transporter = require('nodemailer').createTransport(deepExtend(
|
const config = require('../config').config
|
||||||
{},
|
const Database = require('../db/database').db
|
||||||
config.server.email.smtp,
|
const Logger = require('../logger')
|
||||||
{
|
const MailTemplateService = require('./mailTemplateService')
|
||||||
transport: 'smtp',
|
const Resources = require('../utils/resources')
|
||||||
pool: true
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
MailTemplateService.configureTransporter(transporter);
|
const MAIL_QUEUE_DB_BATCH_SIZE = 50;
|
||||||
|
const MAIL_QUEUE_MAX_PARALLEL_SENDING = 3;
|
||||||
|
|
||||||
function sendMail(options, callback) {
|
const transporter = require('nodemailer').createTransport(deepExtend(
|
||||||
Logger
|
{},
|
||||||
.tag('mail', 'queue')
|
config.server.email.smtp,
|
||||||
.info(
|
{
|
||||||
'Sending pending mail[%d] of type %s. ' +
|
transport: 'smtp',
|
||||||
'Had %d failures before.',
|
pool: true
|
||||||
options.id, options.email, options.failures
|
}
|
||||||
);
|
));
|
||||||
|
|
||||||
MailTemplateService.render(options, function (err, renderedTemplate) {
|
MailTemplateService.configureTransporter(transporter);
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var mailOptions = {
|
function sendMail(options, callback) {
|
||||||
from: options.sender,
|
Logger
|
||||||
to: options.recipient,
|
.tag('mail', 'queue')
|
||||||
subject: renderedTemplate.subject,
|
.info(
|
||||||
html: renderedTemplate.body
|
'Sending pending mail[%d] of type %s. ' +
|
||||||
};
|
'Had %d failures before.',
|
||||||
|
options.id, options.email, options.failures
|
||||||
transporter.sendMail(mailOptions, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.tag('mail', 'queue').info('Mail[%d] has been send.', options.id);
|
|
||||||
|
|
||||||
callback(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
function findPendingMailsBefore(beforeMoment, limit, callback) {
|
MailTemplateService.render(options, function (err, renderedTemplate) {
|
||||||
Database.all(
|
|
||||||
'SELECT * FROM email_queue WHERE modified_at < ? AND failures < ? ORDER BY id ASC LIMIT ?',
|
|
||||||
[beforeMoment.unix(), 5, limit],
|
|
||||||
function (err, rows) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pendingMails;
|
|
||||||
try {
|
|
||||||
pendingMails = _.map(rows, function (row) {
|
|
||||||
return deepExtend(
|
|
||||||
{},
|
|
||||||
row,
|
|
||||||
{
|
|
||||||
data: JSON.parse(row.data)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, pendingMails);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePendingMailFromQueue(id, callback) {
|
|
||||||
Database.run('DELETE FROM email_queue WHERE id = ?', [id], callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function incrementFailureCounterForPendingEmail(id, callback) {
|
|
||||||
var now = moment();
|
|
||||||
Database.run(
|
|
||||||
'UPDATE email_queue SET failures = failures + 1, modified_at = ? WHERE id = ?',
|
|
||||||
[now.unix(), id],
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendPendingMail(pendingMail, callback) {
|
|
||||||
sendMail(pendingMail, function (err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
// we only log the error and increment the failure counter as we want to continue with pending mails
|
return callback(err);
|
||||||
Logger.tag('mail', 'queue').error('Error sending pending mail[' + pendingMail.id + ']:', err);
|
|
||||||
|
|
||||||
return incrementFailureCounterForPendingEmail(pendingMail.id, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removePendingMailFromQueue(pendingMail.id, callback);
|
const mailOptions = {
|
||||||
});
|
from: options.sender,
|
||||||
}
|
to: options.recipient,
|
||||||
|
subject: renderedTemplate.subject,
|
||||||
function doGetMail(id, callback) {
|
html: renderedTemplate.body
|
||||||
Database.get('SELECT * FROM email_queue WHERE id = ?', [id], callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
enqueue: function (sender, recipient, email, data, callback) {
|
|
||||||
if (!_.isPlainObject(data)) {
|
|
||||||
return callback(new Error('Unexpected data: ' + data));
|
|
||||||
}
|
|
||||||
Database.run(
|
|
||||||
'INSERT INTO email_queue ' +
|
|
||||||
'(failures, sender, recipient, email, data) ' +
|
|
||||||
'VALUES (?, ?, ?, ?, ?)',
|
|
||||||
[0, sender, recipient, email, JSON.stringify(data)],
|
|
||||||
function (err, res) {
|
|
||||||
callback(err, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
getMail: function (id, callback) {
|
|
||||||
doGetMail(id, callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
getPendingMails: function (restParams, callback) {
|
|
||||||
Database.get(
|
|
||||||
'SELECT count(*) AS total FROM email_queue',
|
|
||||||
[],
|
|
||||||
function (err, row) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var total = row.total;
|
|
||||||
|
|
||||||
var filter = Resources.filterClause(
|
|
||||||
restParams,
|
|
||||||
'id',
|
|
||||||
['id', 'failures', 'sender', 'recipient', 'email', 'created_at', 'modified_at'],
|
|
||||||
['id', 'failures', 'sender', 'recipient', 'email']
|
|
||||||
);
|
|
||||||
|
|
||||||
Database.all(
|
|
||||||
'SELECT * FROM email_queue WHERE ' + filter.query,
|
|
||||||
_.concat([], filter.params),
|
|
||||||
function (err, rows) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, rows, total);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMail: function (id, callback) {
|
|
||||||
removePendingMailFromQueue(id, callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFailures: function (id, callback) {
|
|
||||||
Database.run(
|
|
||||||
'UPDATE email_queue SET failures = 0, modified_at = ? WHERE id = ?',
|
|
||||||
[moment().unix(), id],
|
|
||||||
function (err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.changes) {
|
|
||||||
return callback('Error: could not reset failure count for mail: ' + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
doGetMail(id, callback);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
sendPendingMails: function (callback) {
|
|
||||||
Logger.tag('mail', 'queue').debug('Start sending pending mails...');
|
|
||||||
|
|
||||||
var startTime = moment();
|
|
||||||
|
|
||||||
var sendNextBatch = function (err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.tag('mail', 'queue').debug('Sending next batch...');
|
|
||||||
|
|
||||||
findPendingMailsBefore(startTime, MAIL_QUEUE_DB_BATCH_SIZE, function (err, pendingMails) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isEmpty(pendingMails)) {
|
|
||||||
Logger.tag('mail', 'queue').debug('Done sending pending mails.');
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async.eachLimit(
|
|
||||||
pendingMails,
|
|
||||||
MAIL_QUEUE_MAX_PARALLEL_SENDING,
|
|
||||||
sendPendingMail,
|
|
||||||
sendNextBatch
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sendNextBatch(null);
|
transporter.sendMail(mailOptions, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.tag('mail', 'queue').info('Mail[%d] has been send.', options.id);
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function findPendingMailsBefore(beforeMoment, limit, callback) {
|
||||||
|
Database.all(
|
||||||
|
'SELECT * FROM email_queue WHERE modified_at < ? AND failures < ? ORDER BY id ASC LIMIT ?',
|
||||||
|
[beforeMoment.unix(), 5, limit],
|
||||||
|
function (err, rows) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pendingMails;
|
||||||
|
try {
|
||||||
|
pendingMails = _.map(rows, function (row) {
|
||||||
|
return deepExtend(
|
||||||
|
{},
|
||||||
|
row,
|
||||||
|
{
|
||||||
|
data: JSON.parse(row.data)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, pendingMails);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePendingMailFromQueue(id, callback) {
|
||||||
|
Database.run('DELETE FROM email_queue WHERE id = ?', [id], callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementFailureCounterForPendingEmail(id, callback) {
|
||||||
|
const now = moment();
|
||||||
|
Database.run(
|
||||||
|
'UPDATE email_queue SET failures = failures + 1, modified_at = ? WHERE id = ?',
|
||||||
|
[now.unix(), id],
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPendingMail(pendingMail, callback) {
|
||||||
|
sendMail(pendingMail, function (err) {
|
||||||
|
if (err) {
|
||||||
|
// we only log the error and increment the failure counter as we want to continue with pending mails
|
||||||
|
Logger.tag('mail', 'queue').error('Error sending pending mail[' + pendingMail.id + ']:', err);
|
||||||
|
|
||||||
|
return incrementFailureCounterForPendingEmail(pendingMail.id, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removePendingMailFromQueue(pendingMail.id, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doGetMail(id, callback) {
|
||||||
|
Database.get('SELECT * FROM email_queue WHERE id = ?', [id], callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
enqueue (sender, recipient, email, data, callback) {
|
||||||
|
if (!_.isPlainObject(data)) {
|
||||||
|
return callback(new Error('Unexpected data: ' + data));
|
||||||
|
}
|
||||||
|
Database.run(
|
||||||
|
'INSERT INTO email_queue ' +
|
||||||
|
'(failures, sender, recipient, email, data) ' +
|
||||||
|
'VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[0, sender, recipient, email, JSON.stringify(data)],
|
||||||
|
function (err, res) {
|
||||||
|
callback(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getMail (id, callback) {
|
||||||
|
doGetMail(id, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPendingMails (restParams, callback) {
|
||||||
|
Database.get(
|
||||||
|
'SELECT count(*) AS total FROM email_queue',
|
||||||
|
[],
|
||||||
|
function (err, row) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = row.total;
|
||||||
|
|
||||||
|
const filter = Resources.filterClause(
|
||||||
|
restParams,
|
||||||
|
'id',
|
||||||
|
['id', 'failures', 'sender', 'recipient', 'email', 'created_at', 'modified_at'],
|
||||||
|
['id', 'failures', 'sender', 'recipient', 'email']
|
||||||
|
);
|
||||||
|
|
||||||
|
Database.all(
|
||||||
|
'SELECT * FROM email_queue WHERE ' + filter.query,
|
||||||
|
_.concat([], filter.params),
|
||||||
|
function (err, rows) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, rows, total);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteMail (id, callback) {
|
||||||
|
removePendingMailFromQueue(id, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFailures (id, callback) {
|
||||||
|
Database.run(
|
||||||
|
'UPDATE email_queue SET failures = 0, modified_at = ? WHERE id = ?',
|
||||||
|
[moment().unix(), id],
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.changes) {
|
||||||
|
return callback('Error: could not reset failure count for mail: ' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
doGetMail(id, callback);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
sendPendingMails (callback) {
|
||||||
|
Logger.tag('mail', 'queue').debug('Start sending pending mails...');
|
||||||
|
|
||||||
|
const startTime = moment();
|
||||||
|
|
||||||
|
const sendNextBatch = function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.tag('mail', 'queue').debug('Sending next batch...');
|
||||||
|
|
||||||
|
findPendingMailsBefore(startTime, MAIL_QUEUE_DB_BATCH_SIZE, function (err, pendingMails) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isEmpty(pendingMails)) {
|
||||||
|
Logger.tag('mail', 'queue').debug('Done sending pending mails.');
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.eachLimit(
|
||||||
|
pendingMails,
|
||||||
|
MAIL_QUEUE_MAX_PARALLEL_SENDING,
|
||||||
|
sendPendingMail,
|
||||||
|
sendNextBatch
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
sendNextBatch(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,125 +1,122 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng')
|
const _ = require('lodash')
|
||||||
.service('MailTemplateService', function (
|
const async = require('async')
|
||||||
UrlBuilder,
|
const deepExtend = require('deep-extend')
|
||||||
config,
|
const fs = require('graceful-fs')
|
||||||
_,
|
const moment = require('moment')
|
||||||
async,
|
|
||||||
deepExtend,
|
|
||||||
fs,
|
|
||||||
moment,
|
|
||||||
Logger
|
|
||||||
) {
|
|
||||||
var templateBasePath = __dirname + '/../mailTemplates';
|
|
||||||
var snippetsBasePath = templateBasePath + '/snippets';
|
|
||||||
|
|
||||||
var templateFunctions = {};
|
const config = require('../config').config
|
||||||
|
const Logger = require('../logger')
|
||||||
|
const UrlBuilder = require('../utils/urlBuilder')
|
||||||
|
|
||||||
function renderSnippet(name, data) {
|
const templateBasePath = __dirname + '/../mailTemplates';
|
||||||
var snippetFile = snippetsBasePath + '/' + name + '.html';
|
const snippetsBasePath = templateBasePath + '/snippets';
|
||||||
|
|
||||||
return _.template(fs.readFileSync(snippetFile).toString())(deepExtend(
|
const templateFunctions = {};
|
||||||
{},
|
|
||||||
// jshint -W040
|
|
||||||
this, // parent data
|
|
||||||
// jshint +W040
|
|
||||||
data,
|
|
||||||
templateFunctions
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
function snippet(name) {
|
function renderSnippet(name, data) {
|
||||||
return function (data) {
|
const snippetFile = snippetsBasePath + '/' + name + '.html';
|
||||||
return renderSnippet.bind(this)(name, data);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLink(href, text) {
|
return _.template(fs.readFileSync(snippetFile).toString())(deepExtend(
|
||||||
return _.template(
|
{},
|
||||||
'<a href="<%- href %>#" style="color: #E5287A;"><%- text %></a>'
|
// jshint -W040
|
||||||
)({
|
this, // parent data
|
||||||
href: href,
|
// jshint +W040
|
||||||
text: text || href
|
data,
|
||||||
});
|
templateFunctions
|
||||||
}
|
));
|
||||||
|
}
|
||||||
|
|
||||||
function renderHR() {
|
function snippet(name) {
|
||||||
return '<hr style="border-top: 1px solid #333333; border-left: 0; border-right: 0; border-bottom: 0;" />';
|
return function (data) {
|
||||||
}
|
return renderSnippet.bind(this)(name, data);
|
||||||
|
|
||||||
function formatDateTime(unix) {
|
|
||||||
return moment.unix(unix).locale('de').local().format('DD.MM.YYYY HH:mm');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatFromNow(unix) {
|
|
||||||
return moment.unix(unix).locale('de').fromNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
templateFunctions.header = snippet('header');
|
|
||||||
templateFunctions.footer = snippet('footer');
|
|
||||||
|
|
||||||
templateFunctions.monitoringFooter = snippet('monitoring-footer');
|
|
||||||
|
|
||||||
templateFunctions.snippet = renderSnippet;
|
|
||||||
|
|
||||||
templateFunctions.link = renderLink;
|
|
||||||
templateFunctions.hr = renderHR;
|
|
||||||
|
|
||||||
templateFunctions.formatDateTime = formatDateTime;
|
|
||||||
templateFunctions.formatFromNow = formatFromNow;
|
|
||||||
|
|
||||||
return {
|
|
||||||
configureTransporter: function (transporter) {
|
|
||||||
var htmlToText = require('nodemailer-html-to-text').htmlToText;
|
|
||||||
transporter.use('compile', htmlToText({
|
|
||||||
tables: ['.table']
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function (mailOptions, callback) {
|
|
||||||
var templatePathPrefix = templateBasePath + '/' + mailOptions.email;
|
|
||||||
|
|
||||||
async.parallel({
|
|
||||||
subject: _.partial(fs.readFile, templatePathPrefix + '.subject.txt'),
|
|
||||||
body: _.partial(fs.readFile, templatePathPrefix + '.body.html')
|
|
||||||
},
|
|
||||||
function (err, templates) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = deepExtend(
|
|
||||||
{},
|
|
||||||
mailOptions.data,
|
|
||||||
{
|
|
||||||
community: config.client.community,
|
|
||||||
editNodeUrl: UrlBuilder.editNodeUrl()
|
|
||||||
},
|
|
||||||
templateFunctions
|
|
||||||
);
|
|
||||||
|
|
||||||
function render(field) {
|
|
||||||
return _.template(templates[field].toString())(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
var renderedTemplate;
|
|
||||||
try {
|
|
||||||
renderedTemplate = {
|
|
||||||
subject: _.trim(render('subject')),
|
|
||||||
body: render('body')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
Logger
|
|
||||||
.tag('mail', 'template')
|
|
||||||
.error('Error rendering template for mail[' + mailOptions.id + ']:', error);
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, renderedTemplate);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function renderLink(href, text) {
|
||||||
|
return _.template(
|
||||||
|
'<a href="<%- href %>#" style="color: #E5287A;"><%- text %></a>'
|
||||||
|
)({
|
||||||
|
href: href,
|
||||||
|
text: text || href
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHR() {
|
||||||
|
return '<hr style="border-top: 1px solid #333333; border-left: 0; border-right: 0; border-bottom: 0;" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(unix) {
|
||||||
|
return moment.unix(unix).locale('de').local().format('DD.MM.YYYY HH:mm');
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFromNow(unix) {
|
||||||
|
return moment.unix(unix).locale('de').fromNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
templateFunctions.header = snippet('header');
|
||||||
|
templateFunctions.footer = snippet('footer');
|
||||||
|
|
||||||
|
templateFunctions.monitoringFooter = snippet('monitoring-footer');
|
||||||
|
|
||||||
|
templateFunctions.snippet = renderSnippet;
|
||||||
|
|
||||||
|
templateFunctions.link = renderLink;
|
||||||
|
templateFunctions.hr = renderHR;
|
||||||
|
|
||||||
|
templateFunctions.formatDateTime = formatDateTime;
|
||||||
|
templateFunctions.formatFromNow = formatFromNow;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
configureTransporter (transporter) {
|
||||||
|
const htmlToText = require('nodemailer-html-to-text').htmlToText;
|
||||||
|
transporter.use('compile', htmlToText({
|
||||||
|
tables: ['.table']
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
render (mailOptions, callback) {
|
||||||
|
const templatePathPrefix = templateBasePath + '/' + mailOptions.email;
|
||||||
|
|
||||||
|
async.parallel({
|
||||||
|
subject: _.partial(fs.readFile, templatePathPrefix + '.subject.txt'),
|
||||||
|
body: _.partial(fs.readFile, templatePathPrefix + '.body.html')
|
||||||
|
},
|
||||||
|
function (err, templates) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = deepExtend(
|
||||||
|
{},
|
||||||
|
mailOptions.data,
|
||||||
|
{
|
||||||
|
community: config.client.community,
|
||||||
|
editNodeUrl: UrlBuilder.editNodeUrl()
|
||||||
|
},
|
||||||
|
templateFunctions
|
||||||
|
);
|
||||||
|
|
||||||
|
function render (field) {
|
||||||
|
return _.template(templates[field].toString())(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let renderedTemplate;
|
||||||
|
try {
|
||||||
|
renderedTemplate = {
|
||||||
|
subject: _.trim(render('subject')),
|
||||||
|
body: render('body')
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
Logger
|
||||||
|
.tag('mail', 'template')
|
||||||
|
.error('Error rendering template for mail[' + mailOptions.id + ']:', error);
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, renderedTemplate);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,346 +1,398 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng')
|
const _ = require('lodash')
|
||||||
.service('NodeService', function (
|
const async = require('async')
|
||||||
config,
|
const crypto = require('crypto')
|
||||||
_,
|
const fs = require('graceful-fs')
|
||||||
async,
|
const glob = require('glob')
|
||||||
crypto,
|
|
||||||
fs,
|
|
||||||
glob,
|
|
||||||
Logger,
|
|
||||||
MailService,
|
|
||||||
Strings,
|
|
||||||
ErrorTypes,
|
|
||||||
UrlBuilder
|
|
||||||
) {
|
|
||||||
var MAX_PARALLEL_NODES_PARSING = 10;
|
|
||||||
|
|
||||||
var linePrefixes = {
|
const config = require('../config').config
|
||||||
hostname: '# Knotenname: ',
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
nickname: '# Ansprechpartner: ',
|
const Logger = require('../logger')
|
||||||
email: '# Kontakt: ',
|
const MailService = require('../services/mailService')
|
||||||
coords: '# Koordinaten: ',
|
const Strings = require('../utils/strings')
|
||||||
mac: '# MAC: ',
|
const UrlBuilder = require('../utils/urlBuilder')
|
||||||
token: '# Token: ',
|
|
||||||
monitoring: '# Monitoring: ',
|
|
||||||
monitoringToken: '# Monitoring-Token: '
|
|
||||||
};
|
|
||||||
|
|
||||||
var filenameParts = ['hostname', 'mac', 'key', 'token', 'monitoringToken'];
|
const MAX_PARALLEL_NODES_PARSING = 10;
|
||||||
|
|
||||||
function generateToken() {
|
const linePrefixes = {
|
||||||
return crypto.randomBytes(8).toString('hex');
|
hostname: '# Knotenname: ',
|
||||||
}
|
nickname: '# Ansprechpartner: ',
|
||||||
|
email: '# Kontakt: ',
|
||||||
|
coords: '# Koordinaten: ',
|
||||||
|
mac: '# MAC: ',
|
||||||
|
token: '# Token: ',
|
||||||
|
monitoring: '# Monitoring: ',
|
||||||
|
monitoringToken: '# Monitoring-Token: '
|
||||||
|
};
|
||||||
|
|
||||||
function toNodeFilesPattern(filter) {
|
const filenameParts = ['hostname', 'mac', 'key', 'token', 'monitoringToken'];
|
||||||
var pattern = _.join(
|
|
||||||
_.map(filenameParts, function (field) {
|
|
||||||
return filter.hasOwnProperty(field) ? filter[field] : '*';
|
|
||||||
}),
|
|
||||||
'@'
|
|
||||||
);
|
|
||||||
|
|
||||||
return config.server.peersPath + '/' + pattern.toLowerCase();
|
function generateToken() {
|
||||||
}
|
return crypto.randomBytes(8).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
function findNodeFiles(filter, callback) {
|
function toNodeFilesPattern(filter) {
|
||||||
glob(toNodeFilesPattern(filter), callback);
|
const pattern = _.join(
|
||||||
}
|
_.map(filenameParts, function (field) {
|
||||||
|
return filter.hasOwnProperty(field) ? filter[field] : '*';
|
||||||
|
}),
|
||||||
|
'@'
|
||||||
|
);
|
||||||
|
|
||||||
function findNodeFilesSync(filter) {
|
return config.server.peersPath + '/' + pattern.toLowerCase();
|
||||||
return glob.sync(toNodeFilesPattern(filter));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function findFilesInPeersPath(callback) {
|
function findNodeFiles(filter, callback) {
|
||||||
glob(config.server.peersPath + '/*', function (err, files) {
|
glob(toNodeFilesPattern(filter), callback);
|
||||||
if (err) {
|
}
|
||||||
return callback(err);
|
|
||||||
|
function findNodeFilesSync(filter) {
|
||||||
|
return glob.sync(toNodeFilesPattern(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFilesInPeersPath(callback) {
|
||||||
|
glob(config.server.peersPath + '/*', function (err, files) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.filter(files, function (file, fileCallback) {
|
||||||
|
if (file[0] === '.') {
|
||||||
|
return fileCallback(null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.filter(files, function (file, fileCallback) {
|
fs.lstat(file, function (err, stats) {
|
||||||
if (file[0] === '.') {
|
if (err) {
|
||||||
return fileCallback(null, false);
|
return fileCallback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.lstat(file, function (err, stats) {
|
fileCallback(null, stats.isFile());
|
||||||
if (err) {
|
});
|
||||||
return fileCallback(err);
|
}, callback);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fileCallback(null, stats.isFile());
|
function parseNodeFilename(filename) {
|
||||||
});
|
const parts = _.split(filename, '@', filenameParts.length);
|
||||||
}, callback);
|
const parsed = {};
|
||||||
});
|
_.each(_.zip(filenameParts, parts), function (part) {
|
||||||
|
parsed[part[0]] = part[1];
|
||||||
|
});
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDuplicate(filter, token) {
|
||||||
|
const files = findNodeFilesSync(filter);
|
||||||
|
if (files.length === 0) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNodeFilename(filename) {
|
if (files.length > 1 || !token /* node is being created*/) {
|
||||||
var parts = _.split(filename, '@', filenameParts.length);
|
return true;
|
||||||
var parsed = {};
|
|
||||||
_.each(_.zip(filenameParts, parts), function (part) {
|
|
||||||
parsed[part[0]] = part[1];
|
|
||||||
});
|
|
||||||
return parsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDuplicate(filter, token) {
|
return parseNodeFilename(files[0]).token !== token;
|
||||||
var files = findNodeFilesSync(filter);
|
}
|
||||||
if (files.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length > 1 || !token /* node is being created*/) {
|
function checkNoDuplicates(token, node, nodeSecrets) {
|
||||||
return true;
|
if (isDuplicate({ hostname: node.hostname }, token)) {
|
||||||
}
|
return {data: {msg: 'Already exists.', field: 'hostname'}, type: ErrorTypes.conflict};
|
||||||
|
|
||||||
return parseNodeFilename(files[0]).token !== token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkNoDuplicates(token, node, nodeSecrets) {
|
if (node.key) {
|
||||||
if (isDuplicate({ hostname: node.hostname }, token)) {
|
if (isDuplicate({ key: node.key }, token)) {
|
||||||
return {data: {msg: 'Already exists.', field: 'hostname'}, type: ErrorTypes.conflict};
|
return {data: {msg: 'Already exists.', field: 'key'}, type: ErrorTypes.conflict};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.key) {
|
|
||||||
if (isDuplicate({ key: node.key }, token)) {
|
|
||||||
return {data: {msg: 'Already exists.', field: 'key'}, type: ErrorTypes.conflict};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDuplicate({ mac: node.mac }, token)) {
|
|
||||||
return {data: {msg: 'Already exists.', field: 'mac'}, type: ErrorTypes.conflict};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeSecrets.monitoringToken && isDuplicate({ monitoringToken: nodeSecrets.monitoringToken }, token)) {
|
|
||||||
return {data: {msg: 'Already exists.', field: 'monitoringToken'}, type: ErrorTypes.conflict};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toNodeFilename(token, node, nodeSecrets) {
|
if (isDuplicate({ mac: node.mac }, token)) {
|
||||||
return config.server.peersPath + '/' +
|
return {data: {msg: 'Already exists.', field: 'mac'}, type: ErrorTypes.conflict};
|
||||||
(
|
|
||||||
(node.hostname || '') + '@' +
|
|
||||||
(node.mac || '') + '@' +
|
|
||||||
(node.key || '') + '@' +
|
|
||||||
(token || '') + '@' +
|
|
||||||
(nodeSecrets.monitoringToken || '')
|
|
||||||
).toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeNodeFile(isUpdate, token, node, nodeSecrets, callback) {
|
if (nodeSecrets.monitoringToken && isDuplicate({ monitoringToken: nodeSecrets.monitoringToken }, token)) {
|
||||||
var filename = toNodeFilename(token, node, nodeSecrets);
|
return {data: {msg: 'Already exists.', field: 'monitoringToken'}, type: ErrorTypes.conflict};
|
||||||
var data = '';
|
}
|
||||||
_.each(linePrefixes, function (prefix, key) {
|
|
||||||
var value;
|
|
||||||
switch (key) {
|
|
||||||
case 'monitoring':
|
|
||||||
if (node.monitoring && node.monitoringConfirmed) {
|
|
||||||
value = 'aktiv';
|
|
||||||
} else if (node.monitoring && !node.monitoringConfirmed) {
|
|
||||||
value = 'pending';
|
|
||||||
} else {
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'monitoringToken':
|
return null;
|
||||||
value = nodeSecrets.monitoringToken || '';
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
function toNodeFilename(token, node, nodeSecrets) {
|
||||||
value = key === 'token' ? token : node[key];
|
return config.server.peersPath + '/' +
|
||||||
if (_.isUndefined(value)) {
|
(
|
||||||
value = _.isUndefined(nodeSecrets[key]) ? '' : nodeSecrets[key];
|
(node.hostname || '') + '@' +
|
||||||
}
|
(node.mac || '') + '@' +
|
||||||
break;
|
(node.key || '') + '@' +
|
||||||
}
|
(token || '') + '@' +
|
||||||
data += prefix + value + '\n';
|
(nodeSecrets.monitoringToken || '')
|
||||||
});
|
).toLowerCase();
|
||||||
if (node.key) {
|
}
|
||||||
data += 'key "' + node.key + '";\n';
|
|
||||||
|
function writeNodeFile(isUpdate, token, node, nodeSecrets, callback) {
|
||||||
|
const filename = toNodeFilename(token, node, nodeSecrets);
|
||||||
|
let data = '';
|
||||||
|
_.each(linePrefixes, function (prefix, key) {
|
||||||
|
let value;
|
||||||
|
switch (key) {
|
||||||
|
case 'monitoring':
|
||||||
|
if (node.monitoring && node.monitoringConfirmed) {
|
||||||
|
value = 'aktiv';
|
||||||
|
} else if (node.monitoring && !node.monitoringConfirmed) {
|
||||||
|
value = 'pending';
|
||||||
|
} else {
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'monitoringToken':
|
||||||
|
value = nodeSecrets.monitoringToken || '';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
value = key === 'token' ? token : node[key];
|
||||||
|
if (_.isUndefined(value)) {
|
||||||
|
value = _.isUndefined(nodeSecrets[key]) ? '' : nodeSecrets[key];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data += prefix + value + '\n';
|
||||||
|
});
|
||||||
|
if (node.key) {
|
||||||
|
data += 'key "' + node.key + '";\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// since node.js is single threaded we don't need a lock
|
||||||
|
|
||||||
|
let error;
|
||||||
|
|
||||||
|
if (isUpdate) {
|
||||||
|
const files = findNodeFilesSync({ token: token });
|
||||||
|
if (files.length !== 1) {
|
||||||
|
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||||
}
|
}
|
||||||
|
|
||||||
// since node.js is single threaded we don't need a lock
|
error = checkNoDuplicates(token, node, nodeSecrets);
|
||||||
|
if (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
var error;
|
const file = files[0];
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
Logger.tag('node', 'save').error('Could not delete old node file: ' + file, error);
|
||||||
|
return callback({data: 'Could not remove old node data.', type: ErrorTypes.internalError});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = checkNoDuplicates(null, node, nodeSecrets);
|
||||||
|
if (error) {
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isUpdate) {
|
try {
|
||||||
var files = findNodeFilesSync({ token: token });
|
fs.writeFileSync(filename, data, 'utf8');
|
||||||
if (files.length !== 1) {
|
}
|
||||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
catch (error) {
|
||||||
}
|
Logger.tag('node', 'save').error('Could not write node file: ' + filename, error);
|
||||||
|
return callback({data: 'Could not write node data.', type: ErrorTypes.internalError});
|
||||||
|
}
|
||||||
|
|
||||||
error = checkNoDuplicates(token, node, nodeSecrets);
|
return callback(null, token, node);
|
||||||
if (error) {
|
}
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = files[0];
|
function deleteNodeFile(token, callback) {
|
||||||
try {
|
findNodeFiles({ token: token }, function (err, files) {
|
||||||
fs.unlinkSync(file);
|
if (err) {
|
||||||
}
|
Logger.tag('node', 'delete').error('Could not find node file: ' + files, err);
|
||||||
catch (error) {
|
return callback({data: 'Could not delete node.', type: ErrorTypes.internalError});
|
||||||
Logger.tag('node', 'save').error('Could not delete old node file: ' + file, error);
|
}
|
||||||
return callback({data: 'Could not remove old node data.', type: ErrorTypes.internalError});
|
|
||||||
}
|
if (files.length !== 1) {
|
||||||
} else {
|
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||||
error = checkNoDuplicates(null, node, nodeSecrets);
|
|
||||||
if (error) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(filename, data, 'utf8');
|
fs.unlinkSync(files[0]);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
Logger.tag('node', 'save').error('Could not write node file: ' + filename, error);
|
Logger.tag('node', 'delete').error('Could not delete node file: ' + files, error);
|
||||||
return callback({data: 'Could not write node data.', type: ErrorTypes.internalError});
|
return callback({data: 'Could not delete node.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, token, node);
|
return callback(null);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function deleteNodeFile(token, callback) {
|
function parseNodeFile(file, callback) {
|
||||||
findNodeFiles({ token: token }, function (err, files) {
|
fs.readFile(file, function (err, contents) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Logger.tag('node', 'delete').error('Could not find node file: ' + files, err);
|
return callback(err);
|
||||||
return callback({data: 'Could not delete node.', type: ErrorTypes.internalError});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length !== 1) {
|
const lines = contents.toString();
|
||||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
const node = {};
|
||||||
fs.unlinkSync(files[0]);
|
const nodeSecrets = {};
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
Logger.tag('node', 'delete').error('Could not delete node file: ' + files, error);
|
|
||||||
return callback({data: 'Could not delete node.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null);
|
_.each(lines.split('\n'), function (line) {
|
||||||
});
|
const entries = {};
|
||||||
}
|
|
||||||
|
|
||||||
function parseNodeFile(file, callback) {
|
for (const key in linePrefixes) {
|
||||||
fs.readFile(file, function (err, contents) {
|
if (linePrefixes.hasOwnProperty(key)) {
|
||||||
if (err) {
|
const prefix = linePrefixes[key];
|
||||||
return callback(err);
|
if (line.substring(0, prefix.length) === prefix) {
|
||||||
}
|
entries[key] = Strings.normalizeString(line.substr(prefix.length));
|
||||||
|
break;
|
||||||
var lines = contents.toString();
|
|
||||||
|
|
||||||
var node = {};
|
|
||||||
var nodeSecrets = {};
|
|
||||||
|
|
||||||
_.each(lines.split('\n'), function (line) {
|
|
||||||
var entries = {};
|
|
||||||
|
|
||||||
for (var key in linePrefixes) {
|
|
||||||
if (linePrefixes.hasOwnProperty(key)) {
|
|
||||||
var prefix = linePrefixes[key];
|
|
||||||
if (line.substring(0, prefix.length) === prefix) {
|
|
||||||
entries[key] = Strings.normalizeString(line.substr(prefix.length));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_.isEmpty(entries) && line.substring(0, 5) === 'key "') {
|
if (_.isEmpty(entries) && line.substring(0, 5) === 'key "') {
|
||||||
entries.key = Strings.normalizeString(line.split('"')[1]);
|
entries.key = Strings.normalizeString(line.split('"')[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(entries, function (value, key) {
|
||||||
|
if (key === 'mac') {
|
||||||
|
node.mac = value;
|
||||||
|
node.mapId = _.toLower(value).replace(/:/g, '');
|
||||||
|
} else if (key === 'monitoring') {
|
||||||
|
const active = value === 'aktiv';
|
||||||
|
const pending = value === 'pending';
|
||||||
|
node.monitoring = active || pending;
|
||||||
|
node.monitoringConfirmed = active;
|
||||||
|
node.monitoringState = active ? 'active' : (pending ? 'pending' : 'disabled');
|
||||||
|
} else if (key === 'monitoringToken') {
|
||||||
|
nodeSecrets.monitoringToken = value;
|
||||||
|
} else {
|
||||||
|
node[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_.each(entries, function (value, key) {
|
|
||||||
if (key === 'mac') {
|
|
||||||
node.mac = value;
|
|
||||||
node.mapId = _.toLower(value).replace(/:/g, '');
|
|
||||||
} else if (key === 'monitoring') {
|
|
||||||
var active = value === 'aktiv';
|
|
||||||
var pending = value === 'pending';
|
|
||||||
node.monitoring = active || pending;
|
|
||||||
node.monitoringConfirmed = active;
|
|
||||||
node.monitoringState = active ? 'active' : (pending ? 'pending' : 'disabled');
|
|
||||||
} else if (key === 'monitoringToken') {
|
|
||||||
nodeSecrets.monitoringToken = value;
|
|
||||||
} else {
|
|
||||||
node[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
callback(null, node, nodeSecrets);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function findNodeDataByFilePattern(filter, callback) {
|
callback(null, node, nodeSecrets);
|
||||||
findNodeFiles(filter, function (err, files) {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNodeDataByFilePattern(filter, callback) {
|
||||||
|
findNodeFiles(filter, function (err, files) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length !== 1) {
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = files[0];
|
||||||
|
return parseNodeFile(file, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeDataByFilePattern(filter, callback) {
|
||||||
|
findNodeDataByFilePattern(filter, function (err, node, nodeSecrets) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, node, nodeSecrets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMonitoringConfirmationMail(node, nodeSecrets, callback) {
|
||||||
|
const confirmUrl = UrlBuilder.monitoringConfirmUrl(nodeSecrets);
|
||||||
|
const disableUrl = UrlBuilder.monitoringDisableUrl(nodeSecrets);
|
||||||
|
|
||||||
|
MailService.enqueue(
|
||||||
|
config.server.email.from,
|
||||||
|
node.nickname + ' <' + node.email + '>',
|
||||||
|
'monitoring-confirmation',
|
||||||
|
{
|
||||||
|
node: node,
|
||||||
|
confirmUrl: confirmUrl,
|
||||||
|
disableUrl: disableUrl
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
Logger.tag('monitoring', 'confirmation').error('Could not enqueue confirmation mail.', err);
|
||||||
|
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createNode: function (node, callback) {
|
||||||
|
const token = generateToken();
|
||||||
|
const nodeSecrets = {};
|
||||||
|
|
||||||
|
node.monitoringConfirmed = false;
|
||||||
|
|
||||||
|
if (node.monitoring) {
|
||||||
|
nodeSecrets.monitoringToken = generateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
writeNodeFile(false, token, node, nodeSecrets, function (err, token, node) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length !== 1) {
|
if (node.monitoring && !node.monitoringConfirmed) {
|
||||||
return callback(null);
|
return sendMonitoringConfirmationMail(node, nodeSecrets, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null, token, node);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = files[0];
|
return callback(null, token, node);
|
||||||
return parseNodeFile(file, callback);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
function getNodeDataByFilePattern(filter, callback) {
|
updateNode: function (token, node, callback) {
|
||||||
findNodeDataByFilePattern(filter, function (err, node, nodeSecrets) {
|
this.getNodeDataByToken(token, function (err, currentNode, nodeSecrets) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node) {
|
let monitoringConfirmed = false;
|
||||||
return callback({data: 'Node not found.', type: ErrorTypes.notFound});
|
let monitoringToken = '';
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, node, nodeSecrets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMonitoringConfirmationMail(node, nodeSecrets, callback) {
|
|
||||||
var confirmUrl = UrlBuilder.monitoringConfirmUrl(nodeSecrets);
|
|
||||||
var disableUrl = UrlBuilder.monitoringDisableUrl(nodeSecrets);
|
|
||||||
|
|
||||||
MailService.enqueue(
|
|
||||||
config.server.email.from,
|
|
||||||
node.nickname + ' <' + node.email + '>',
|
|
||||||
'monitoring-confirmation',
|
|
||||||
{
|
|
||||||
node: node,
|
|
||||||
confirmUrl: confirmUrl,
|
|
||||||
disableUrl: disableUrl
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('monitoring', 'confirmation').error('Could not enqueue confirmation mail.', err);
|
|
||||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
createNode: function (node, callback) {
|
|
||||||
var token = generateToken();
|
|
||||||
var nodeSecrets = {};
|
|
||||||
|
|
||||||
node.monitoringConfirmed = false;
|
|
||||||
|
|
||||||
if (node.monitoring) {
|
if (node.monitoring) {
|
||||||
nodeSecrets.monitoringToken = generateToken();
|
if (!currentNode.monitoring) {
|
||||||
|
// monitoring just has been enabled
|
||||||
|
monitoringConfirmed = false;
|
||||||
|
monitoringToken = generateToken();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// monitoring is still enabled
|
||||||
|
|
||||||
|
if (currentNode.email !== node.email) {
|
||||||
|
// new email so we need a new token and a reconfirmation
|
||||||
|
monitoringConfirmed = false;
|
||||||
|
monitoringToken = generateToken();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// email unchanged, keep token (fix if not set) and confirmation state
|
||||||
|
monitoringConfirmed = currentNode.monitoringConfirmed;
|
||||||
|
monitoringToken = nodeSecrets.monitoringToken || generateToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeNodeFile(false, token, node, nodeSecrets, function (err, token, node) {
|
node.monitoringConfirmed = monitoringConfirmed;
|
||||||
|
nodeSecrets.monitoringToken = monitoringToken;
|
||||||
|
|
||||||
|
writeNodeFile(true, token, node, nodeSecrets, function (err, token, node) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -357,178 +409,124 @@ angular.module('ffffng')
|
||||||
|
|
||||||
return callback(null, token, node);
|
return callback(null, token, node);
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
|
},
|
||||||
|
|
||||||
updateNode: function (token, node, callback) {
|
internalUpdateNode: function (token, node, nodeSecrets, callback) {
|
||||||
this.getNodeDataByToken(token, function (err, currentNode, nodeSecrets) {
|
writeNodeFile(true, token, node, nodeSecrets, callback);
|
||||||
if (err) {
|
},
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var monitoringConfirmed = false;
|
deleteNode: function (token, callback) {
|
||||||
var monitoringToken = '';
|
deleteNodeFile(token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
if (node.monitoring) {
|
getAllNodes: function (callback) {
|
||||||
if (!currentNode.monitoring) {
|
findNodeFiles({}, function (err, files) {
|
||||||
// monitoring just has been enabled
|
if (err) {
|
||||||
monitoringConfirmed = false;
|
Logger.tag('nodes').error('Error getting all nodes:', err);
|
||||||
monitoringToken = generateToken();
|
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
async.mapLimit(
|
||||||
// monitoring is still enabled
|
files,
|
||||||
|
MAX_PARALLEL_NODES_PARSING,
|
||||||
if (currentNode.email !== node.email) {
|
parseNodeFile,
|
||||||
// new email so we need a new token and a reconfirmation
|
function (err, nodes) {
|
||||||
monitoringConfirmed = false;
|
|
||||||
monitoringToken = generateToken();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// email unchanged, keep token (fix if not set) and confirmation state
|
|
||||||
monitoringConfirmed = currentNode.monitoringConfirmed;
|
|
||||||
monitoringToken = nodeSecrets.monitoringToken || generateToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node.monitoringConfirmed = monitoringConfirmed;
|
|
||||||
nodeSecrets.monitoringToken = monitoringToken;
|
|
||||||
|
|
||||||
writeNodeFile(true, token, node, nodeSecrets, function (err, token, node) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
Logger.tag('nodes').error('Error getting all nodes:', err);
|
||||||
|
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.monitoring && !node.monitoringConfirmed) {
|
return callback(null, nodes);
|
||||||
return sendMonitoringConfirmationMail(node, nodeSecrets, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, token, node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, token, node);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
internalUpdateNode: function (token, node, nodeSecrets, callback) {
|
|
||||||
writeNodeFile(true, token, node, nodeSecrets, callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteNode: function (token, callback) {
|
|
||||||
deleteNodeFile(token, callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
getAllNodes: function (callback) {
|
|
||||||
findNodeFiles({}, function (err, files) {
|
|
||||||
if (err) {
|
|
||||||
Logger.tag('nodes').error('Error getting all nodes:', err);
|
|
||||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async.mapLimit(
|
getNodeDataByMac: function (mac, callback) {
|
||||||
files,
|
return findNodeDataByFilePattern({ mac: mac }, callback);
|
||||||
MAX_PARALLEL_NODES_PARSING,
|
},
|
||||||
parseNodeFile,
|
|
||||||
function (err, nodes) {
|
getNodeDataByToken: function (token, callback) {
|
||||||
|
return getNodeDataByFilePattern({ token: token }, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
getNodeDataByMonitoringToken: function (monitoringToken, callback) {
|
||||||
|
return getNodeDataByFilePattern({ monitoringToken: monitoringToken }, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
fixNodeFilenames: function (callback) {
|
||||||
|
findFilesInPeersPath(function (err, files) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.mapLimit(
|
||||||
|
files,
|
||||||
|
MAX_PARALLEL_NODES_PARSING,
|
||||||
|
function (file, fileCallback) {
|
||||||
|
parseNodeFile(file, function (err, node, nodeSecrets) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Logger.tag('nodes').error('Error getting all nodes:', err);
|
return fileCallback(err);
|
||||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, nodes);
|
const expectedFilename = toNodeFilename(node.token, node, nodeSecrets);
|
||||||
}
|
if (file !== expectedFilename) {
|
||||||
);
|
return fs.rename(file, expectedFilename, function (err) {
|
||||||
});
|
if (err) {
|
||||||
},
|
return fileCallback(new Error(
|
||||||
|
'Cannot rename file ' + file + ' to ' + expectedFilename + ' => ' + err
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
getNodeDataByMac: function (mac, callback) {
|
fileCallback(null);
|
||||||
return findNodeDataByFilePattern({ mac: mac }, callback);
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
getNodeDataByToken: function (token, callback) {
|
fileCallback(null);
|
||||||
return getNodeDataByFilePattern({ token: token }, callback);
|
});
|
||||||
},
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getNodeDataByMonitoringToken: function (monitoringToken, callback) {
|
getNodeStatistics: function (callback) {
|
||||||
return getNodeDataByFilePattern({ monitoringToken: monitoringToken }, callback);
|
this.getAllNodes(function (err, nodes) {
|
||||||
},
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
fixNodeFilenames: function (callback) {
|
const nodeStatistics = {
|
||||||
findFilesInPeersPath(function (err, files) {
|
registered: _.size(nodes),
|
||||||
if (err) {
|
withVPN: 0,
|
||||||
return callback(err);
|
withCoords: 0,
|
||||||
|
monitoring: {
|
||||||
|
active: 0,
|
||||||
|
pending: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_.each(nodes, function (node) {
|
||||||
|
if (node.key) {
|
||||||
|
nodeStatistics.withVPN += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async.mapLimit(
|
if (node.coords) {
|
||||||
files,
|
nodeStatistics.withCoords += 1;
|
||||||
MAX_PARALLEL_NODES_PARSING,
|
|
||||||
function (file, fileCallback) {
|
|
||||||
parseNodeFile(file, function (err, node, nodeSecrets) {
|
|
||||||
if (err) {
|
|
||||||
return fileCallback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedFilename = toNodeFilename(node.token, node, nodeSecrets);
|
|
||||||
if (file !== expectedFilename) {
|
|
||||||
return fs.rename(file, expectedFilename, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return fileCallback(new Error(
|
|
||||||
'Cannot rename file ' + file + ' to ' + expectedFilename + ' => ' + err
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fileCallback(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fileCallback(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getNodeStatistics: function (callback) {
|
|
||||||
this.getAllNodes(function (err, nodes) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeStatistics = {
|
switch (node.monitoringState) {
|
||||||
registered: _.size(nodes),
|
case 'active':
|
||||||
withVPN: 0,
|
nodeStatistics.monitoring.active += 1;
|
||||||
withCoords: 0,
|
break;
|
||||||
monitoring: {
|
case 'pending':
|
||||||
active: 0,
|
nodeStatistics.monitoring.pending += 1;
|
||||||
pending: 0
|
break;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
_.each(nodes, function (node) {
|
|
||||||
if (node.key) {
|
|
||||||
nodeStatistics.withVPN += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.coords) {
|
|
||||||
nodeStatistics.withCoords += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (node.monitoringState) {
|
|
||||||
case 'active':
|
|
||||||
nodeStatistics.monitoring.active += 1;
|
|
||||||
break;
|
|
||||||
case 'pending':
|
|
||||||
nodeStatistics.monitoring.pending += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(null, nodeStatistics);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
callback(null, nodeStatistics);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('DatabaseUtil', function (_) {
|
const _ = require('lodash')
|
||||||
return {
|
|
||||||
inCondition: function (field, list) {
|
module.exports = {
|
||||||
return {
|
inCondition (field, list) {
|
||||||
query: '(' + field + ' IN (' + _.join(_.times(list.length, _.constant('?')), ', ') + '))',
|
return {
|
||||||
params: list
|
query: '(' + field + ' IN (' + _.join(_.times(list.length, _.constant('?')), ', ') + '))',
|
||||||
};
|
params: list
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('ErrorTypes', function () {
|
module.exports = {
|
||||||
return {
|
badRequest: {code: 400},
|
||||||
badRequest: {code: 400},
|
notFound: {code: 404},
|
||||||
notFound: {code: 404},
|
conflict: {code: 409},
|
||||||
conflict: {code: 409},
|
internalError: {code: 500}
|
||||||
internalError: {code: 500}
|
}
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,224 +1,229 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('Resources', function (_, Constraints, Validator, ErrorTypes, Logger) {
|
const _ = require('lodash')
|
||||||
function respond(res, httpCode, data, type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'html':
|
|
||||||
res.writeHead(httpCode, {'Content-Type': 'text/html'});
|
|
||||||
res.end(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
const Constraints = require('../../shared/validation/constraints')
|
||||||
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
const ErrorTypes = require('../utils/errorTypes')
|
||||||
res.end(JSON.stringify(data));
|
const Logger = require('../logger')
|
||||||
break;
|
const Validator = require('../validation/validator')
|
||||||
}
|
|
||||||
|
function respond(res, httpCode, data, type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'html':
|
||||||
|
res.writeHead(httpCode, {'Content-Type': 'text/html'});
|
||||||
|
res.end(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
||||||
|
res.end(JSON.stringify(data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderByClause(restParams, defaultSortField, allowedSortFields) {
|
||||||
|
let sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||||
|
if (!sortField) {
|
||||||
|
sortField = defaultSortField;
|
||||||
}
|
}
|
||||||
|
|
||||||
function orderByClause(restParams, defaultSortField, allowedSortFields) {
|
return {
|
||||||
var sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
query: 'ORDER BY ' + sortField + ' ' + (restParams._sortDir === 'ASC' ? 'ASC' : 'DESC'),
|
||||||
if (!sortField) {
|
params: []
|
||||||
sortField = defaultSortField;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function limitOffsetClause(restParams) {
|
||||||
|
const page = restParams._page;
|
||||||
|
const perPage = restParams._perPage;
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: 'LIMIT ? OFFSET ?',
|
||||||
|
params: [perPage, ((page - 1) * perPage)]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForLikePattern(str) {
|
||||||
|
return str
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/%/g, '\\%')
|
||||||
|
.replace(/_/g, '\\_');
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterCondition(restParams, filterFields) {
|
||||||
|
if (_.isEmpty(filterFields)) {
|
||||||
return {
|
return {
|
||||||
query: 'ORDER BY ' + sortField + ' ' + (restParams._sortDir === 'ASC' ? 'ASC' : 'DESC'),
|
query: '1 = 1',
|
||||||
params: []
|
params: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function limitOffsetClause(restParams) {
|
let query = _.join(
|
||||||
var page = restParams._page;
|
_.map(filterFields, function (field) {
|
||||||
var perPage = restParams._perPage;
|
return 'LOWER(' + field + ') LIKE ?';
|
||||||
|
}),
|
||||||
|
' OR '
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
query += ' ESCAPE \'\\\'';
|
||||||
query: 'LIMIT ? OFFSET ?',
|
|
||||||
params: [perPage, ((page - 1) * perPage)]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeForLikePattern(str) {
|
const search = '%' + (_.isString(restParams.q) ? escapeForLikePattern(_.toLower(restParams.q.trim())) : '') + '%';
|
||||||
return str
|
const params = _.times(filterFields.length, _.constant(search));
|
||||||
.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/%/g, '\\%')
|
|
||||||
.replace(/_/g, '\\_');
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterCondition(restParams, filterFields) {
|
|
||||||
if (_.isEmpty(filterFields)) {
|
|
||||||
return {
|
|
||||||
query: '1 = 1',
|
|
||||||
params: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = _.join(
|
|
||||||
_.map(filterFields, function (field) {
|
|
||||||
return 'LOWER(' + field + ') LIKE ?';
|
|
||||||
}),
|
|
||||||
' OR '
|
|
||||||
);
|
|
||||||
|
|
||||||
query += ' ESCAPE \'\\\'';
|
|
||||||
|
|
||||||
var search = '%' + (_.isString(restParams.q) ? escapeForLikePattern(_.toLower(restParams.q.trim())) : '') + '%';
|
|
||||||
var params = _.times(filterFields.length, _.constant(search));
|
|
||||||
|
|
||||||
return {
|
|
||||||
query: query,
|
|
||||||
params: params
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConstrainedValues(data, constraints) {
|
|
||||||
var values = {};
|
|
||||||
_.each(_.keys(constraints), function (key) {
|
|
||||||
var value = data[key];
|
|
||||||
values[key] =
|
|
||||||
_.isUndefined(value) && !_.isUndefined(constraints[key].default) ? constraints[key].default : value;
|
|
||||||
});
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getData: function (req) {
|
query: query,
|
||||||
return _.extend({}, req.body, req.params, req.query);
|
params: params
|
||||||
},
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getValidRestParams: function(type, subtype, req, callback) {
|
function getConstrainedValues(data, constraints) {
|
||||||
var constraints = Constraints.rest[type];
|
const values = {};
|
||||||
if (!_.isPlainObject(constraints)) {
|
_.each(_.keys(constraints), function (key) {
|
||||||
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
|
const value = data[key];
|
||||||
|
values[key] =
|
||||||
|
_.isUndefined(value) && !_.isUndefined(constraints[key].default) ? constraints[key].default : value;
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getData (req) {
|
||||||
|
return _.extend({}, req.body, req.params, req.query);
|
||||||
|
},
|
||||||
|
|
||||||
|
getValidRestParams(type, subtype, req, callback) {
|
||||||
|
const constraints = Constraints.rest[type];
|
||||||
|
if (!_.isPlainObject(constraints)) {
|
||||||
|
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
|
||||||
|
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
|
}
|
||||||
|
|
||||||
|
let filterConstraints = {};
|
||||||
|
if (subtype) {
|
||||||
|
filterConstraints = Constraints[subtype + 'Filters'];
|
||||||
|
if (!_.isPlainObject(filterConstraints)) {
|
||||||
|
Logger.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
|
||||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||||
}
|
}
|
||||||
|
|
||||||
var filterConstraints = {};
|
|
||||||
if (subtype) {
|
|
||||||
filterConstraints = Constraints[subtype + 'Filters'];
|
|
||||||
if (!_.isPlainObject(filterConstraints)) {
|
|
||||||
Logger.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
|
|
||||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = this.getData(req);
|
|
||||||
|
|
||||||
var restParams = getConstrainedValues(data, constraints);
|
|
||||||
var filterParams = getConstrainedValues(data, filterConstraints);
|
|
||||||
|
|
||||||
var areValidParams = Validator.forConstraints(constraints);
|
|
||||||
var areValidFilters = Validator.forConstraints(filterConstraints);
|
|
||||||
if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
|
|
||||||
return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest});
|
|
||||||
}
|
|
||||||
|
|
||||||
restParams.filters = filterParams;
|
|
||||||
|
|
||||||
callback(null, restParams);
|
|
||||||
},
|
|
||||||
|
|
||||||
filter: function (entities, allowedFilterFields, restParams) {
|
|
||||||
var query = restParams.q;
|
|
||||||
if (query) {
|
|
||||||
query = _.toLower(query.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryMatches(entity) {
|
|
||||||
if (!query) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return _.some(allowedFilterFields, function (field) {
|
|
||||||
var value = entity[field];
|
|
||||||
if (_.isNumber(value)) {
|
|
||||||
value = value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isString(value) || _.isEmpty(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = _.toLower(value);
|
|
||||||
if (field === 'mac') {
|
|
||||||
return _.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.includes(value, query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var filters = restParams.filters;
|
|
||||||
|
|
||||||
function filtersMatch(entity) {
|
|
||||||
if (_.isEmpty(filters)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.every(filters, function (value, key) {
|
|
||||||
if (_.isUndefined(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (_.startsWith(key, 'has')) {
|
|
||||||
var entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
|
|
||||||
return _.isEmpty(entity[entityKey]).toString() !== value;
|
|
||||||
}
|
|
||||||
return entity[key] === value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.filter(entities, function (entity) {
|
|
||||||
return queryMatches(entity) && filtersMatch(entity);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
sort: function (entities, allowedSortFields, restParams) {
|
|
||||||
var sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
|
||||||
if (!sortField) {
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sorted = _.sortBy(entities, [sortField]);
|
|
||||||
|
|
||||||
return restParams._sortDir === 'ASC' ? sorted : _.reverse(sorted);
|
|
||||||
},
|
|
||||||
|
|
||||||
getPageEntities: function (entities, restParams) {
|
|
||||||
var page = restParams._page;
|
|
||||||
var perPage = restParams._perPage;
|
|
||||||
|
|
||||||
return entities.slice((page - 1) * perPage, page * perPage);
|
|
||||||
},
|
|
||||||
|
|
||||||
whereCondition: filterCondition,
|
|
||||||
|
|
||||||
filterClause: function (restParams, defaultSortField, allowedSortFields, filterFields) {
|
|
||||||
var orderBy = orderByClause(
|
|
||||||
restParams,
|
|
||||||
defaultSortField,
|
|
||||||
allowedSortFields
|
|
||||||
);
|
|
||||||
var limitOffset = limitOffsetClause(restParams);
|
|
||||||
|
|
||||||
var filter = filterCondition(
|
|
||||||
restParams,
|
|
||||||
filterFields
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
query: filter.query + ' ' + orderBy.query + ' ' + limitOffset.query,
|
|
||||||
params: _.concat(filter.params, orderBy.params, limitOffset.params)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
success: function (res, data) {
|
|
||||||
respond(res, 200, data, 'json');
|
|
||||||
},
|
|
||||||
|
|
||||||
successHtml: function (res, html) {
|
|
||||||
respond(res, 200, html, 'html');
|
|
||||||
},
|
|
||||||
|
|
||||||
error: function (res, err) {
|
|
||||||
respond(res, err.type.code, err.data, 'json');
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
const data = this.getData(req);
|
||||||
|
|
||||||
|
const restParams = getConstrainedValues(data, constraints);
|
||||||
|
const filterParams = getConstrainedValues(data, filterConstraints);
|
||||||
|
|
||||||
|
const areValidParams = Validator.forConstraints(constraints);
|
||||||
|
const areValidFilters = Validator.forConstraints(filterConstraints);
|
||||||
|
if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
|
||||||
|
return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest});
|
||||||
|
}
|
||||||
|
|
||||||
|
restParams.filters = filterParams;
|
||||||
|
|
||||||
|
callback(null, restParams);
|
||||||
|
},
|
||||||
|
|
||||||
|
filter (entities, allowedFilterFields, restParams) {
|
||||||
|
let query = restParams.q;
|
||||||
|
if (query) {
|
||||||
|
query = _.toLower(query.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryMatches(entity) {
|
||||||
|
if (!query) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _.some(allowedFilterFields, function (field) {
|
||||||
|
let value = entity[field];
|
||||||
|
if (_.isNumber(value)) {
|
||||||
|
value = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isString(value) || _.isEmpty(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = _.toLower(value);
|
||||||
|
if (field === 'mac') {
|
||||||
|
return _.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.includes(value, query);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = restParams.filters;
|
||||||
|
|
||||||
|
function filtersMatch(entity) {
|
||||||
|
if (_.isEmpty(filters)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.every(filters, function (value, key) {
|
||||||
|
if (_.isUndefined(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_.startsWith(key, 'has')) {
|
||||||
|
const entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
|
||||||
|
return _.isEmpty(entity[entityKey]).toString() !== value;
|
||||||
|
}
|
||||||
|
return entity[key] === value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.filter(entities, function (entity) {
|
||||||
|
return queryMatches(entity) && filtersMatch(entity);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sort (entities, allowedSortFields, restParams) {
|
||||||
|
const sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||||
|
if (!sortField) {
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = _.sortBy(entities, [sortField]);
|
||||||
|
|
||||||
|
return restParams._sortDir === 'ASC' ? sorted : _.reverse(sorted);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPageEntities (entities, restParams) {
|
||||||
|
const page = restParams._page;
|
||||||
|
const perPage = restParams._perPage;
|
||||||
|
|
||||||
|
return entities.slice((page - 1) * perPage, page * perPage);
|
||||||
|
},
|
||||||
|
|
||||||
|
whereCondition: filterCondition,
|
||||||
|
|
||||||
|
filterClause (restParams, defaultSortField, allowedSortFields, filterFields) {
|
||||||
|
const orderBy = orderByClause(
|
||||||
|
restParams,
|
||||||
|
defaultSortField,
|
||||||
|
allowedSortFields
|
||||||
|
);
|
||||||
|
const limitOffset = limitOffsetClause(restParams);
|
||||||
|
|
||||||
|
const filter = filterCondition(
|
||||||
|
restParams,
|
||||||
|
filterFields
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: filter.query + ' ' + orderBy.query + ' ' + limitOffset.query,
|
||||||
|
params: _.concat(filter.params, orderBy.params, limitOffset.params)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
success (res, data) {
|
||||||
|
respond(res, 200, data, 'json');
|
||||||
|
},
|
||||||
|
|
||||||
|
successHtml (res, html) {
|
||||||
|
respond(res, 200, html, 'html');
|
||||||
|
},
|
||||||
|
|
||||||
|
error (res, err) {
|
||||||
|
respond(res, err.type.code, err.data, 'json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('Strings', function (_) {
|
const _ = require('lodash')
|
||||||
return {
|
|
||||||
normalizeString: function (str) {
|
|
||||||
return _.isString(str) ? str.trim().replace(/\s+/g, ' ') : str;
|
|
||||||
},
|
|
||||||
|
|
||||||
normalizeMac: function (mac) {
|
module.exports = {
|
||||||
// parts only contains values at odd indexes
|
normalizeString (str) {
|
||||||
var parts = mac.toUpperCase().replace(/:/g, '').split(/([A-F0-9]{2})/);
|
return _.isString(str) ? str.trim().replace(/\s+/g, ' ') : str;
|
||||||
|
},
|
||||||
|
|
||||||
var macParts = [];
|
normalizeMac (mac) {
|
||||||
|
// parts only contains values at odd indexes
|
||||||
|
const parts = mac.toUpperCase().replace(/:/g, '').split(/([A-F0-9]{2})/);
|
||||||
|
|
||||||
for (var i = 1; i < parts.length; i += 2) {
|
const macParts = [];
|
||||||
macParts.push(parts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return macParts.join(':');
|
for (let i = 1; i < parts.length; i += 2) {
|
||||||
},
|
macParts.push(parts[i]);
|
||||||
|
|
||||||
parseInt: function (str) {
|
|
||||||
var parsed = _.parseInt(str, 10);
|
|
||||||
return parsed.toString() === str ? parsed : undefined;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
return macParts.join(':');
|
||||||
|
},
|
||||||
|
|
||||||
|
parseInt (str) {
|
||||||
|
const parsed = _.parseInt(str, 10);
|
||||||
|
return parsed.toString() === str ? parsed : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,39 +1,41 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('UrlBuilder', function (_, config) {
|
const _ = require('lodash')
|
||||||
function formUrl(route, queryParams) {
|
|
||||||
var url = config.server.baseUrl;
|
const config = require('../config').config
|
||||||
if (route || queryParams) {
|
|
||||||
url += '/#/';
|
function formUrl(route, queryParams) {
|
||||||
}
|
let url = config.server.baseUrl;
|
||||||
if (route) {
|
if (route || queryParams) {
|
||||||
url += route;
|
url += '/#/';
|
||||||
}
|
|
||||||
if (queryParams) {
|
|
||||||
url += '?';
|
|
||||||
url += _.join(
|
|
||||||
_.map(
|
|
||||||
queryParams,
|
|
||||||
function (value, key) {
|
|
||||||
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
'&'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
if (route) {
|
||||||
|
url += route;
|
||||||
|
}
|
||||||
|
if (queryParams) {
|
||||||
|
url += '?';
|
||||||
|
url += _.join(
|
||||||
|
_.map(
|
||||||
|
queryParams,
|
||||||
|
function (value, key) {
|
||||||
|
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'&'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
module.exports = {
|
||||||
editNodeUrl: function () {
|
editNodeUrl () {
|
||||||
return formUrl('update');
|
return formUrl('update');
|
||||||
},
|
},
|
||||||
|
|
||||||
monitoringConfirmUrl: function (nodeSecrets) {
|
monitoringConfirmUrl (nodeSecrets) {
|
||||||
return formUrl('monitoring/confirm', { token: nodeSecrets.monitoringToken });
|
return formUrl('monitoring/confirm', { token: nodeSecrets.monitoringToken });
|
||||||
},
|
},
|
||||||
monitoringDisableUrl: function (nodeSecrets) {
|
monitoringDisableUrl (nodeSecrets) {
|
||||||
return formUrl('monitoring/disable', { token: nodeSecrets.monitoringToken });
|
return formUrl('monitoring/disable', { token: nodeSecrets.monitoringToken });
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
|
||||||
|
|
|
@ -1,93 +1,96 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').factory('Validator', function (_, Strings, Logger) {
|
const _ = require('lodash')
|
||||||
// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)
|
|
||||||
|
|
||||||
function isValidBoolean(value) {
|
const Strings = require('../utils/strings')
|
||||||
return _.isBoolean(value) || value === 'true' || value === 'false';
|
const Logger = require('../logger')
|
||||||
|
|
||||||
|
// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)
|
||||||
|
|
||||||
|
function isValidBoolean(value) {
|
||||||
|
return _.isBoolean(value) || value === 'true' || value === 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidNumber(constraint, value) {
|
||||||
|
if (_.isString(value)) {
|
||||||
|
value = Strings.parseInt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidNumber(constraint, value) {
|
if (!_.isNumber(value)) {
|
||||||
if (_.isString(value)) {
|
|
||||||
value = Strings.parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isNumber(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isNaN(value) || !_.isFinite(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isNumber(constraint.min) && value < constraint.min) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isNumber(constraint.max) && value > constraint.max) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidEnum(constraint, value) {
|
|
||||||
if (!_.isString(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.indexOf(constraint.allowed, value) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidString(constraint, value) {
|
|
||||||
if (!_.isString(value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var trimmed = value.trim();
|
|
||||||
return (trimmed === '' && constraint.optional) || trimmed.match(constraint.regex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValid(constraint, acceptUndefined, value) {
|
|
||||||
if (value === undefined) {
|
|
||||||
return acceptUndefined || constraint.optional;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (constraint.type) {
|
|
||||||
case 'boolean':
|
|
||||||
return isValidBoolean(value);
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
return isValidNumber(constraint, value);
|
|
||||||
|
|
||||||
case 'enum':
|
|
||||||
return isValidEnum(constraint, value);
|
|
||||||
|
|
||||||
case 'string':
|
|
||||||
return isValidString(constraint, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.tag('validation').error('No validation method for constraint type: {}', constraint.type);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function areValid(constraints, acceptUndefined, values) {
|
if (_.isNaN(value) || !_.isFinite(value)) {
|
||||||
var fields = Object.keys(constraints);
|
return false;
|
||||||
for (var i = 0; i < fields.length; i ++) {
|
|
||||||
var field = fields[i];
|
|
||||||
if (!isValid(constraints[field], acceptUndefined, values[field])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (_.isNumber(constraint.min) && value < constraint.min) {
|
||||||
forConstraint: function (constraint, acceptUndefined) {
|
return false;
|
||||||
return _.partial(isValid, constraint, acceptUndefined);
|
}
|
||||||
},
|
|
||||||
forConstraints: function (constraints, acceptUndefined) {
|
if (_.isNumber(constraint.max) && value > constraint.max) {
|
||||||
return _.partial(areValid, constraints, acceptUndefined);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidEnum(constraint, value) {
|
||||||
|
if (!_.isString(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.indexOf(constraint.allowed, value) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidString(constraint, value) {
|
||||||
|
if (!_.isString(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return (trimmed === '' && constraint.optional) || trimmed.match(constraint.regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValid(constraint, acceptUndefined, value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return acceptUndefined || constraint.optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (constraint.type) {
|
||||||
|
case 'boolean':
|
||||||
|
return isValidBoolean(value);
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return isValidNumber(constraint, value);
|
||||||
|
|
||||||
|
case 'enum':
|
||||||
|
return isValidEnum(constraint, value);
|
||||||
|
|
||||||
|
case 'string':
|
||||||
|
return isValidString(constraint, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.tag('validation').error('No validation method for constraint type: {}', constraint.type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function areValid(constraints, acceptUndefined, values) {
|
||||||
|
const fields = Object.keys(constraints);
|
||||||
|
for (let i = 0; i < fields.length; i ++) {
|
||||||
|
const field = fields[i];
|
||||||
|
if (!isValid(constraints[field], acceptUndefined, values[field])) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
forConstraint (constraint, acceptUndefined) {
|
||||||
|
return _.partial(isValid, constraint, acceptUndefined);
|
||||||
|
},
|
||||||
|
forConstraints (constraints, acceptUndefined) {
|
||||||
|
return _.partial(areValid, constraints, acceptUndefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,113 +1,139 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('ffffng').constant('Constraints', {
|
(function () {
|
||||||
id:{
|
var constraints = {
|
||||||
type: 'string',
|
id:{
|
||||||
regex: /^[1-9][0-9]*/,
|
|
||||||
optional: false
|
|
||||||
},
|
|
||||||
token:{
|
|
||||||
type: 'string',
|
|
||||||
regex: /^[0-9a-f]{16}$/i,
|
|
||||||
optional: false
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
hostname: {
|
|
||||||
type: 'string',
|
type: 'string',
|
||||||
regex: /^[-a-z0-9_]{1,32}$/i,
|
regex: /^[1-9][0-9]*/,
|
||||||
optional: false
|
optional: false
|
||||||
},
|
},
|
||||||
key: {
|
token:{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
regex: /^([a-f0-9]{64})$/i,
|
regex: /^[0-9a-f]{16}$/i,
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
|
|
||||||
optional: false
|
optional: false
|
||||||
},
|
},
|
||||||
nickname: {
|
node: {
|
||||||
type: 'string',
|
hostname: {
|
||||||
regex: /^[-a-z0-9_ äöüß]{1,64}$/i,
|
|
||||||
optional: false
|
|
||||||
},
|
|
||||||
mac: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i,
|
|
||||||
optional: false
|
|
||||||
},
|
|
||||||
coords: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
monitoring: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nodeFilters: {
|
|
||||||
hasKey: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
hasCoords: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
onlineState: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^(ONLINE|OFFLINE)$/,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
monitoringState: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^(disabled|active|pending)$/,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
site: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^[a-z0-9_-]{1,32}$/,
|
|
||||||
optional: true
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
regex: /^[a-z0-9_-]{1,32}$/,
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rest: {
|
|
||||||
list: {
|
|
||||||
_page: {
|
|
||||||
type: 'number',
|
|
||||||
min: 1,
|
|
||||||
optional: true,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
_perPage: {
|
|
||||||
type: 'number',
|
|
||||||
min: 1,
|
|
||||||
max: 50,
|
|
||||||
optional: true,
|
|
||||||
default: 20
|
|
||||||
},
|
|
||||||
_sortDir: {
|
|
||||||
type: 'enum',
|
|
||||||
allowed: ['ASC', 'DESC'],
|
|
||||||
optional: true,
|
|
||||||
default: 'ASC'
|
|
||||||
},
|
|
||||||
_sortField: {
|
|
||||||
type: 'string',
|
type: 'string',
|
||||||
regex: /^[a-zA-Z0-9_]{1,32}$/,
|
regex: /^[-a-z0-9_]{1,32}$/i,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^([a-f0-9]{64})$/i,
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
q: {
|
email: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
regex: /^[äöüß a-z0-9!#$%&@:.'*+/=?^_`{|}~-]{1,64}$/i,
|
regex: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
nickname: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[-a-z0-9_ äöüß]{1,64}$/i,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
coords: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/,
|
||||||
optional: true
|
optional: true
|
||||||
|
},
|
||||||
|
monitoring: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeFilters: {
|
||||||
|
hasKey: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
hasCoords: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
onlineState: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^(ONLINE|OFFLINE)$/,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
monitoringState: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^(disabled|active|pending)$/,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[a-z0-9_-]{1,32}$/,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[a-z0-9_-]{1,32}$/,
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rest: {
|
||||||
|
list: {
|
||||||
|
_page: {
|
||||||
|
type: 'number',
|
||||||
|
min: 1,
|
||||||
|
optional: true,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
_perPage: {
|
||||||
|
type: 'number',
|
||||||
|
min: 1,
|
||||||
|
max: 50,
|
||||||
|
optional: true,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
_sortDir: {
|
||||||
|
type: 'enum',
|
||||||
|
allowed: ['ASC', 'DESC'],
|
||||||
|
optional: true,
|
||||||
|
default: 'ASC'
|
||||||
|
},
|
||||||
|
_sortField: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[a-zA-Z0-9_]{1,32}$/,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
q: {
|
||||||
|
type: 'string',
|
||||||
|
regex: /^[äöüß a-z0-9!#$%&@:.'*+/=?^_`{|}~-]{1,64}$/i,
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
let _angular = null
|
||||||
|
try {
|
||||||
|
_angular = angular
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// ReferenceError, as angular is not defined.
|
||||||
|
}
|
||||||
|
|
||||||
|
let _module = null
|
||||||
|
try {
|
||||||
|
_module = module
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// ReferenceError, as module is not defined.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_angular) {
|
||||||
|
angular.module('ffffng').constant('Constraints', constraints)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_module) {
|
||||||
|
module.exports = constraints
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
Loading…
Reference in a new issue