Add config page
This commit is contained in:
1 parent
1e41c72d60
commit
498947bc4a
11 files changed
+372
-7
No files matched your search
@@ -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