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

+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));
}
};
+10 -2
View File
@@ -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)
});
+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 {
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
View File
@@ -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 => {
+5 -2
View File
@@ -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
View File
@@ -29,7 +29,8 @@ for (const r of [
"presets",
"provisions",
"virtualParameters",
"files"
"files",
"config"
]) {
resources[r] = {
objects: new Map(),