Move UI configs to DB

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-01-16 05:22:14 -08:00
1 parent 192ed6b034
commit 1cceba3f8b
22 files changed
+335 -446

No files matched your search

+12 -1
View File
@@ -11,6 +11,7 @@ const cluster = require("../lib/cluster");
const server = require("../lib/server"); const server = require("../lib/server");
const ui = require("../lib/ui"); const ui = require("../lib/ui");
const db = require("../lib/ui/db"); const db = require("../lib/ui/db");
const db2 = require("../lib/db");
const cache = require("../lib/cache"); const cache = require("../lib/cache");
const VERSION = require("../package.json").version; const VERSION = require("../package.json").version;
@@ -22,6 +23,7 @@ const SERVICE_PORT = config.get("UI_PORT");
function exitWorkerGracefully() { function exitWorkerGracefully() {
db.disconnect(); db.disconnect();
db2.disconnect();
cache.disconnect(); cache.disconnect();
cluster.worker.disconnect(); cluster.worker.disconnect();
} }
@@ -105,8 +107,17 @@ if (!cluster.worker) {
ui.listener(req, res); ui.listener(req, res);
}; };
db2.connect(err => {
if (err) throw err;
cache.connect(err => { cache.connect(err => {
if (err) throw err; if (err) throw err;
server.start(SERVICE_PORT, SERVICE_ADDRESS, ssl, listener, ui.onConnection); server.start(
SERVICE_PORT,
SERVICE_ADDRESS,
ssl,
listener,
ui.onConnection
);
});
}); });
} }
-1
View File
@@ -78,7 +78,6 @@ async function copyStatic() {
"CHANGELOG.md", "CHANGELOG.md",
"npm-shrinkwrap.json", "npm-shrinkwrap.json",
"config/config-sample.json", "config/config-sample.json",
"config/config-ui-sample.json",
"config/auth-sample.js", "config/auth-sample.js",
"config/ext-sample.js", "config/ext-sample.js",
"public/logo.svg", "public/logo.svg",
+1
View File
@@ -10,5 +10,6 @@
"FS_HOSTNAME" : "acs.example.com", "FS_HOSTNAME" : "acs.example.com",
"UI_INTERFACE" : "0.0.0.0", "UI_INTERFACE" : "0.0.0.0",
"UI_PORT" : 3000, "UI_PORT" : 3000,
"UI_JWT_SECRET": "",
"DEBUG" : false "DEBUG" : false
} }
-313
View File
@@ -1,313 +0,0 @@
{
"server": {
"nbi": "http://127.0.0.1:7557/",
"mongodbConnectionUrl": "mongodb://127.0.0.1:27017",
"jwtSecret": "jwtSecret"
},
"auth": {
"simple": {
"users": {
"admin": {
"password": "admin",
"roles": "admin"
},
"view": {
"password": "view",
"roles": "view"
}
}
}
},
"permissions": {
"admin": [
{
"devices": {
"access": 3,
"validate": {
"pass": true
}
},
"faults": {
"access": 3,
"validate": {
"pass": true
}
},
"presets": {
"access": 3,
"validate": {
"pass": true
}
},
"provisions" : {
"access": 3,
"validate": {
"pass": true
}
},
"virtualParameters" : {
"access": 3,
"validate": {
"pass": true
}
},
"files": {
"access": 3,
"validate": {
"pass": true
}
}
}
],
"view": [
{
"devices": {
"access": 2
},
"faults": {
"access": 2
},
"presets": {
"access": 2
},
"provisions": {
"access": 2
},
"virtualParameters": {
"access": 2
},
"files": {
"access": 2
}
}
]
},
"ui": {
"pageSize": 10,
"filters": [
{
"label": "Serial number",
"parameter": "DeviceID.SerialNumber",
"type": "string"
},
{
"label": "Product class",
"parameter": "DeviceID.ProductClass",
"type": "string"
},
{
"label": "Tag",
"parameter": "tag",
"type": "string"
}
],
"index": [
{
"label": "Serial number",
"parameter": "DeviceID.SerialNumber",
"type": "device-link",
"components": [
{
"type": "parameter"
}
]
},
{
"label": "Product class",
"parameter": "DeviceID.ProductClass"
},
{
"label": "Software version",
"parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
},
{
"label": "IP",
"parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress"
},
{
"label": "SSID",
"parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID"
},
{
"label": "Last inform",
"parameter": "DATE_STRING(Events.Inform)",
"type": "container",
"element": "span.inform",
"components": [
{
"type": "parameter"
},
{
"type": "overview-dot",
"chart": "online"
}
]
},
{
"label": "Tags",
"parameter": "Tags",
"type": "tags",
"writable": false,
"unsortable": true
}
],
"overview": {
"groups": {
"online": {
"label": "",
"charts": [
"online"
]
}
},
"charts": {
"online": {
"label": "Online status",
"slices": {
"onlineNow": {
"label": "Online now",
"color": "#31a354",
"filter": "Events.Inform > NOW() - 5 * 60 * 1000"
},
"past24": {
"label": "Past 24 hours",
"color": "#addd8e",
"filter": "Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)"
},
"others": {
"label": "Others",
"color": "#f7fcb9",
"filter": "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)"
}
}
}
}
},
"device": [
{
"type": "tags"
},
{
"type": "ping"
},
{
"type": "parameter-list",
"parameters": [
{
"type": "container",
"label": "Last inform",
"element": "span.inform",
"parameter": "DATE_STRING(Events.Inform)",
"components": [
{
"type": "parameter"
},
{
"type": "overview-dot",
"chart": "online"
},
{
"type": "summon-button",
"parameters": [
"InternetGatewayDevice.DeviceInfo.HardwareVersion",
"InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress",
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase",
"InternetGatewayDevice.LANDevice.1.Hosts.Host"
]
}
]
},
{
"label": "Serial number",
"parameter": "InternetGatewayDevice.DeviceInfo.SerialNumber"
},
{
"label": "Product class",
"parameter": "InternetGatewayDevice.DeviceInfo.ProductClass"
},
{
"label": "OUI",
"parameter": "InternetGatewayDevice.DeviceInfo.ManufacturerOUI"
},
{
"label": "Manufacturer",
"parameter": "InternetGatewayDevice.DeviceInfo.Manufacturer"
},
{
"label": "Hardware version",
"parameter": "InternetGatewayDevice.DeviceInfo.HardwareVersion"
},
{
"label": "Software version",
"parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
},
{
"label": "MAC",
"parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress"
},
{
"label": "IP",
"parameter": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress"
},
{
"label": "WLAN SSID",
"parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID"
},
{
"label": "WLAN passphrase",
"parameter": "InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase"
}
]
},
{
"type": "parameter-table",
"label": "WLAN hosts",
"parameter": "InternetGatewayDevice.LANDevice.1.Hosts.Host",
"childParameters": [
{
"label": "Host name",
"parameter": "HostName"
},
{
"label": "IP address",
"parameter": "IPAddress"
},
{
"label": "MAC address",
"parameter": "MACAddress"
}
]
},
{
"type": "container",
"element": "div",
"components": [
{
"type": "container",
"element": "h3",
"components": ["Faults"]
},
{"type": "device-faults"}
]
},
{
"type": "container",
"element": "div.container-full-width",
"components": [
{
"type": "container",
"element": "h3",
"components": ["All parameters"]
},
{"type": "all-parameters"}
]
},
{
"type": "device-actions"
}
]
}
}
+1
View File
@@ -59,6 +59,7 @@ const options = {
UI_SSL: { type: "bool", default: false }, UI_SSL: { type: "bool", default: false },
UI_LOG_FILE: { type: "path", default: "" }, UI_LOG_FILE: { type: "path", default: "" },
UI_ACCESS_LOG_FILE: { type: "path", default: "" }, UI_ACCESS_LOG_FILE: { type: "path", default: "" },
UI_JWT_SECRET: { type: "string", default: "" },
UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 }, UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 },
+62 -3
View File
@@ -307,8 +307,30 @@ function refresh(callback) {
db.getConfig((err, conf) => { db.getConfig((err, conf) => {
if (err) return void reject(err); if (err) return void reject(err);
conf.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
const _config = {}; const _config = {};
for (const c of conf) _config[c.id] = c.value;
for (const c of conf) {
if (
c.id.startsWith("auth.") ||
c.id.startsWith("permissions.") ||
c.id.startsWith("ui.")
) {
const keys = c.id.split(".");
let ref = _config;
while (keys.length > 1) {
const k = keys.shift();
if (typeof ref[k] !== "object") ref[k] = {};
ref = ref[k];
}
// Evaluate expressions to simplify them
ref[keys[0]] = expression.evaluate(c.value);
} else {
// Evaluate expressions to simplify them
_config[c.id] = expression.evaluate(c.value);
}
}
resolve(_config); resolve(_config);
}); });
@@ -348,12 +370,25 @@ function refresh(callback) {
} }
function getCurrentSnapshot(callback) { function getCurrentSnapshot(callback) {
if (Date.now() < nextRefresh) return void callback(null, currentSnapshot); if (Date.now() < nextRefresh) {
refresh(err => { if (callback) return callback(null, currentSnapshot);
return Promise.resolve(currentSnapshot);
}
if (callback) {
return refresh(err => {
callback(err, currentSnapshot); callback(err, currentSnapshot);
}); });
} }
return new Promise((resolve, reject) => {
refresh(err => {
if (err) return void reject(err);
resolve(currentSnapshot);
});
});
}
function hasSnapshot(hash) { function hasSnapshot(hash) {
return snapshots.has(hash); return snapshots.has(hash);
} }
@@ -410,6 +445,27 @@ function getConfig(snapshotKey, key, context, now) {
return expression.evaluate(snapshot.config[key], context, now || Date.now()); return expression.evaluate(snapshot.config[key], context, now || Date.now());
} }
function getAuthConfig(snapshotKey) {
const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.config.auth;
}
function getPermissionsConfig(snapshotKey) {
const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.config.permissions;
}
function getUiConfig(snapshotKey) {
const snapshot = snapshots.get(snapshotKey);
if (!snapshot) throw new Error("Cache snapshot does not exist");
return snapshot.config.ui;
}
exports.getCurrentSnapshot = getCurrentSnapshot; exports.getCurrentSnapshot = getCurrentSnapshot;
exports.hasSnapshot = hasSnapshot; exports.hasSnapshot = hasSnapshot;
exports.getPresets = getPresets; exports.getPresets = getPresets;
@@ -417,3 +473,6 @@ exports.getProvisions = getProvisions;
exports.getVirtualParameters = getVirtualParameters; exports.getVirtualParameters = getVirtualParameters;
exports.getFiles = getFiles; exports.getFiles = getFiles;
exports.getConfig = getConfig; exports.getConfig = getConfig;
exports.getAuthConfig = getAuthConfig;
exports.getPermissionsConfig = getPermissionsConfig;
exports.getUiConfig = getUiConfig;
+32 -17
View File
@@ -8,21 +8,24 @@ const koaBodyParser = require("koa-bodyparser");
const koaJwt = require("koa-jwt"); const koaJwt = require("koa-jwt");
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
const config = require("./ui/config"); const config = require("./config");
const api = require("./ui/api"); const api = require("./ui/api");
const Authorizer = require("./common/authorizer"); const Authorizer = require("./common/authorizer");
const expression = require("./common/expression"); const expression = require("./common/expression");
const logger = require("./logger"); const logger = require("./logger");
const localCache = require("./local-cache");
const koa = new Koa(); const koa = new Koa();
const router = new Router(); const router = new Router();
const JWT_SECRET = config.server.jwtSecret; const JWT_SECRET = config.get("UI_JWT_SECRET");
const JWT_COOKIE = "genieacs-ui-jwt"; const JWT_COOKIE = "genieacs-ui-jwt";
function getPermissionSets(roles) { function getPermissionSets(ctx) {
const allPermissions = config.permissions; const allPermissions = localCache.getPermissionsConfig(
const permissionSets = roles.map(role => { ctx.state.configSnapshot
);
const permissionSets = ctx.state.user.roles.map(role => {
return Object.values(allPermissions[role] || {}).map(p => { return Object.values(allPermissions[role] || {}).map(p => {
p = Object.assign({}, p); p = Object.assign({}, p);
for (let [k, v] of Object.entries(p)) { for (let [k, v] of Object.entries(p)) {
@@ -47,8 +50,9 @@ function getPermissionSets(roles) {
return permissionSets; return permissionSets;
} }
function authSimple(username, password) { function authSimple(ctx, username, password) {
const user = config.auth.simple.users[username]; const authConfig = localCache.getAuthConfig(ctx.state.configSnapshot);
const user = authConfig.simple.users[username];
if (user && user.password === password) { if (user && user.password === password) {
const roles = user.roles.split(",").map(s => s.trim()); const roles = user.roles.split(",").map(s => s.trim());
return Promise.resolve(roles); return Promise.resolve(roles);
@@ -60,6 +64,12 @@ koa.on("error", async err => {
throw err; throw err;
}); });
koa.use(async (ctx, next) => {
const configSnapshot = await localCache.getCurrentSnapshot();
ctx.state.configSnapshot = configSnapshot;
return next();
});
koa.use( koa.use(
koaJwt({ koaJwt({
secret: JWT_SECRET, secret: JWT_SECRET,
@@ -69,18 +79,21 @@ koa.use(
); );
koa.use(async (ctx, next) => { koa.use(async (ctx, next) => {
if (ctx.state.user && ctx.state.user.roles) { if (ctx.state.user && ctx.state.user.roles)
ctx.state.authorizer = new Authorizer( ctx.state.authorizer = new Authorizer(getPermissionSets(ctx));
getPermissionSets(ctx.state.user.roles) else ctx.state.authorizer = new Authorizer([]);
);
} else {
ctx.state.authorizer = new Authorizer([]);
}
return next(); return next();
}); });
router.post("/login", async ctx => { router.post("/login", async ctx => {
if (!JWT_SECRET) {
ctx.status = 500;
ctx.body = "UI_JWT_SECRET is not set";
logger.error({ message: "UI_JWT_SECRET is not set" });
return;
}
const username = ctx.request.body.username; const username = ctx.request.body.username;
const password = ctx.request.body.password; const password = ctx.request.body.password;
@@ -105,7 +118,7 @@ router.post("/login", async ctx => {
logger.accessWarn(log); logger.accessWarn(log);
} }
const roles = await authSimple(username, password); const roles = await authSimple(ctx, username, password);
if (roles) return void success(roles, "simple"); if (roles) return void success(roles, "simple");
@@ -135,7 +148,7 @@ router.use("/api", api.routes(), api.allowedMethods());
router.get("/", async ctx => { router.get("/", async ctx => {
let permissionSets = []; let permissionSets = [];
if (ctx.state.user && ctx.state.user.roles) if (ctx.state.user && ctx.state.user.roles)
permissionSets = getPermissionSets(ctx.state.user.roles); permissionSets = getPermissionSets(ctx);
ctx.body = ` ctx.body = `
<html> <html>
@@ -146,7 +159,9 @@ router.get("/", async ctx => {
</head> </head>
<body> <body>
<script> <script>
window.clientConfig = ${JSON.stringify(config.getClientConfig())}; window.clientConfig = ${JSON.stringify({
ui: localCache.getUiConfig(ctx.state.configSnapshot)
})};
window.username = ${JSON.stringify( window.username = ${JSON.stringify(
ctx.state.user ? ctx.state.user.username : "" ctx.state.user ? ctx.state.user.username : ""
)}; )};
+19 -9
View File
@@ -4,7 +4,7 @@ const http = require("http");
const https = require("https"); const https = require("https");
const url = require("url"); const url = require("url");
const config = require("./config"); const config = require("../config");
const db = require("./db"); const db = require("./db");
const mongodbFunctions = require("./mongodb-functions"); const mongodbFunctions = require("./mongodb-functions");
@@ -71,7 +71,9 @@ function deleteResource(resource, id) {
const resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
const options = url.parse( const options = url.parse(
`${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}` `http://127.0.0.1:${config.get(
"NBI_PORT"
)}/${resourceUrl}/${encodeURIComponent(id)}`
); );
options.method = "DELETE"; options.method = "DELETE";
@@ -91,9 +93,9 @@ function deleteResource(resource, id) {
function postTask(task, connectionRequest, callback) { function postTask(task, connectionRequest, callback) {
const options = url.parse( const options = url.parse(
`${config.server.nbi}devices/${encodeURIComponent(task.device)}/tasks${ `http://127.0.0.1:${config.get("NBI_PORT")}/devices/${encodeURIComponent(
connectionRequest ? "?connection_request" : "" task.device
}` )}/tasks${connectionRequest ? "?connection_request" : ""}`
); );
options.method = "POST"; options.method = "POST";
const _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
@@ -210,7 +212,9 @@ function updateTags(deviceId, tags) {
([tag, onOff]) => ([tag, onOff]) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const options = url.parse( const options = url.parse(
`${config.server.nbi}devices/${encodeURIComponent( `http://127.0.0.1:${config.get(
"NBI_PORT"
)}/devices/${encodeURIComponent(
deviceId deviceId
)}/tags/${encodeURIComponent(tag)}` )}/tags/${encodeURIComponent(tag)}`
); );
@@ -234,7 +238,9 @@ function updateTags(deviceId, tags) {
function ping(host) { function ping(host) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options = url.parse( const options = url.parse(
`${config.server.nbi}ping/${encodeURIComponent(host)}` `http://127.0.0.1:${config.get("NBI_PORT")}/ping/${encodeURIComponent(
host
)}`
); );
const _http = options.protocol === "https:" ? https : http; const _http = options.protocol === "https:" ? https : http;
@@ -294,7 +300,9 @@ function putResource(resource, id, data) {
const resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
const options = url.parse( const options = url.parse(
`${config.server.nbi}${resourceUrl}/${encodeURIComponent(id)}` `http://127.0.0.1:${config.get(
"NBI_PORT"
)}/${resourceUrl}/${encodeURIComponent(id)}`
); );
options.method = "PUT"; options.method = "PUT";
@@ -343,7 +351,9 @@ function putResource(resource, id, data) {
function putFile(id, metadata, fileStream) { function putFile(id, metadata, fileStream) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options = url.parse( const options = url.parse(
`${config.server.nbi}files/${encodeURIComponent(id)}` `http://127.0.0.1:${config.get("NBI_PORT")}/files/${encodeURIComponent(
id
)}`
); );
options.method = "PUT"; options.method = "PUT";
-36
View File
@@ -1,36 +0,0 @@
"use strict";
const fs = require("fs");
const configFile = JSON.parse(fs.readFileSync("./config/config-ui.json"));
const config = loadConfig(configFile);
function getClientConfig() {
return Object.assign({}, config, {
server: null,
auth: null,
permissions: null
});
}
function loadConfig(conf, root = {}) {
for (const [key, value] of Object.entries(conf)) {
const keys = key.split(".");
let ref = root;
while (keys.length > 1) {
const k = keys.shift();
if (typeof ref[k] !== "object") ref[k] = {};
ref = ref[k];
}
if (typeof value === "object") {
if (typeof ref[keys[0]] !== "object") ref[keys[0]] = {};
loadConfig(value, ref[keys[0]]);
} else {
ref[keys[0]] = value;
}
}
return root;
}
module.exports = Object.assign({}, config, { getClientConfig });
+6 -17
View File
@@ -1,7 +1,7 @@
"use strict"; "use strict";
const mongodb = require("mongodb"); const mongodb = require("mongodb");
const config = require("./config"); const config = require("../config");
const mongodbFunctions = require("./mongodb-functions"); const mongodbFunctions = require("./mongodb-functions");
const expression = require("../common/expression"); const expression = require("../common/expression");
@@ -9,24 +9,13 @@ const CACHE_TTL = 300000;
let _clientPromise = null; let _clientPromise = null;
const RESOURCE_DB = {
devices: "genieacs",
faults: "genieacs",
tasks: "genieacs",
presets: "genieacs",
provisions: "genieacs",
virtualParameters: "genieacs",
files: "genieacs",
cache: "genieacs-ui"
};
const RESOURCE_COLLECTION = { const RESOURCE_COLLECTION = {
files: "fs.files" files: "fs.files"
}; };
function ensureIndexes(client) { function ensureIndexes(client) {
client client
.db(RESOURCE_DB["cache"]) .db()
.collection("cache") .collection("cache")
.createIndex({ expire: 1 }, { expireAfterSeconds: 0 }); .createIndex({ expire: 1 }, { expireAfterSeconds: 0 });
} }
@@ -34,7 +23,7 @@ function ensureIndexes(client) {
function getClient() { function getClient() {
if (!_clientPromise) { if (!_clientPromise) {
_clientPromise = new Promise((resolve, reject) => { _clientPromise = new Promise((resolve, reject) => {
const CONNECTION_URL = config.server.mongodbConnectionUrl; const CONNECTION_URL = config.get("MONGODB_CONNECTION_URL");
mongodb.MongoClient.connect( mongodb.MongoClient.connect(
CONNECTION_URL, CONNECTION_URL,
{ useNewUrlParser: true }, { useNewUrlParser: true },
@@ -54,7 +43,7 @@ function cache(key, valueGetter, ttl) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient() getClient()
.then(client => { .then(client => {
const collection = client.db(RESOURCE_DB["cache"]).collection("cache"); const collection = client.db().collection("cache");
collection.findOne({ _id: key }, (err, doc) => { collection.findOne({ _id: key }, (err, doc) => {
if (err) return void reject(err); if (err) return void reject(err);
if (doc != null) return void resolve(JSON.parse(doc.value)); if (doc != null) return void resolve(JSON.parse(doc.value));
@@ -103,7 +92,7 @@ function query(resource, filter, options, callback) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient().then(client => { getClient().then(client => {
const collection = client const collection = client
.db(RESOURCE_DB[resource]) .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
const cursor = collection.find(q); const cursor = collection.find(q);
if (options.projection) { if (options.projection) {
@@ -188,7 +177,7 @@ function count(resource, filter) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getClient().then(client => { getClient().then(client => {
const collection = client const collection = client
.db(RESOURCE_DB[resource]) .db()
.collection(RESOURCE_COLLECTION[resource] || resource); .collection(RESOURCE_COLLECTION[resource] || resource);
collection.find(q).count((err, c) => { collection.find(q).count((err, c) => {
if (err) reject(err); if (err) reject(err);
+1 -1
View File
@@ -53,7 +53,7 @@
}, },
"scripts": { "scripts": {
"compile": "echo 'Compile step no longer required, skipping.'", "compile": "echo 'Compile step no longer required, skipping.'",
"configure": "if [ ! -f ./config/config.json ]; then cp ./config/config-sample.json ./config/config.json; cp ./config/config-ui-sample.json ./config/config-ui.json; fi", "configure": "if [ ! -f ./config/config.json ]; then cp ./config/config-sample.json ./config/config.json; fi",
"install": "npm run configure", "install": "npm run configure",
"test": "node test", "test": "node test",
"lint": "eslint --fix lib ui build bin/*; prettier --write \"lib/**/*.js\" \"ui/**/*.js\" \"build/**/*.js\" bin/*", "lint": "eslint --fix lib ui build bin/*; prettier --write \"lib/**/*.js\" \"ui/**/*.js\" \"build/**/*.js\" bin/*",
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env node
"use strict";
const mongodb = require("mongodb");
const config = require("../lib/config");
const configData = {
"auth.simple.users.admin.password": "'admin'",
"auth.simple.users.admin.roles": "'admin'",
"auth.simple.users.view.password": "'view'",
"auth.simple.users.view.roles": "'view'",
"permissions.admin.0.devices.access": "3",
"permissions.admin.0.devices.validate.pass": "true",
"permissions.admin.0.faults.access": "3",
"permissions.admin.0.faults.validate.pass": "true",
"permissions.admin.0.files.access": "3",
"permissions.admin.0.files.validate.pass": "true",
"permissions.admin.0.presets.access": "3",
"permissions.admin.0.presets.validate.pass": "true",
"permissions.admin.0.provisions.access": "3",
"permissions.admin.0.provisions.validate.pass": "true",
"permissions.admin.0.virtualParameters.access": "3",
"permissions.admin.0.virtualParameters.validate.pass": "true",
"permissions.view.0.devices.access": "2",
"permissions.view.0.faults.access": "2",
"permissions.view.0.files.access": "2",
"permissions.view.0.presets.access": "2",
"permissions.view.0.provisions.access": "2",
"permissions.view.0.virtualParameters.access": "2",
"ui.device.0.type": "'tags'",
"ui.device.1.type": "'ping'",
"ui.device.2.parameters.0.components.0.type": "'parameter'",
"ui.device.2.parameters.0.components.1.chart": "'online'",
"ui.device.2.parameters.0.components.1.type": "'overview-dot'",
"ui.device.2.parameters.0.components.2.parameters.0":
"InternetGatewayDevice.DeviceInfo.HardwareVersion",
"ui.device.2.parameters.0.components.2.parameters.1":
"InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"ui.device.2.parameters.0.components.2.parameters.2":
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress",
"ui.device.2.parameters.0.components.2.parameters.3":
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"ui.device.2.parameters.0.components.2.parameters.4":
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
"ui.device.2.parameters.0.components.2.parameters.5":
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase",
"ui.device.2.parameters.0.components.2.parameters.6":
"InternetGatewayDevice.LANDevice.1.Hosts.Host",
"ui.device.2.parameters.0.components.2.type": "'summon-button'",
"ui.device.2.parameters.0.element": "'span.inform'",
"ui.device.2.parameters.0.label": "'Last inform'",
"ui.device.2.parameters.0.parameter": "DATE_STRING(Events.Inform)",
"ui.device.2.parameters.0.type": "'container'",
"ui.device.2.parameters.1.label": "'Serial number'",
"ui.device.2.parameters.1.parameter":
"InternetGatewayDevice.DeviceInfo.SerialNumber",
"ui.device.2.parameters.10.label": "'WLAN passphrase'",
"ui.device.2.parameters.10.parameter":
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase",
"ui.device.2.parameters.2.label": "'Product class'",
"ui.device.2.parameters.2.parameter":
"InternetGatewayDevice.DeviceInfo.ProductClass",
"ui.device.2.parameters.3.label": "'OUI'",
"ui.device.2.parameters.3.parameter":
"InternetGatewayDevice.DeviceInfo.ManufacturerOUI",
"ui.device.2.parameters.4.label": "'Manufacturer'",
"ui.device.2.parameters.4.parameter":
"InternetGatewayDevice.DeviceInfo.Manufacturer",
"ui.device.2.parameters.5.label": "'Hardware version'",
"ui.device.2.parameters.5.parameter":
"InternetGatewayDevice.DeviceInfo.HardwareVersion",
"ui.device.2.parameters.6.label": "'Software version'",
"ui.device.2.parameters.6.parameter":
"InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"ui.device.2.parameters.7.label": "'MAC'",
"ui.device.2.parameters.7.parameter":
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress",
"ui.device.2.parameters.8.label": "'IP'",
"ui.device.2.parameters.8.parameter":
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"ui.device.2.parameters.9.label": "'WLAN SSID'",
"ui.device.2.parameters.9.parameter":
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
"ui.device.2.type": "'parameter-list'",
"ui.device.3.childParameters.0.label": "'Host name'",
"ui.device.3.childParameters.0.parameter": "HostName",
"ui.device.3.childParameters.1.label": "'IP address'",
"ui.device.3.childParameters.1.parameter": "IPAddress",
"ui.device.3.childParameters.2.label": "'MAC address'",
"ui.device.3.childParameters.2.parameter": "MACAddress",
"ui.device.3.label": "'WLAN hosts'",
"ui.device.3.parameter": "InternetGatewayDevice.LANDevice.1.Hosts.Host",
"ui.device.3.type": "'parameter-table'",
"ui.device.4.components.0.components.0": "'Faults'",
"ui.device.4.components.0.element": "'h3'",
"ui.device.4.components.0.type": "'container'",
"ui.device.4.components.1.type": "'device-faults'",
"ui.device.4.element": "'div'",
"ui.device.4.type": "'container'",
"ui.device.5.components.0.components.0": "'All parameters'",
"ui.device.5.components.0.element": "'h3'",
"ui.device.5.components.0.type": "'container'",
"ui.device.5.components.1.type": "'all-parameters'",
"ui.device.5.element": "'div.container-full-width'",
"ui.device.5.type": "'container'",
"ui.device.6.type": "'device-actions'",
"ui.filters.0.label": "'Serial number'",
"ui.filters.0.parameter": "DeviceID.SerialNumber",
"ui.filters.0.type": "'string'",
"ui.filters.1.label": "'Product class'",
"ui.filters.1.parameter": "DeviceID.ProductClass",
"ui.filters.1.type": "'string'",
"ui.filters.2.label": "'Tag'",
"ui.filters.2.parameter": "tag",
"ui.filters.2.type": "'string'",
"ui.index.0.components.0.type": "'parameter'",
"ui.index.0.label": "'Serial number'",
"ui.index.0.parameter": "DeviceID.SerialNumber",
"ui.index.0.type": "'device-link'",
"ui.index.1.label": "'Product class'",
"ui.index.1.parameter": "DeviceID.ProductClass",
"ui.index.2.label": "'Software version'",
"ui.index.2.parameter": "InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"ui.index.3.label": "'IP'",
"ui.index.3.parameter":
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"ui.index.4.label": "'SSID'",
"ui.index.4.parameter":
"InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID",
"ui.index.5.components.0.type": "'parameter'",
"ui.index.5.components.1.chart": "'online'",
"ui.index.5.components.1.type": "'overview-dot'",
"ui.index.5.element": "'span.inform'",
"ui.index.5.label": "'Last inform'",
"ui.index.5.parameter": "DATE_STRING(Events.Inform)",
"ui.index.5.type": "'container'",
"ui.index.6.label": "'Tags'",
"ui.index.6.parameter": "Tags",
"ui.index.6.type": "'tags'",
"ui.index.6.unsortable": "true",
"ui.index.6.writable": "false",
"ui.overview.charts.online.label": "'Online status'",
"ui.overview.charts.online.slices.1_onlineNow.color": "'#31a354'",
"ui.overview.charts.online.slices.1_onlineNow.filter":
"Events.Inform > NOW() - 5 * 60 * 1000",
"ui.overview.charts.online.slices.1_onlineNow.label": "'Online now'",
"ui.overview.charts.online.slices.2_past24.color": "'#addd8e'",
"ui.overview.charts.online.slices.2_past24.filter":
"Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)",
"ui.overview.charts.online.slices.2_past24.label": "'Past 24 hours'",
"ui.overview.charts.online.slices.3_others.color": "'#f7fcb9'",
"ui.overview.charts.online.slices.3_others.filter":
"Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)",
"ui.overview.charts.online.slices.3_others.label": "'Others'",
"ui.overview.groups.online.charts.0": "'online'",
"ui.overview.groups.online.label": "''",
"ui.pageSize": "10"
};
async function run() {
const client = await mongodb.MongoClient.connect(
config.get("MONGODB_CONNECTION_URL"),
{ useNewUrlParser: true }
);
const collection = client.db().collection("config");
await collection.deleteMany({});
const objs = Object.entries(configData).map(c => ({
_id: c[0],
value: c[1]
}));
await collection.insertMany(objs);
client.close();
}
run();
+5 -1
View File
@@ -4,6 +4,10 @@ import m from "mithril";
import * as components from "../components"; import * as components from "../components";
import * as taskQueue from "../task-queue"; import * as taskQueue from "../task-queue";
import longTextComponent from "../long-text-component"; import longTextComponent from "../long-text-component";
import * as expression from "../../lib/common/expression";
import memoize from "../../lib/common/memoize";
const memoizedParse = memoize(expression.parse);
function escapeRegExp(str) { function escapeRegExp(str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&"); return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
@@ -47,7 +51,7 @@ const component = {
val.push( val.push(
m( m(
components.get("parameter"), components.get("parameter"),
Object.assign({ device: device, parameter: k }) Object.assign({ device: device, parameter: memoizedParse(k) })
) )
); );
} else if (p.object && p.writable) { } else if (p.object && p.writable) {
+2 -6
View File
@@ -3,16 +3,12 @@
import m from "mithril"; import m from "mithril";
import * as components from "../components"; import * as components from "../components";
import * as store from "../store"; import * as store from "../store";
import * as expression from "../../lib/common/expression";
import memoize from "../../lib/common/memoize";
const memoizedParse = memoize(expression.parse);
const component = { const component = {
view: vnode => { view: vnode => {
if (vnode.attrs.filter) { if (vnode.attrs.filter) {
const filter = memoizedParse(vnode.attrs.filter); if (!store.evaluateExpression(vnode.attrs.filter, vnode.attrs.device))
if (!store.evaluateExpression(filter, vnode.attrs.device)) return null; return null;
} }
const children = Object.values(vnode.attrs.components).map(c => { const children = Object.values(vnode.attrs.components).map(c => {
+1 -5
View File
@@ -3,10 +3,6 @@
import m from "mithril"; import m from "mithril";
import config from "../config"; import config from "../config";
import * as store from "../store"; import * as store from "../store";
import * as expression from "../../lib/common/expression";
import memoize from "../../lib/common/memoize";
const memoizedParse = memoize(expression.parse);
const CHARTS = config.ui.overview.charts; const CHARTS = config.ui.overview.charts;
@@ -15,7 +11,7 @@ const component = {
const device = vnode.attrs.device; const device = vnode.attrs.device;
const chart = CHARTS[vnode.attrs.chart]; const chart = CHARTS[vnode.attrs.chart];
for (const slice of Object.values(chart.slices)) { for (const slice of Object.values(chart.slices)) {
const filter = memoizedParse(slice.filter); const filter = slice.filter;
if (store.evaluateExpression(filter, device)) { if (store.evaluateExpression(filter, device)) {
const dot = m( const dot = m(
"svg", "svg",
+4 -15
View File
@@ -4,25 +4,15 @@ import m from "mithril";
import * as components from "../components"; import * as components from "../components";
import * as taskQueue from "../task-queue"; import * as taskQueue from "../task-queue";
import * as store from "../store"; import * as store from "../store";
import * as expression from "../../lib/common/expression";
import * as expressionParser from "../../lib/common/expression-parser"; import * as expressionParser from "../../lib/common/expression-parser";
import memoize from "../../lib/common/memoize";
const memoizeParse = memoize(expression.parse);
const component = { const component = {
oninit: vnode => { oninit: vnode => {
const obj = expression.parse(vnode.attrs.parameter); const obj = vnode.attrs.parameter;
if (!Array.isArray(obj) || !obj[0] === "PARAM") if (!Array.isArray(obj) || !obj[0] === "PARAM")
throw new Error("Object must be a parameter path"); throw new Error("Object must be a parameter path");
vnode.state.object = obj[1]; vnode.state.object = obj[1];
vnode.state.parameters = Object.values(vnode.attrs.childParameters).map( vnode.state.parameters = Object.values(vnode.attrs.childParameters);
parameter => {
return Object.assign({}, parameter, {
parameter: expression.parse(parameter.parameter)
});
}
);
}, },
view: vnode => { view: vnode => {
const device = vnode.attrs.device; const device = vnode.attrs.device;
@@ -47,8 +37,7 @@ const component = {
const rows = []; const rows = [];
for (const i of instances) { for (const i of instances) {
let filter = let filter = vnode.attrs.filter != null ? vnode.attrs.filter : true;
vnode.attrs.filter != null ? memoizeParse(vnode.attrs.filter) : true;
filter = expressionParser.map(filter, e => { filter = expressionParser.map(filter, e => {
if (Array.isArray(e) && e[0] === "PARAM") if (Array.isArray(e) && e[0] === "PARAM")
@@ -70,7 +59,7 @@ const component = {
components.get(p.type || "parameter"), components.get(p.type || "parameter"),
Object.assign({}, p, { Object.assign({}, p, {
device: device, device: device,
parameter: expressionParser.stringify(param), parameter: param,
label: "" label: ""
}) })
) )
+1 -3
View File
@@ -8,12 +8,10 @@ import memoize from "../../lib/common/memoize";
import timeAgo from "../timeago"; import timeAgo from "../timeago";
import longTextComponent from "../long-text-component"; import longTextComponent from "../long-text-component";
const memoizedParse = memoize(expression.parse);
const evaluateParam = memoize((exp, obj, now) => { const evaluateParam = memoize((exp, obj, now) => {
let timestamp = now; let timestamp = now;
const params = new Set(); const params = new Set();
const value = expression.evaluate(memoizedParse(exp), obj, now, e => { const value = expression.evaluate(exp, obj, now, e => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
if (e[0] === "PARAM") { if (e[0] === "PARAM") {
params.add(e[1]); params.add(e[1]);
+1 -6
View File
@@ -4,10 +4,6 @@ import m from "mithril";
import * as taskQueue from "../task-queue"; import * as taskQueue from "../task-queue";
import * as store from "../store"; import * as store from "../store";
import * as notifications from "../notifications"; import * as notifications from "../notifications";
import * as expression from "../../lib/common/expression";
import memoize from "../../lib/common/memoize";
const memoizedParse = memoize(expression.parse);
const component = { const component = {
view: vnode => { view: vnode => {
@@ -20,8 +16,7 @@ const component = {
onclick: e => { onclick: e => {
e.target.disabled = true; e.target.disabled = true;
const params = Object.values(vnode.attrs.parameters) const params = Object.values(vnode.attrs.parameters)
.map(p => { .map(exp => {
const exp = memoizedParse(p);
if (!Array.isArray(exp) || !exp[0] === "PARAM") return null; if (!Array.isArray(exp) || !exp[0] === "PARAM") return null;
return store.evaluateExpression(exp[1], device); return store.evaluateExpression(exp[1], device);
}) })
+4 -3
View File
@@ -17,8 +17,8 @@ const PAGE_SIZE = config.ui.pageSize || 10;
const memoizedParse = memoize(expression.parse); const memoizedParse = memoize(expression.parse);
const memoizedJsonParse = memoize(JSON.parse); const memoizedJsonParse = memoize(JSON.parse);
const memoizedGetSortable = memoize(str => { const memoizedGetSortable = memoize(p => {
const expressionParams = expression.extractParams(expression.parse(str)); const expressionParams = expression.extractParams(p);
if (expressionParams.length === 1) { if (expressionParams.length === 1) {
const param = expression.evaluate(expressionParams[0]); const param = expression.evaluate(expressionParams[0]);
if (typeof param === "string") return param; if (typeof param === "string") return param;
@@ -28,7 +28,8 @@ const memoizedGetSortable = memoize(str => {
const getDownloadUrl = memoize((filter, indexParameters) => { const getDownloadUrl = memoize((filter, indexParameters) => {
const columns = {}; const columns = {};
for (const p of indexParameters) columns[p.label] = p.parameter; for (const p of indexParameters)
columns[p.label] = expression.stringify(p.parameter);
return `/api/devices.csv?${m.buildQueryString({ return `/api/devices.csv?${m.buildQueryString({
filter: filter, filter: filter,
columns: JSON.stringify(columns) columns: JSON.stringify(columns)
+1 -5
View File
@@ -4,10 +4,6 @@ import m from "mithril";
import config from "./config"; import config from "./config";
import * as store from "./store"; import * as store from "./store";
import pieChartComponent from "./pie-chart-component"; import pieChartComponent from "./pie-chart-component";
import * as expression from "../lib/common/expression";
import memoize from "../lib/common/memoize";
const memoizedParse = memoize(expression.parse);
const GROUPS = config.ui.overview.groups; const GROUPS = config.ui.overview.groups;
const CHARTS = {}; const CHARTS = {};
@@ -22,7 +18,7 @@ function queryCharts(charts) {
charts[chartName] = chart = Object.assign({}, chart); charts[chartName] = chart = Object.assign({}, chart);
chart.slices = Object.assign({}, chart.slices); chart.slices = Object.assign({}, chart.slices);
for (let [sliceName, slice] of Object.entries(chart.slices)) { for (let [sliceName, slice] of Object.entries(chart.slices)) {
const filter = memoizedParse(slice.filter); const filter = slice.filter;
chart.slices[sliceName] = slice = Object.assign({}, slice); chart.slices[sliceName] = slice = Object.assign({}, slice);
slice.count = store.count("devices", filter); slice.count = store.count("devices", filter);
} }
+6 -2
View File
@@ -1,6 +1,10 @@
"use strict"; "use strict";
import m from "mithril"; import m from "mithril";
import * as expression from "../lib/common/expression";
import memoize from "../lib/common/memoize";
const memoizedStringify = memoize(expression.stringify);
const drawChart = function(chartData) { const drawChart = function(chartData) {
const slices = chartData.slices; const slices = chartData.slices;
@@ -28,7 +32,7 @@ const drawChart = function(chartData) {
"a", "a",
{ {
href: `/#!/devices/?${m.buildQueryString({ href: `/#!/devices/?${m.buildQueryString({
filter: slice.filter filter: memoizedStringify(slice.filter)
})}` })}`
}, },
slice.count.value || 0 slice.count.value || 0
@@ -68,7 +72,7 @@ const drawChart = function(chartData) {
"a", "a",
{ {
"xlink:href": `/#!/devices/?${m.buildQueryString({ "xlink:href": `/#!/devices/?${m.buildQueryString({
filter: slice.filter filter: memoizedStringify(slice.filter)
})}` })}`
}, },
[ [
+1 -2
View File
@@ -1,7 +1,6 @@
"use strict"; "use strict";
import config from "./config"; import config from "./config";
import * as expression from "../lib/common/expression";
const resources = { const resources = {
devices: {}, devices: {},
@@ -37,7 +36,7 @@ const resources = {
for (const v of Object.values(config.ui.filters)) { for (const v of Object.values(config.ui.filters)) {
resources.devices[v.label] = { resources.devices[v.label] = {
parameter: expression.parse(v.parameter), parameter: v.parameter,
type: (v.type || "").split(",").map(s => s.trim()) type: (v.type || "").split(",").map(s => s.trim())
}; };
} }