Add overview page
This commit is contained in:
1 parent
a7e07cbde9
commit
93c6a4dfcd
6 files changed
+304
-10
No files matched your search
+1
-1
@@ -23,7 +23,7 @@ function pagify(pageName, page) {
|
||||
page
|
||||
.init(args)
|
||||
.then(st => {
|
||||
if (!st) return m.reoute.set("/");
|
||||
if (!st) return m.route.set("/");
|
||||
state = st;
|
||||
resolve();
|
||||
})
|
||||
|
||||
+29
-1
@@ -37,5 +37,33 @@ export default {
|
||||
parameter:
|
||||
"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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -149,12 +149,14 @@ h1 {
|
||||
h2 {
|
||||
color: var(--color5);
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--color5);
|
||||
font-size: 110%;
|
||||
margin-top: 1em;
|
||||
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
@@ -11,7 +11,7 @@ const layout = {
|
||||
m("img.logo", { src: "logo.svg" }),
|
||||
m(menu, { page: vnode.attrs.page })
|
||||
]),
|
||||
m("#content", [vnode.children])
|
||||
m("#content", { class: `page-${vnode.attrs.page}` }, [vnode.children])
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
+91
-7
@@ -1,21 +1,105 @@
|
||||
"use strict";
|
||||
|
||||
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() {
|
||||
return new Promise(resolve => {
|
||||
resolve({ message: "Hello world!" });
|
||||
return new Promise((resolve, reject) => {
|
||||
queryCharts(config.overview)
|
||||
.then(charts => {
|
||||
resolve({ charts: charts });
|
||||
})
|
||||
.catch(err => {
|
||||
err.message = err.message || "Unknown error";
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
document.title = "Overview - GenieACS";
|
||||
return m(
|
||||
"a",
|
||||
{ href: "#!/devices/202BC1-BM632w-000000" },
|
||||
vnode.attrs.message
|
||||
);
|
||||
let children = [];
|
||||
for (let group of Object.values(vnode.attrs.charts)) {
|
||||
if (group.label) children.push(m("h1", group.label));
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user