Add config page
This commit is contained in:
1 parent
1e41c72d60
commit
498947bc4a
11 files changed
+372
-7
No files matched your search
@@ -5,6 +5,7 @@ import https from "https";
|
||||
import url from "url";
|
||||
import * as config from "../config";
|
||||
import * as db from "./db";
|
||||
import * as cache from "../cache";
|
||||
import * as mongodbFunctions from "./mongodb-functions";
|
||||
|
||||
export function filterToMongoQuery(filter, negate = false, res = {}) {
|
||||
@@ -67,6 +68,18 @@ export function filterToMongoQuery(filter, negate = false, res = {}) {
|
||||
|
||||
export function deleteResource(resource, id) {
|
||||
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 =
|
||||
resource === "virtualParameters" ? "virtual_parameters" : resource;
|
||||
const options = url.parse(
|
||||
@@ -296,6 +309,18 @@ export function ping(host) {
|
||||
|
||||
export function putResource(resource, id, data) {
|
||||
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 =
|
||||
resource === "virtualParameters" ? "virtual_parameters" : resource;
|
||||
const options = url.parse(
|
||||
|
||||
@@ -25,6 +25,7 @@ const RESOURCE_IDS = {
|
||||
provisions: "_id",
|
||||
files: "_id",
|
||||
virtualParameters: "_id",
|
||||
config: "_id",
|
||||
faults: "_id",
|
||||
tasks: "_id"
|
||||
};
|
||||
@@ -35,6 +36,7 @@ const resources = {
|
||||
provisions: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
||||
files: 0 | RESOURCE_DELETE,
|
||||
virtualParameters: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
||||
config: 0 | RESOURCE_DELETE | RESOURCE_PUT,
|
||||
faults: 0 | RESOURCE_DELETE,
|
||||
tasks: 0
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
if (_clientPromise) {
|
||||
_clientPromise.then(client => {
|
||||
|
||||
@@ -19,12 +19,15 @@ const configData = {
|
||||
"permissions.admin.0.presets.validate.pass": "true",
|
||||
"permissions.admin.0.provisions.access": "3",
|
||||
"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.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.config.access": "2",
|
||||
"permissions.view.0.provisions.access": "2",
|
||||
"permissions.view.0.virtualParameters.access": "2",
|
||||
"ui.device.0.type": "'tags'",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,12 +14,19 @@ import * as presetsPage from "./presets-page";
|
||||
import * as provisionsPage from "./provisions-page";
|
||||
import * as virtualParametersPage from "./virtual-parameters-page";
|
||||
import * as filesPage from "./files-page";
|
||||
import * as configPage from "./config-page";
|
||||
import Authorizer from "../lib//common/authorizer";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
window.authorizer = new Authorizer(window.permissionSets);
|
||||
|
||||
const adminPages = ["presets", "provisions", "virtualParameters", "files"];
|
||||
const adminPages = [
|
||||
"presets",
|
||||
"provisions",
|
||||
"virtualParameters",
|
||||
"files",
|
||||
"config"
|
||||
];
|
||||
|
||||
let state;
|
||||
|
||||
@@ -107,5 +114,6 @@ m.route(document.body, "/overview", {
|
||||
"virtualParameters",
|
||||
virtualParametersPage
|
||||
),
|
||||
"/admin/files": pagify("files", filesPage)
|
||||
"/admin/files": pagify("files", filesPage),
|
||||
"/admin/config": pagify("config", configPage)
|
||||
});
|
||||
@@ -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
@@ -263,6 +263,7 @@ i {
|
||||
|
||||
#side-menu {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
& > ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
@@ -291,7 +292,6 @@ i {
|
||||
|
||||
#content {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
margin: 10px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
+7
-1
@@ -8,7 +8,13 @@ import userMenu from "./user-menu";
|
||||
import adminMenu from "./admin-menu";
|
||||
import * as overlay from "./overlay";
|
||||
|
||||
const adminPages = ["presets", "provisions", "virtualParameters", "files"];
|
||||
const adminPages = [
|
||||
"presets",
|
||||
"provisions",
|
||||
"virtualParameters",
|
||||
"files",
|
||||
"config"
|
||||
];
|
||||
|
||||
const layout = {
|
||||
view: vnode => {
|
||||
|
||||
@@ -134,7 +134,7 @@ const component = {
|
||||
"button.primary",
|
||||
{
|
||||
type: "button",
|
||||
title: `Delete ${singular[resource]}`,
|
||||
title: `Delete ${singular[resource] || resource}`,
|
||||
onclick: e => {
|
||||
e.redraw = false;
|
||||
e.target.disabled = true;
|
||||
@@ -151,7 +151,10 @@ const component = {
|
||||
form.push(m(".actions-bar", buttons));
|
||||
|
||||
const children = [
|
||||
m("h1", `${current.isNew ? "New" : "Editing"} ${singular[resource]}`),
|
||||
m(
|
||||
"h1",
|
||||
`${current.isNew ? "New" : "Editing"} ${singular[resource] || resource}`
|
||||
),
|
||||
m(
|
||||
"form",
|
||||
{
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ for (const r of [
|
||||
"presets",
|
||||
"provisions",
|
||||
"virtualParameters",
|
||||
"files"
|
||||
"files",
|
||||
"config"
|
||||
]) {
|
||||
resources[r] = {
|
||||
objects: new Map(),
|
||||
|
||||
Reference in new issue
Block a user