Implement layout and device list page

This commit is contained in:
Zaid Abdulla committed 2018-12-26 00:20:45 -08:00
1 parent f33717acb0
commit 1c9061ab5a
19 files changed
+1151 -30

No files matched your search

+1 -3
View File
@@ -6,12 +6,10 @@
"parserOptions": {
"sourceType": "script"
},
"plugins": ["prettier"],
"rules": {
"no-shadow": ["error", { "allow": ["err"] }],
"handle-callback-err": "error",
"prefer-arrow-callback": "error",
"curly": ["error", "multi", "consistent"],
"prettier/prettier": "error"
"curly": ["error", "multi", "consistent"]
}
}
+5 -2
View File
@@ -1,6 +1,9 @@
{
"env": {
"browser": true,
"commonjs": true
"browser": true
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 8
}
}
+40 -2
View File
@@ -1,5 +1,43 @@
"use strict";
const m = require("mithril");
import m from "mithril";
m.render(document.body, "hello world");
import layout from "./layout";
import * as overviewPage from "./overview-page";
import * as devicesPage from "./devices-page";
import * as errorPage from "./error-page";
let state;
function pagify(pageName, page) {
const component = {
render: () => {
if (state && state.error) return m(errorPage.component, state);
else return m(layout, { page: pageName }, m(page.component, state));
}
};
if (page.init)
component.onmatch = args => {
return new Promise(resolve => {
page
.init(args)
.then(st => {
if (!st) return m.reoute.set("/");
state = st;
resolve();
})
.catch(err => {
state = { error: err.message };
resolve();
});
});
};
return component;
}
m.route(document.body, "/overview", {
"/overview": pagify("overview", overviewPage),
"/devices": pagify("devices", devicesPage)
});
+24
View File
@@ -0,0 +1,24 @@
"use strict";
export default {
pageSize: 10,
index: [
{
label: "Serial number",
parameter: "DeviceID.SerialNumber"
},
{
label: "Product class",
parameter: "DeviceID.ProductClass"
},
{
label: "Software version",
parameter: "InternetGatewayDevice.DeviceInfo.SoftwareVersion"
},
{
label: "MAC",
parameter:
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress"
}
]
};
+202
View File
@@ -0,0 +1,202 @@
@import "normalize.css";
:root {
--base-font-size: 13px;
--base-line-height: 16px;
--color1: #ebe0bf;
--color2: #b9b098;
--color3: #f2ebd5;
--color4: #046380;
--color5: #002f2f;
--fade: 0.2s;
}
body,
input,
button,
select,
option,
textarea {
font-family: sans-serif;
color: var(--color5);
}
a {
color: var(--color4);
cursor: pointer;
transition: background-color var(--fade), color var(--fade);
&:visited {
color: var(--color4);
}
&:hover {
color: white;
background-color: var(--color4);
}
}
strong,
b {
font-weight: bold;
}
i {
font-style: italic;
}
#header {
background-color: #f2eddf;
border-bottom: 1px solid var(--color1);
overflow: visible;
position: relative;
& > nav {
display: inline-block;
overflow: visible;
position: absolute;
bottom: -1px;
& > ul {
font-weight: bold;
display: inline-block;
margin: 0 10px;
padding: 0;
& > li {
margin: 0 2px;
background-color: var(--color1);
display: inline-block;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
transition: background-color var(--fade);
&.active,
&:hover {
background-color: white;
}
& > a,
& > a:visited,
& > a:hover {
display: inline-block;
min-width: 140px;
color: var(--color4);
text-decoration: none;
background: none;
padding: 8px;
}
}
}
}
& > .logo {
margin: 10px;
display: inline;
height: 56px;
}
}
#content {
margin: 10px;
min-height: 250px;
}
table.table {
border-spacing: 0;
text-align: left;
& th {
color: var(--color4);
font-weight: bold;
border-bottom: 2px solid var(--color4);
margin: 0;
padding: 0.2em 0.5em;
}
& td {
margin: 0;
padding: 0.2em 0.5em;
border-bottom: 1px dotted var(--color1);
& .action a {
visibility: hidden;
}
}
& tbody tr:hover td {
& .action {
visibility: visible;
}
transition: background-color var(--fade);
background-color: var(--color3);
}
& tfoot td > a {
margin-left: 1em;
}
}
h1 {
color: var(--color4);
font-size: 130%;
font-weight: bold;
margin-top: 1em;
margin-bottom: 0.5em;
}
h2 {
color: var(--color5);
font-weight: bold;
margin-top: 1em;
margin-bottom: 0.5em;
}
h3 {
color: var(--color5);
margin-top: 1em;
margin-bottom: 0.5em;
}
/* Remove button padding in FF */
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input[type="button"],
button {
display: inline-block;
min-width: 6em;
position: relative;
top: 0px;
vertical-align: baseline;
margin: 0 0.1em;
outline: none;
cursor: pointer;
text-align: center;
text-decoration: none;
padding: 0.1em 0.6em;
border-top-left-radius: 1em;
border-bottom-right-radius: 1em;
border-top-right-radius: 0.2em;
border-bottom-left-radius: 0.2em;
color: var(--color4);
transition: background-color var(--fade), color var(--fade);
&:link,
&:visited {
color: var(--color4);
background-color: var(--color1);
}
&:hover,
&:active {
color: var(--color1);
background-color: var(--color4);
}
&:disabled {
background-color: var(--color1);
color: var(--color2);
}
}
+450
View File
@@ -0,0 +1,450 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
*/
html {
line-height: 1.15; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/**
* Add the correct display in IE 9-.
*/
article,
aside,
footer,
header,
nav,
section {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in IE.
*/
figcaption,
figure,
main {
/* 1 */
display: block;
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
audio,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: sans-serif; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"],
/* 1 */ [type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* 1. Add the correct display in IE 9-.
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
*/
details,
/* 1 */ menu {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Scripting
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
canvas {
display: inline-block;
}
/**
* Add the correct display in IE.
*/
template {
display: none;
}
/* Hidden
========================================================================== */
/**
* Add the correct display in IE 10-.
*/
[hidden] {
display: none;
}
+115
View File
@@ -0,0 +1,115 @@
"use strict";
import m from "mithril";
import config from "./config";
function load(filter, limit) {
return new Promise((resolve, reject) => {
m
.request({
url: "/api/devices/",
data: { filter: JSON.stringify(filter), limit: limit }
})
.then(res => {
resolve(res);
})
.catch(err => {
err.message = err.message || "Unknown error";
reject(err);
});
});
}
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/",
data: { filter: JSON.stringify(filter), limit: limit },
extract: extract
})
.then(res => {
resolve(res);
})
.catch(err => {
err.message = err.message || "Unknown error";
reject(err);
});
});
}
function init(args) {
return new Promise((resolve, reject) => {
Promise.all([count(args.filter), load(args.filter, config.pageSize)])
.then(res => {
resolve({ filter: args.filter, total: res[0], devices: res[1] });
})
.catch(reject);
});
}
function renderTable(devices, parameters, total, showMoreCallback) {
let labels = [];
for (let param of parameters) labels.push(m("th", param.label));
let rows = [];
for (let device of devices)
rows.push(
m(
"tr",
parameters.map(p => {
if (!device[p.parameter] || !device[p.parameter].value)
return m("td.na");
if (!device[p.parameter].value[0] === "") return m("td.blank");
return m("td", device[p.parameter].value[0]);
})
)
);
let footerElements = [`${devices.length}/${total}`];
if (devices.length < total)
footerElements.push(m("a", { onclick: showMoreCallback }, "Show more"));
let tfoot = m(
"tfoot",
m("tr", m("td", { colspan: labels.length }, footerElements))
);
return m("table.table", [
m("thead", m("tr", labels)),
m("tbody", rows),
tfoot
]);
}
const component = {
view: vnode => {
document.title = "Devices - GenieACS";
function showMore() {
const lastDevice = vnode.attrs.devices[vnode.attrs.devices.length - 1];
let f = Object.assign({}, vnode.attrs.filter, {
"DeviceID.ID>": lastDevice["DeviceID.ID"].value[0]
});
load(f, config.pageSize).then(res => {
vnode.attrs.devices = vnode.attrs.devices.concat(res);
});
}
return [
m("h1", "Listing devices"),
renderTable(
vnode.attrs.devices,
config.index,
vnode.attrs.total,
showMore
)
];
}
};
export { init, component };
+10
View File
@@ -0,0 +1,10 @@
"use strict";
const component = {
view: function(vnode) {
document.title = "Error! - GenieACS";
return vnode.attrs.error;
}
};
export { component };
+19
View File
@@ -0,0 +1,19 @@
"use strict";
import m from "mithril";
import menu from "./menu";
const layout = {
view: vnode => {
return [
m("#header", [
m("img.logo", { src: "logo.svg" }),
m(menu, { page: vnode.attrs.page })
]),
m("#content", [vnode.children])
];
}
};
export default layout;
+27
View File
@@ -0,0 +1,27 @@
"use strict";
import m from "mithril";
const menu = {
view: vnode => {
const active = { [vnode.attrs.page]: "active" };
return m(
"nav",
m("ul", [
m(
"li",
{ class: active["overview"] },
m("a", { href: "#!/overview" }, "Overview")
),
m(
"li",
{ class: active["devices"] },
m("a", { href: "#!/devices" }, "Devices")
)
])
);
}
};
export default menu;
+22
View File
@@ -0,0 +1,22 @@
"use strict";
import m from "mithril";
const init = function() {
return new Promise(resolve => {
resolve({ message: "Hello world!" });
});
};
const component = {
view: vnode => {
document.title = "Overview - GenieACS";
return m(
"a",
{ href: "#!/devices/202BC1-BM632w-000000" },
vnode.attrs.message
);
}
};
export { init, component };
+7 -4
View File
@@ -4,9 +4,9 @@
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint --fix server/*.js client/*.js",
"build": "webpack",
"start": "npm run lint;npm run build;node server/server"
"lint": "eslint --fix server/*.js client/*.js;prettier --write server/*.js client/*.js client/css/*.css",
"build": "npm run lint;webpack;postcss -c postcss.config.js client/css/app.css -o public/app.css",
"start": "node server/server"
},
"author": "Zaid Abdulla <zaid@genieacs.com>",
"dependencies": {
@@ -17,8 +17,11 @@
"mithril": "^1.1.5"
},
"devDependencies": {
"cssnano": "^3.10.0",
"eslint": "^4.12.1",
"eslint-plugin-prettier": "^2.3.1",
"postcss-cli": "^4.1.1",
"postcss-cssnext": "^3.0.2",
"postcss-import": "^11.0.0",
"prettier": "^1.8.2",
"webpack": "^3.9.1"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
plugins: [
require("postcss-import"),
require("postcss-cssnext")({warnForDuplicates: false}),
require("cssnano")
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

+3
View File
@@ -1,5 +1,8 @@
<html>
<head>
<title>GenieACS</title>
<link rel="shortcut icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="app.css">
</head>
<body>
<script src="app.js"></script>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="310" height="64" viewBox="0 0 82.020832 16.933334"><defs><linearGradient id="b"><stop offset="0" stop-color="#737373"/><stop offset="1" stop-color="#4b4b4b"/></linearGradient><linearGradient id="a"><stop offset="0" stop-color="#b72f5f"/><stop offset="1" stop-color="#872346"/></linearGradient><linearGradient xlink:href="#a" id="c" gradientUnits="userSpaceOnUse" gradientTransform="translate(173.233 -461.003) scale(.21343)" x1="16.045" y1="132.803" x2="16.045" y2="-81.164"/><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" x1="46.459" y1="281.163" x2="46.459" y2="296.66" gradientTransform="translate(1.09 16.9) scale(.94143)"/><linearGradient xlink:href="#b" id="e" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.105 -994.648) scale(3.55816)" x1="46.459" y1="281.163" x2="46.459" y2="296.66"/></defs><path d="M173.233-461.003l1.953-3.714c-.868-1.509-2.042-2.814-3.522-3.917-2.419-1.43-4.386-1.199-5.901.694 2.931 1.323 5.421 3.635 7.47 6.937m-7.577 11.151a6.095 6.095 0 0 0 .673-3.49 23.76 23.76 0 0 0-1.473-6.285 20.998 20.998 0 0 1-1.206-4.855 6.766 6.766 0 0 1 .48-3.426c.463-1.117 1.298-2.074 2.508-2.87-1.302-.392-2.44-.499-3.415-.32-1.707.326-2.82 1.354-3.34 3.083a10.152 10.152 0 0 0-.267 5.08 33.38 33.38 0 0 0 1.238 4.674 32.993 32.993 0 0 1 1.227 4.856c.107.626.118 1.277.032 1.953a4.72 4.72 0 0 1-.779 2.006 4.498 4.498 0 0 1-1.707 1.44c.697.129 1.387.235 2.07.32a4.124 4.124 0 0 0 2.07-.351c.84-.37 1.47-.975 1.89-1.815m22.463 5.123l.63-.62-5.25-4.77 1.579-1.579 4.77 5.25.619-.629-5.24-4.77 1.483-1.483 4.77 5.24.63-.62-5.25-4.77 1.58-1.58 5.015 5.016 1.067-1.045-4.503-4.504h.01l-5.87-5.859-3.563-1.462-5.102 3.831-.02.022-.022-.022.01-.02 3.394-4.526-1.131-.458-4.973 4.973 1.26 3.073 4.652-3.49.01-.01.033.02-.01.012-3.95 5.26.64 1.57 4.814 4.802h-.01l5.559 5.549 1.057-1.046-5.027-5.026 1.569-1.569 4.77 5.24m4.14-28.173c4.405 4.403 6.607 9.722 6.607 15.954 0 6.218-2.202 11.536-6.606 15.954-4.418 4.404-9.736 6.606-15.954 6.606-6.233 0-11.55-2.202-15.955-6.606-2.703-2.71-4.574-5.756-5.613-9.135l.448.245c2.241 1.181 4.226.577 5.955-1.814-1.565-.32-2.82-.658-3.767-1.014a5.467 5.467 0 0 1-1.43-.821c-.96-.762-1.565-1.73-1.814-2.903a11.315 11.315 0 0 1 .032-5.005c.81-4.34 2.874-8.16 6.19-11.461 4.403-4.411 9.721-6.617 15.954-6.617 6.225 0 11.543 2.206 15.954 6.617" fill="url(#c)" transform="matrix(.35176 0 0 -.35176 -53.595 -152.27)"/><path style="line-height:6.61458302px;-inkscape-font-specification:Norwegian" d="M47.104 281.834c-.456 0-.816.388-.816.849 0 .46.355.816.816.816a.82.82 0 0 0 .833-.816.85.85 0 0 0-.833-.849zm-21.502 1.404c-.205 0-.382.046-.5.13-.118.085-.187.201-.187.4v.981l-.151-.031a9.89 9.89 0 0 0-1.872-.173c-1.288 0-2.148.066-2.657.374a1.264 1.264 0 0 0-.548.676c-.119.313-.177.73-.177 1.28v6.123c0 .982.25 1.508.787 1.824.539.315 1.407.41 2.628.41 1.308 0 2.178-.13 2.701-.484.524-.354.745-.932.745-1.942 0-.821-.147-1.37-.543-1.722-.395-.352-1.075-.528-2.186-.528h-2.787v-1.234l.148.03c.478.092 1.094.11 1.89.11 1.292 0 2.148-.112 2.658-.453.51-.34.724-.915.724-1.958v-3.284c0-.198-.07-.314-.186-.399a.843.843 0 0 0-.487-.13zm6.112 1.29c-1.226 0-2.038.137-2.526.495-.488.357-.697.945-.697 1.947v2.84c0 .935.23 1.54.743 1.932.512.39 1.337.571 2.544.571 1.392 0 2.17-.058 2.567-.196.199-.069.298-.15.355-.246.058-.097.078-.227.078-.405 0-.208-.04-.378-.117-.485-.076-.108-.186-.171-.396-.171-.794 0-1.057.19-2.408.19-.593 0-1.06-.078-1.395-.255-.335-.177-.53-.473-.53-.84v-.89h4.301a.717.717 0 0 0 .539-.213c.13-.138.213-.342.213-.62v-1.212c0-1.037-.179-1.623-.654-1.97-.476-.348-1.3-.472-2.617-.472zm8.83 0c-1.303 0-2.117.086-2.59.49-.472.403-.665 1.168-.665 2.623v4.002c0 .215.074.338.197.426.123.088.31.134.524.134.223 0 .412-.046.537-.134.125-.088.198-.211.198-.426v-3.81c0-.424.006-.754.04-1.019.036-.265.102-.47.236-.623.135-.152.328-.236.574-.28.246-.044.552-.053.949-.053.4 0 .712.01.963.053.25.044.447.126.587.278.139.151.211.358.25.623.038.265.046.597.046 1.02v3.81c0 .217.07.34.19.427.122.088.308.134.53.134.215 0 .401-.046.525-.134.123-.088.196-.211.196-.426v-4.002c0-1.455-.201-2.219-.682-2.623-.48-.404-1.303-.49-2.605-.49zm12.996 0c-1.227 0-2.038.137-2.527.495-.488.357-.696.945-.696 1.947v2.84c0 .935.23 1.54.742 1.932.513.39 1.338.571 2.545.571 1.392 0 2.17-.058 2.567-.196.2-.069.298-.15.355-.246.058-.097.079-.227.079-.405 0-.208-.04-.378-.117-.485-.077-.108-.188-.171-.398-.171-.793 0-1.056.19-2.406.19-.594 0-1.062-.078-1.396-.255-.335-.177-.53-.473-.53-.84v-.89h4.301a.716.716 0 0 0 .539-.213c.13-.138.213-.342.213-.62v-1.212c0-1.037-.18-1.623-.655-1.97-.475-.348-1.3-.472-2.616-.472zm-6.436.13c-.212 0-.398.047-.522.14-.124.09-.198.22-.198.435v6.41c0 .215.073.338.196.426.123.088.31.134.524.134.223 0 .414-.046.54-.134.124-.088.198-.211.198-.426v-6.41c0-.215-.075-.342-.201-.434-.126-.092-.316-.142-.537-.142zm-24.212 1.135c.662 0 1.4.065 1.918.146l.105.016v.92c0 .246-.01.453-.058.632a.757.757 0 0 1-Line truncated

After

Width:  |  Height:  |  Size: 8.5 KiB

+51 -4
View File
@@ -6,6 +6,7 @@ const url = require("url");
const querystring = require("querystring");
const config = require("./config");
const device = require("./device");
const commaAscii = ",".charCodeAt(0);
const newLineAscii = "\n".charCodeAt(0);
@@ -19,6 +20,7 @@ function filtersToQuery(filters) {
for (let [k, v] of Object.entries(filter)) {
let [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
if (!op || op === "=") q[param] = v;
else if (op === ">") q[param] = { $gt: v };
else throw new Error(`Operator "${op}" not recognized`);
}
queries.push(q);
@@ -28,22 +30,63 @@ function filtersToQuery(filters) {
else return { $or: queries };
}
function count(resource, filters, limit) {
return new Promise((resolve, reject) => {
let qs = {};
if (filters) {
let q = filtersToQuery(filters);
if (resource === "devices") q = device.transposeQuery(q);
qs.query = JSON.stringify(q);
}
if (limit) qs.limit = limit;
let options = url.parse(
`${config.GENIEACS_NBI}${resource}?${querystring.stringify(qs)}`
);
options.method = "HEAD";
let _http = options.protocol === "https:" ? https : http;
_http
.request(options, res => {
if (res.statusCode !== 200) {
reject(new Error(`Unexpected status code ${res.statusCode}`));
res.resume();
return;
}
resolve(+res.headers["total"]);
})
.end();
});
}
function query(resource, filters, limit, callback) {
return new Promise((resolve, reject) => {
let ret;
if (!callback) ret = [];
let q = {};
if (filters) q.query = JSON.stringify(filtersToQuery(filters));
let qs = {};
if (filters) {
let q = filtersToQuery(filters);
if (resource === "devices") q = device.transposeQuery(q);
qs.query = JSON.stringify(q);
}
if (limit) q.limit = limit;
if (limit) qs.limit = limit;
let options = url.parse(
`${config.GENIEACS_NBI}${resource}?${querystring.stringify(q)}`
`${config.GENIEACS_NBI}${resource}?${querystring.stringify(qs)}`
);
let _http = options.protocol === "https:" ? https : http;
_http.get(options, res => {
if (res.statusCode !== 200) {
reject(new Error(`Unexpected status code ${res.statusCode}`));
res.resume();
return;
}
let chunks = [];
let bytes = 0;
res.on("data", chunk => {
@@ -69,6 +112,7 @@ function query(resource, filters, limit, callback) {
if (buf.length > 1) {
let obj = JSON.parse(buf);
if (resource === "devices") obj = device.transpose(obj);
if (ret) ret.push(obj);
else callback(obj);
}
@@ -89,6 +133,8 @@ function query(resource, filters, limit, callback) {
}
if (buf.length > 1) {
let obj = JSON.parse(buf);
if (resource === "devices") obj = device.transpose(obj);
if (ret) ret.push(obj);
else callback(obj);
}
@@ -104,3 +150,4 @@ function query(resource, filters, limit, callback) {
}
exports.query = query;
exports.count = count;
+15 -15
View File
@@ -16,35 +16,35 @@ const resources = {
for (let [resource, update] of Object.entries(resources)) {
router.head(`/${resource}`, async ctx => {
let filters, limit;
if (ctx.request.query.query)
if (Array.isArray(ctx.request.query.query))
filters = ctx.request.query.query.map(q => JSON.parse(q));
let filter, limit;
if (ctx.request.query.filter)
if (Array.isArray(ctx.request.query.filter))
filter = ctx.request.query.query.map(q => JSON.parse(q));
else {
filters = JSON.parse(ctx.request.query.query);
if (!Array.isArray(filters)) filters = [filters];
filter = JSON.parse(ctx.request.query.filter);
if (!Array.isArray(filter)) filter = [filter];
}
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
let res = await apiFunctions.query(resource, filters, limit);
ctx.set("X-Total-Count", res.length);
let count = await apiFunctions.count(resource, filter, limit);
ctx.set("X-Total-Count", count);
ctx.body = "";
});
router.get(`/${resource}`, async ctx => {
let filters, limit;
if (ctx.request.query.query)
if (Array.isArray(ctx.request.query.query))
filters = ctx.request.query.query.map(q => JSON.parse(q));
let filter, limit;
if (ctx.request.query.filter)
if (Array.isArray(ctx.request.query.filter))
filter = ctx.request.query.query.map(q => JSON.parse(q));
else {
filters = JSON.parse(ctx.request.query.query);
if (!Array.isArray(filters)) filters = [filters];
filter = JSON.parse(ctx.request.query.filter);
if (!Array.isArray(filter)) filter = [filter];
}
if (ctx.request.query.limit) limit = +ctx.request.query.limit;
ctx.body = await apiFunctions.query(resource, filters, limit);
ctx.body = await apiFunctions.query(resource, filter, limit);
});
router.head(`/${resource}/:id`, async (ctx, next) => {
+152
View File
@@ -0,0 +1,152 @@
"use strict";
function recursive(input, root, output, timestamp) {
for (let [name, tree] of Object.entries(input)) {
if (root.length === 0)
if (name === "_lastInform") {
output["Events.Inform"] = {
value: [timestamp, "xsd:dateTime"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_registered") {
output["Events.Registered"] = {
value: [timestamp, "xsd:dateTime"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_lastBoot") {
output["Events.1_BOOT"] = {
value: [timestamp, "xsd:dateTime"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_lastBootstrap") {
output["Events.0_BOOTSTRAP"] = {
value: [timestamp, "xsd:dateTime"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_id") {
output["DeviceID.ID"] = {
value: [tree, "xsd:string"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_deviceId") {
output["DeviceID.Manufacturer"] = {
value: [tree["_Manufacturer"], "xsd:string"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
output["DeviceID.OUI"] = {
value: [tree["_OUI"], "xsd:string"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
output["DeviceID.ProductClass"] = {
value: [tree["_ProductClass"], "xsd:string"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
output["DeviceID.SerialNumber"] = {
value: [tree["_SerialNumber"], "xsd:string"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
} else if (name === "_tags") {
output["Tags"] = {
writable: true,
writableTimestamp: timestamp,
object: true,
objectTimestamp: timestamp
};
for (let t of tree)
output[`Tags.${t}`] = {
value: [true, "xsd:boolean"],
valueTimestamp: timestamp,
writable: false,
writableTimestamp: timestamp,
object: false,
objectTimestamp: timestamp
};
}
if (name.startsWith("_")) continue;
let childrenTimestamp = timestamp;
if (root.length === 0) childrenTimestamp = input["_timestamp"] || 1;
else if (input["_timestamp"] > timestamp)
childrenTimestamp = input["_timestamp"];
const attrs = {};
if (tree["_value"]) {
attrs["value"] = [tree["_value"], tree["_type"]];
attrs["valueTimestamp"] = childrenTimestamp;
attrs["object"] = false;
attrs["objectTimestamp"] = childrenTimestamp;
} else if (tree["_object"]) {
attrs["object"] = tree["_object"];
attrs["objectTimestamp"] = childrenTimestamp;
}
if (tree["_writable"]) {
attrs["writable"] = tree["_writable"];
attrs["writableTimestamp"] = childrenTimestamp;
}
let r = root.concat(name);
output[r.join(".")] = attrs;
if (attrs["object"]) recursive(tree, r, output, childrenTimestamp);
}
}
function transpose(device) {
const newDevice = {};
const timestamp = new Date(device["_lastInform"] || 1).getTime();
recursive(device, [], newDevice, timestamp);
return newDevice;
}
function transposeQuery(query) {
const newQuery = {};
for (let [key, val] of Object.entries(query))
if (key.startsWith("$")) newQuery[key] = transposeQuery(val);
else if (key === "DeviceID.ID") newQuery["_id"] = val;
return newQuery;
}
exports.transpose = transpose;
exports.transposeQuery = transposeQuery;