Add overview page

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2018-12-26 00:22:07 -08:00
1 parent a7e07cbde9
commit 93c6a4dfcd
6 files changed
+304 -10

No files matched your search

+1 -1
View File
@@ -23,7 +23,7 @@ function pagify(pageName, page) {
page page
.init(args) .init(args)
.then(st => { .then(st => {
if (!st) return m.reoute.set("/"); if (!st) return m.route.set("/");
state = st; state = st;
resolve(); resolve();
}) })
+29 -1
View File
@@ -37,5 +37,33 @@ export default {
parameter: parameter:
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress" "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress"
} }
] ],
overview: {
online: {
label: "Online devices",
charts: {
all: {
label: "All devices",
slices: {
onlineNow: {
label: "Online now",
color: "#31a354",
filter: "Events.Inform > NOW() - 5 * 60 * 1000"
},
past24: {
label: "Past 24 hours",
color: "#addd8e",
filter:
"Events.Inform > (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000) AND Events.Inform < (NOW() - 5 * 60 * 1000)"
},
others: {
label: "Others",
color: "#f7fcb9",
filter: "Events.Inform < (NOW() - 5 * 60 * 1000) - (24 * 60 * 60 * 1000)"
}
}
}
}
}
}
}; };
+62
View File
@@ -149,12 +149,14 @@ h1 {
h2 { h2 {
color: var(--color5); color: var(--color5);
font-weight: bold; font-weight: bold;
font-size: 120%;
margin-top: 1em; margin-top: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
h3 { h3 {
color: var(--color5); color: var(--color5);
font-size: 110%;
margin-top: 1em; margin-top: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@@ -226,3 +228,63 @@ button {
} }
} }
} }
.page-overview {
& > h1 {
text-align: center;
}
}
.overview-chart-group {
display: flex;
justify-content: center;
& h2 {
text-align: center;
}
}
.pie-chart {
margin: 20px;
text-align: center;
& > svg {
& > path {
stroke: var(--color1);
stroke-width: 1px;
}
& > a {
transition: opacity var(--fade);
opacity: 0;
& > text {
font-weight: bold;
opacity: 0.4;
}
& > path {
stroke: var(--color4);
}
&:hover {
opacity: 1;
}
}
}
& > .legend {
text-align: left;
margin: 15px;
& > .legend-line {
& > .color {
height: 12px;
width: 12px;
margin-right: 5px;
border: 1px solid var(--color1);
display: inline-block;
}
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ const layout = {
m("img.logo", { src: "logo.svg" }), m("img.logo", { src: "logo.svg" }),
m(menu, { page: vnode.attrs.page }) m(menu, { page: vnode.attrs.page })
]), ]),
m("#content", [vnode.children]) m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
]; ];
} }
}; };
+91 -7
View File
@@ -1,21 +1,105 @@
"use strict"; "use strict";
import m from "mithril"; import m from "mithril";
import config from "./config";
import pieChartComponent from "./pie-chart-component";
import * as filterParser from "../common/filter-parser";
function count(filter, limit) {
return new Promise((resolve, reject) => {
function extract(xhr) {
return +xhr.getResponseHeader("x-total-count");
}
m
.request({
method: "HEAD",
url:
"/api/devices/?" +
m.buildQueryString({ filter: filter, limit: limit }),
extract: extract
})
.then(res => {
resolve(res);
})
.catch(err => {
err.message = err.message || "Unknown error";
reject(err);
});
});
}
function queryCharts(groups) {
const now = Date.now();
function evalNow(exp) {
if (exp[0] == "FUNC" && exp[1] === "NOW" && exp.length == 2) return now;
}
let calls = [];
groups = Object.assign({}, groups);
for (let [groupName, group] of Object.entries(groups)) {
groups[groupName] = group = Object.assign({}, group);
for (let [chartName, chart] of Object.entries(group.charts)) {
group.charts[chartName] = chart = Object.assign({}, chart);
for (let [sliceName, slice] of Object.entries(chart.slices)) {
chart.slices[sliceName] = slice = Object.assign({}, slice);
calls.push(
new Promise((resolve, reject) => {
let f = filterParser.map(filterParser.parse(slice.filter), evalNow);
count(filterParser.stringify(f))
.then(c => {
slice.count = c;
resolve();
})
.catch(reject);
})
);
}
}
}
return new Promise((resolve, reject) => {
Promise.all(calls)
.then(() => {
resolve(groups);
})
.catch(reject);
});
}
const init = function() { const init = function() {
return new Promise(resolve => { return new Promise((resolve, reject) => {
resolve({ message: "Hello world!" }); queryCharts(config.overview)
.then(charts => {
resolve({ charts: charts });
})
.catch(err => {
err.message = err.message || "Unknown error";
reject(err);
});
}); });
}; };
const component = { const component = {
view: vnode => { view: vnode => {
document.title = "Overview - GenieACS"; document.title = "Overview - GenieACS";
return m( let children = [];
"a", for (let group of Object.values(vnode.attrs.charts)) {
{ href: "#!/devices/202BC1-BM632w-000000" }, if (group.label) children.push(m("h1", group.label));
vnode.attrs.message
); let groupChildren = [];
for (let chart of Object.values(group.charts)) {
let chartChildren = [];
if (chart.label) chartChildren.push(m("h2", chart.label));
chartChildren.push(m(pieChartComponent, { chart: chart }));
groupChildren.push(m(".overview-chart", chartChildren));
}
children.push(m(".overview-chart-group", groupChildren));
}
return children;
} }
}; };
+120
View File
@@ -0,0 +1,120 @@
"use strict";
import m from "mithril";
const drawChart = function(chartData) {
let slices = chartData.slices;
let total = Array.from(Object.values(chartData.slices)).reduce(
(a, s) => a + s.count,
0
);
let legend = [];
let paths = [];
let links = [];
let currentProgressPercentage = 0;
let startX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100;
let startY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100;
let endX, endY;
for (let slice of Object.values(slices)) {
let percent = total > 0 ? slice.count / total : 0;
legend.push(
m(".legend-line", [
m("span.color", {
style: `background-color: ${slice.color} !important;`
}),
`${slice.label}: `,
m(
"a",
{
href: `/#!/devices/?${m.buildQueryString({
filter: slice.filter
})}`
},
slice.count
),
` (${(percent * 100).toFixed(2)}%)`
])
);
if (percent > 0) {
currentProgressPercentage += percent;
endX = Math.cos(2 * Math.PI * currentProgressPercentage) * 100;
endY = Math.sin(2 * Math.PI * currentProgressPercentage) * 100;
let isBigArc = percent > 0.5 ? 1 : 0;
let sketch =
`M ${startX} ${startY} ` + // Move to the starting point
`A 100 100 0 ${isBigArc} 1 ${endX} ${endY} ` + // Draw an Arc from starting point to ending point
`L 0 0 z`; // complete the shape by drawing a line to the center of circle
startX = endX;
startY = endY;
paths.push(
m("path", {
d: sketch,
fill: slice.color
})
);
let percentageX =
Math.cos(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50;
let percentageY =
Math.sin(2 * Math.PI * (currentProgressPercentage - percent / 2)) * 50;
links.push(
m(
"a",
{
href: `/#!/devices/?${m.buildQueryString({
filter: slice.filter
})}`
},
[
m("path", {
d: sketch,
"fill-opacity": 0
}),
m(
"text",
{
x: percentageX,
y: percentageY,
"dominant-baseline": "middle",
"text-anchor": "middle"
},
`${(percent * 100).toFixed(2)}%`
)
]
)
);
}
}
legend.push(m("span.legend-total", `Total: ${total}`));
return m("div", { class: "pie-chart" }, [
m(
"svg",
{
// Adding 2 as padding; strokes must not be more than 2
viewBox: "-102 -102 204 204",
width: "204px",
height: "204px",
xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink"
},
paths.concat(links)
),
m(".legend", legend)
]);
};
const component = {
view: vnode => {
return drawChart(vnode.attrs.chart);
}
};
export default component;