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 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(
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 => {
|
||||||
|
|||||||
@@ -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'",
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)
|
||||||
});
|
});
|
||||||
@@ -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 {
|
#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
@@ -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 => {
|
||||||
|
|||||||
@@ -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
@@ -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(),
|
||||||
|
|||||||
Reference in new issue
Block a user