Compare commits

...
6 Commits
8 changed files with 133 additions and 111 deletions

No files matched your search

+7
View File
@@ -1,5 +1,12 @@
# Change Log
## 1.2.12 (2024-03-28)
- Fix broken XMPP support in the previous release.
- Fix regression causing CSV downloads to be buffered in memory before being
streamed to the client.
## 1.2.11 (2024-03-21)
- Resolved an issue from the previous release that caused incompatibility with
+18
View File
@@ -0,0 +1,18 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.12"
sphinx:
configuration: docs/conf.py
formats: all
python:
install:
- requirements: docs/requirements.txt
+2 -2
View File
@@ -19,7 +19,7 @@ import json
# -- Project information -----------------------------------------------------
project = 'GenieACS Documentation'
copyright = '2019, GenieACS Inc.'
copyright = '2024, GenieACS Inc.'
author = 'GenieACS Inc.'
# The full version, including alpha/beta/rc tags
@@ -58,7 +58,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# html_static_path = ['_static']
master_doc = 'index'
+1
View File
@@ -0,0 +1 @@
sphinx_rtd_theme==2.0.0
+66 -64
View File
@@ -1,4 +1,4 @@
import { PassThrough } from "node:stream";
import { Readable } from "node:stream";
import Router from "koa-router";
import { ObjectId } from "mongodb";
import * as db from "./db.ts";
@@ -88,10 +88,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
.replace(/[:.]/g, "")}.csv`,
);
ctx.body = new PassThrough();
ctx.body.write(
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp\n",
);
const lines: string[] = [
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp",
];
for (const k of Object.keys(device).sort()) {
const p = device[k];
@@ -118,9 +117,9 @@ router.get(`/devices/:id.csv`, async (ctx) => {
p.accessList ? p.accessList.join(", ") : "",
new Date(p.accessListTimestamp).toJSON(),
];
ctx.body.write(row.map((r) => (r != null ? r : "")).join(",") + "\n");
lines.push(row.map((r) => (r != null ? r : "")).join(","));
}
ctx.body.end();
ctx.body = lines.join("\n");
logger.accessInfo(log);
});
@@ -201,16 +200,17 @@ for (const [resource, flags] of Object.entries(resources)) {
]);
}
ctx.body = new PassThrough();
ctx.type = "application/json";
let c = 0;
ctx.body.write("[\n");
for await (const obj of db.query(resource, filter, options))
ctx.body.write((c++ ? "," : "") + JSON.stringify(obj) + "\n");
ctx.body.end("]");
logger.accessInfo(log);
ctx.type = "application/json";
ctx.body = Readable.from(
(async function* () {
let c = 0;
yield "[\n";
for await (const obj of db.query(resource, filter, options))
yield (c++ ? "," : "") + JSON.stringify(obj) + "\n";
yield "]";
})(),
);
});
// CSV download
@@ -260,60 +260,62 @@ for (const [resource, flags] of Object.entries(resources)) {
]);
}
ctx.body = new PassThrough();
logger.accessInfo(log);
ctx.type = "text/csv";
ctx.attachment(
`${resource}-${new Date(now).toISOString().replace(/[:.]/g, "")}.csv`,
);
ctx.body.write(
Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) + "\n",
ctx.body = Readable.from(
(async function* () {
yield Object.keys(columns).map((k) => `"${k.replace(/"/, '""')}"`) +
"\n";
for await (const obj of db.query(resource, filter, options)) {
const arr = Object.values(columns).map((exp) => {
const v = evaluate(exp, obj, null, (e) => {
if (Array.isArray(e)) {
if (e[0] === "PARAM") {
if (resource === "devices") {
if (e[1] === "Tags") {
const tags = [];
for (const p in obj)
if (p.startsWith("Tags."))
tags.push(decodeTag(p.slice(5)));
return tags.join(", ");
}
if (e === exp) {
const p = obj[e[1]];
if (
p &&
p.value &&
p.value[1] === "xsd:dateTime" &&
typeof p.value[0] === "number"
)
return new Date(p.value[0]).toJSON();
}
} else if (resource === "faults") {
if (e[1] === "detail") return yamlStringify(obj["detail"]);
}
} else if (e[0] === "FUNC") {
if (e[1] === "DATE_STRING") {
if (e[2] && !Array.isArray(e[2]))
return new Date(e[2]).toJSON();
}
}
}
return e;
});
if (Array.isArray(v) || v == null) return "";
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
return v;
});
yield arr.join(",") + "\n";
}
})(),
);
for await (const obj of db.query(resource, filter, options)) {
const arr = Object.values(columns).map((exp) => {
const v = evaluate(exp, obj, null, (e) => {
if (Array.isArray(e)) {
if (e[0] === "PARAM") {
if (resource === "devices") {
if (e[1] === "Tags") {
const tags = [];
for (const p in obj)
if (p.startsWith("Tags.")) tags.push(decodeTag(p.slice(5)));
return tags.join(", ");
}
if (e === exp) {
const p = obj[e[1]];
if (
p &&
p.value &&
p.value[1] === "xsd:dateTime" &&
typeof p.value[0] === "number"
)
return new Date(p.value[0]).toJSON();
}
} else if (resource === "faults") {
if (e[1] === "detail") return yamlStringify(obj["detail"]);
}
} else if (e[0] === "FUNC") {
if (e[1] === "DATE_STRING") {
if (e[2] && !Array.isArray(e[2]))
return new Date(e[2]).toJSON();
}
}
}
return e;
});
if (Array.isArray(v) || v == null) return "";
if (typeof v === "string") return `"${v.replace(/"/g, '""')}"`;
return v;
});
ctx.body.write(arr.join(",") + "\n");
}
ctx.body.end();
logger.accessInfo(log);
});
router.head(`/${resource}/:id`, async (ctx) => {
+36 -42
View File
@@ -321,50 +321,44 @@ export default class XmppClient extends EventEmitter {
this._iqStanzaCallbacks = new Map();
}
static connect(opts: XmppClientOptions): Promise<XmppClient> {
return new Promise((resolve, reject) => {
let socket = new net.Socket();
socket.on("error", reject);
socket.connect(
opts.port || 5222,
opts.host,
void async function () {
static async connect(opts: XmppClientOptions): Promise<XmppClient> {
function connectSocket(host: string, port: number): Promise<net.Socket> {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.on("error", reject);
socket.connect(port, host, () => {
socket.removeListener("error", reject);
try {
let status = 1;
while (status) {
if (status === STATUS_STARTTLS)
socket = await upgradeTls(socket, opts.host);
status = await xmppStream(
socket,
init(
socket,
opts.host,
opts.username,
opts.password,
opts.resource,
),
);
}
resolve(socket);
});
});
}
const client = new XmppClient();
client._socket = socket;
client._host = opts.host;
client._username = opts.username;
client._resource = opts.resource;
socket.on("data", client._onData.bind(client));
socket.on("error", client._onError.bind(client));
if (opts.timeout)
socket.setTimeout(opts.timeout, client.close.bind(client));
resolve(client);
} catch (err) {
socket.destroy();
reject(err);
}
},
);
});
let socket = await connectSocket(opts.host, opts.port || 5222);
try {
let status = 1;
while (status) {
if (status === STATUS_STARTTLS)
socket = await upgradeTls(socket, opts.host);
status = await xmppStream(
socket,
init(socket, opts.host, opts.username, opts.password, opts.resource),
);
}
} catch (err) {
socket.destroy();
throw err;
}
const client = new XmppClient();
client._socket = socket;
client._host = opts.host;
client._username = opts.username;
client._resource = opts.resource;
socket.on("data", client._onData.bind(client));
socket.on("error", client._onError.bind(client));
if (opts.timeout)
socket.setTimeout(opts.timeout, client.close.bind(client));
return client;
}
close(): void {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "genieacs",
"version": "1.2.11",
"version": "1.2.12",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "genieacs",
"version": "1.2.11",
"version": "1.2.12",
"license": "AGPL-3.0",
"dependencies": {
"@breejs/later": "^4.2.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "genieacs",
"version": "1.2.11",
"version": "1.2.12",
"description": "A TR-069 Auto Configuration Server (ACS)",
"repository": {
"type": "git",