Add config page

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-02-04 23:47:54 -08:00
1 parent 1e41c72d60
commit 498947bc4a
11 files changed
+372 -7

No files matched your search

+25
View File
@@ -5,6 +5,7 @@ import https from "https";
import url from "url"; import url from "url";
import * as config from "../config"; import * as config from "../config";
import * as db from "./db"; import * as db from "./db";
import * as cache from "../cache";
import * as mongodbFunctions from "./mongodb-functions"; import * as mongodbFunctions from "./mongodb-functions";
export function filterToMongoQuery(filter, negate = false, res = {}) { export function filterToMongoQuery(filter, negate = false, res = {}) {
@@ -67,6 +68,18 @@ export function filterToMongoQuery(filter, negate = false, res = {}) {
export function deleteResource(resource, id) { export function deleteResource(resource, id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (resource === "config") {
db.deleteConfig(id)
.then(() => {
cache.del("presets_hash", err => {
if (err) return void reject(err);
resolve();
});
})
.catch(reject);
return;
}
const resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
const options = url.parse( const options = url.parse(
@@ -296,6 +309,18 @@ export function ping(host) {
export function putResource(resource, id, data) { export function putResource(resource, id, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (resource === "config") {
db.putConfig(id, data)
.then(() => {
cache.del("presets_hash", err => {
if (err) return void reject(err);
resolve();
});
})
.catch(reject);
return;
}
const resourceUrl = const resourceUrl =
resource === "virtualParameters" ? "virtual_parameters" : resource; resource === "virtualParameters" ? "virtual_parameters" : resource;
const options = url.parse( const options = url.parse(
+2
View File
@@ -25,6 +25,7 @@ const RESOURCE_IDS = {
provisions: "_id", provisions: "_id",
files: "_id", files: "_id",
virtualParameters: "_id", virtualParameters: "_id",
config: "_id",
faults: "_id", faults: "_id",
tasks: "_id" tasks: "_id"
}; };
@@ -35,6 +36,7 @@ const resources = {
provisions: 0 | RESOURCE_DELETE | RESOURCE_PUT, provisions: 0 | RESOURCE_DELETE | RESOURCE_PUT,
files: 0 | RESOURCE_DELETE, files: 0 | RESOURCE_DELETE,
virtualParameters: 0 | RESOURCE_DELETE | RESOURCE_PUT, virtualParameters: 0 | RESOURCE_DELETE | RESOURCE_PUT,
config: 0 | RESOURCE_DELETE | RESOURCE_PUT,
faults: 0 | RESOURCE_DELETE, faults: 0 | RESOURCE_DELETE,
tasks: 0 tasks: 0
}; };
+32
View File
@@ -187,6 +187,38 @@ export function count(resource, filter) {
}); });
} }
export function deleteConfig(id) {
return new Promise((resolve, reject) => {
getClient()
.then(client => {
const collection = client
.db()
.collection(RESOURCE_COLLECTION["config"] || "config");
collection.deleteOne({ _id: id }, err => {
if (err) return void reject(err);
resolve();
});
})
.catch(reject);
});
}
export function putConfig(id, object) {
return new Promise((resolve, reject) => {
getClient()
.then(client => {
const collection = client
.db()
.collection(RESOURCE_COLLECTION["config"] || "config");
collection.replaceOne({ _id: id }, object, { upsert: true }, err => {
if (err) return void reject(err);
resolve();
});
})
.catch(reject);
});
}
export function disconnect() { export function disconnect() {
if (_clientPromise) { if (_clientPromise) {
_clientPromise.then(client => { _clientPromise.then(client => {
+3
View File
@@ -19,12 +19,15 @@ const configData = {
"permissions.admin.0.presets.validate.pass": "true", "permissions.admin.0.presets.validate.pass": "true",
"permissions.admin.0.provisions.access": "3", "permissions.admin.0.provisions.access": "3",
"permissions.admin.0.provisions.validate.pass": "true", "permissions.admin.0.provisions.validate.pass": "true",
"permissions.admin.0.config.access": "3",
"permissions.admin.0.config.validate.pass": "true",
"permissions.admin.0.virtualParameters.access": "3", "permissions.admin.0.virtualParameters.access": "3",
"permissions.admin.0.virtualParameters.validate.pass": "true", "permissions.admin.0.virtualParameters.validate.pass": "true",
"permissions.view.0.devices.access": "2", "permissions.view.0.devices.access": "2",
"permissions.view.0.faults.access": "2", "permissions.view.0.faults.access": "2",
"permissions.view.0.files.access": "2", "permissions.view.0.files.access": "2",
"permissions.view.0.presets.access": "2", "permissions.view.0.presets.access": "2",
"permissions.view.0.config.access": "2",
"permissions.view.0.provisions.access": "2", "permissions.view.0.provisions.access": "2",
"permissions.view.0.virtualParameters.access": "2", "permissions.view.0.virtualParameters.access": "2",
"ui.device.0.type": "'tags'", "ui.device.0.type": "'tags'",
+10
View File
@@ -47,6 +47,16 @@ const menu = {
); );
} }
if (window.authorizer.hasAccess("config", 1)) {
tabs.push(
m(
"li",
{ class: active["config"] },
m("a", { href: "#!/admin/config" }, "Config")
)
);
}
return m("nav#side-menu", m("ul", tabs)); return m("nav#side-menu", m("ul", tabs));
} }
}; };
+10 -2
View File
@@ -14,12 +14,19 @@ import * as presetsPage from "./presets-page";
import * as provisionsPage from "./provisions-page"; import * as provisionsPage from "./provisions-page";
import * as virtualParametersPage from "./virtual-parameters-page"; import * as virtualParametersPage from "./virtual-parameters-page";
import * as filesPage from "./files-page"; import * as filesPage from "./files-page";
import * as configPage from "./config-page";
import Authorizer from "../lib//common/authorizer"; import Authorizer from "../lib//common/authorizer";
import * as notifications from "./notifications"; import * as notifications from "./notifications";
window.authorizer = new Authorizer(window.permissionSets); window.authorizer = new Authorizer(window.permissionSets);
const adminPages = ["presets", "provisions", "virtualParameters", "files"]; const adminPages = [
"presets",
"provisions",
"virtualParameters",
"files",
"config"
];
let state; let state;
@@ -107,5 +114,6 @@ m.route(document.body, "/overview", {
"virtualParameters", "virtualParameters",
virtualParametersPage virtualParametersPage
), ),
"/admin/files": pagify("files", filesPage) "/admin/files": pagify("files", filesPage),
"/admin/config": pagify("config", configPage)
}); });
+275
View File
@@ -0,0 +1,275 @@
"use strict";
import m from "mithril";
import * as store from "./store";
import * as notifications from "./notifications";
import putFormComponent from "./put-form-component";
import * as overlay from "./overlay";
import longTextComponent from "./long-text-component";
import * as expressionParser from "../lib/common/expression-parser";
import { loadCodeMirror } from "./dynamic-loader";
const attributes = [
{ id: "_id", label: "Key" },
{ id: "value", label: "Value" }
];
function putActionHandler(action, _object, isNew) {
return new Promise((resolve, reject) => {
const object = Object.assign({}, _object);
if (action === "save") {
const id = object["_id"];
delete object["_id"];
if (!id) return void resolve({ _id: "ID can not be empty" });
try {
expressionParser.parse(object.value);
} catch (err) {
return void resolve({
value: "Config value must be valid expression"
});
}
store
.resourceExists("config", id)
.then(exists => {
if (exists && isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Config already exists" });
}
if (!exists && !isNew) {
store.fulfill(0, Date.now());
return void resolve({ _id: "Config does not exist" });
}
store
.putResource("config", id, object)
.then(() => {
notifications.push(
"success",
`Config ${exists ? "updated" : "created"}`
);
store.fulfill(0, Date.now());
resolve();
})
.catch(reject);
})
.catch(reject);
} else if (action === "delete") {
store
.deleteResource("config", object["_id"])
.then(() => {
notifications.push("success", "Config deleted");
store.fulfill(0, Date.now());
resolve();
})
.catch(err => {
store.fulfill(0, Date.now());
reject(err);
});
} else {
reject(new Error("Undefined action"));
}
});
}
const formData = {
resource: "config",
attributes: attributes
};
function escapeRegExp(str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}
function init() {
if (!window.authorizer.hasAccess("config", 2)) {
return Promise.reject(
new Error("You are not authorized to view this page")
);
}
return new Promise((resolve, reject) => {
loadCodeMirror()
.then(() => {
resolve({});
})
.catch(reject);
});
}
function renderTable(confsResponse, searchString) {
const confs = confsResponse.value.sort((a, b) => {
return a._id < b._id ? -1 : 1;
});
let regex;
if (searchString) {
const keywords = searchString.split(" ").filter(s => s);
if (keywords.length)
regex = new RegExp(keywords.map(s => escapeRegExp(s)).join(".*"), "i");
}
const rows = [];
for (const conf of confs) {
const attrs = {};
if (regex && !regex.test(conf._id) && !regex.test(conf.value))
attrs.style = "display: none;";
const edit = m(
"button",
{
title: "Edit config value",
onclick: () => {
const cb = () => {
return m(
putFormComponent,
Object.assign(
{
base: conf,
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, false)
.then(errors => {
errors = errors ? Object.values(errors) : [];
if (errors.length) {
for (const err of errors)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
}
},
formData
)
);
};
overlay.open(cb);
}
},
"✎"
);
const del = m(
"button",
{
title: "Delete config",
onclick: () => {
if (!confirm(`Deleting ${conf._id} config. Are you sure?`)) return;
putActionHandler("delete", conf).catch(err => {
throw err;
});
}
},
"✕"
);
rows.push(
m(
"tr",
attrs,
m("td.left", m(longTextComponent, { text: conf._id })),
m(
"td.right",
m("span", [
m(longTextComponent, { text: `${conf.value}` }),
edit,
del
])
)
)
);
}
if (!rows.length)
rows.push(m("tr.empty", m("td", { colspan: 2 }, "No config")));
return m("table", m("tbody", rows));
}
const component = {
view: vnode => {
document.title = "Config - GenieACS";
const search = m("input", {
type: "text",
placeholder: "Search config",
oninput: e => {
vnode.state.searchString = e.target.value;
e.redraw = false;
clearTimeout(vnode.state.timeout);
vnode.state.timeout = setTimeout(m.redraw, 250);
}
});
let newConfig;
if (window.authorizer.hasAccess("config", 3)) {
newConfig = m(
"button.primary",
{
title: "Create new config",
onclick: () => {
const cb = () => {
return m(
putFormComponent,
Object.assign(
{
actionHandler: (action, object) => {
return new Promise(resolve => {
putActionHandler(action, object, true)
.then(errors => {
errors = errors ? Object.values(errors) : [];
if (errors.length) {
for (const err of errors)
notifications.push("error", err);
} else {
overlay.close(cb);
}
resolve();
})
.catch(err => {
notifications.push("error", err.message);
resolve();
});
});
}
},
formData
)
);
};
overlay.open(cb);
}
},
"New"
);
}
const confs = store.fetch("config", true);
return [
m("h1", "Listing config"),
m(
".all-parameters",
search,
m(
".parameter-list",
{ style: "height: 400px" },
renderTable(confs, vnode.state.searchString)
),
m(".actions-bar", newConfig)
)
];
}
};
export { init, component };
+1 -1
View File
@@ -263,6 +263,7 @@ i {
#side-menu { #side-menu {
width: 200px; width: 200px;
min-width: 200px;
& > ul { & > ul {
padding: 0; padding: 0;
list-style: none; list-style: none;
@@ -291,7 +292,6 @@ i {
#content { #content {
flex-grow: 1; flex-grow: 1;
overflow: auto;
margin: 10px; margin: 10px;
min-height: 250px; min-height: 250px;
} }
+7 -1
View File
@@ -8,7 +8,13 @@ import userMenu from "./user-menu";
import adminMenu from "./admin-menu"; import adminMenu from "./admin-menu";
import * as overlay from "./overlay"; import * as overlay from "./overlay";
const adminPages = ["presets", "provisions", "virtualParameters", "files"]; const adminPages = [
"presets",
"provisions",
"virtualParameters",
"files",
"config"
];
const layout = { const layout = {
view: vnode => { view: vnode => {
+5 -2
View File
@@ -134,7 +134,7 @@ const component = {
"button.primary", "button.primary",
{ {
type: "button", type: "button",
title: `Delete ${singular[resource]}`, title: `Delete ${singular[resource] || resource}`,
onclick: e => { onclick: e => {
e.redraw = false; e.redraw = false;
e.target.disabled = true; e.target.disabled = true;
@@ -151,7 +151,10 @@ const component = {
form.push(m(".actions-bar", buttons)); form.push(m(".actions-bar", buttons));
const children = [ const children = [
m("h1", `${current.isNew ? "New" : "Editing"} ${singular[resource]}`), m(
"h1",
`${current.isNew ? "New" : "Editing"} ${singular[resource] || resource}`
),
m( m(
"form", "form",
{ {
+2 -1
View File
@@ -29,7 +29,8 @@ for (const r of [
"presets", "presets",
"provisions", "provisions",
"virtualParameters", "virtualParameters",
"files" "files",
"config"
]) { ]) {
resources[r] = { resources[r] = {
objects: new Map(), objects: new Map(),