Implement TR-069 Upload method

This commit is contained in:
Zaid Abdulla committed 2020-12-08 17:21:18 -08:00
1 parent d29448e3a7
commit dae521ae89
22 files changed
+1016 -68

No files matched your search

+2
View File
@@ -39,6 +39,7 @@ import tags from "./components/tags";
import ping from "./components/ping";
import deviceLink from "./components/device-link";
import longTextComponent from "./long-text-component";
import deviceUploads from "./components/device-uploads";
const comps = {
parameter,
@@ -54,6 +55,7 @@ const comps = {
ping,
"device-link": deviceLink,
"long-text": longTextComponent,
"device-uploads": deviceUploads,
};
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
+25
View File
@@ -22,6 +22,7 @@ import { m } from "../components";
import * as taskQueue from "../task-queue";
import * as notifications from "../notifications";
import * as store from "../store";
import * as overlay from "../overlay";
const component: ClosureComponent = (): Component => {
return {
@@ -78,6 +79,30 @@ const component: ClosureComponent = (): Component => {
)
);
buttons.push(
m(
"button.primary",
{
title: "Fetch files from device",
onclick: () => {
const deviceId = device["DeviceID.ID"].value[0];
overlay.open(() => {
const dev = store.fetch("devices", [
"=",
["PARAM", "DeviceID.ID"],
deviceId,
]);
return [
m("h3", `Uploads from ${deviceId}`),
m("device-uploads", { device: dev.value[0] }),
];
});
},
},
"Uploads"
)
);
buttons.push(
m(
"button.primary",
+155
View File
@@ -0,0 +1,155 @@
/**
* Copyright 2013-2020 GenieACS Inc.
*
* This file is part of GenieACS.
*
* GenieACS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* GenieACS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClosureComponent, Component } from "mithril";
import { m } from "../components";
import * as store from "../store";
import * as notifications from "../notifications";
import { getIcon } from "../icons";
import * as taskQueue from "../task-queue";
const component: ClosureComponent = (): Component => {
return {
view: (vnode) => {
const device = vnode.attrs["device"];
const deviceId = device["DeviceID.ID"].value[0];
const files = store.fetch("uploads", [
"AND",
[">", ["PARAM", "_id"], `${deviceId}/`],
["<", ["PARAM", "_id"], `${deviceId}/zzzz`],
]);
const uploads: Record<string, any> = {};
for (const [k, v] of Object.entries(device)) {
if (k.startsWith("Uploads.")) {
const parts = k.split(".");
if (parts.length !== 3) continue;
uploads[parts[1]] = uploads[parts[1]] || {};
uploads[parts[1]][parts[2]] = v["value"][0];
}
}
const headers = ["Filename", "Type", "Timestamp", "Status"].map((l) =>
m("th", l)
);
const thead = m("thead", m("tr", headers));
let minTime = 999999;
const timestamp = store.getTimestamp();
const rows = [];
const uploadsSorted = Object.values(uploads).sort(
(a, b) => a["Upload"] - b["Upload"]
);
for (const u of Object.values(uploadsSorted)) {
if (!u["Upload"]) continue;
const ready = u["LastUpload"] >= u["Upload"];
if (!ready) {
minTime = Math.min(minTime, u["Upload"] - timestamp);
rows.push([
m("td", u["FileName"]),
m("td", u["FileType"]),
m("td", new Date(u["Upload"]).toLocaleString()),
m("td", "Waiting for upload"),
]);
} else {
const filePath = `${deviceId}/${u["FileName"]}`;
if (!Array.isArray(files.value)) continue;
if (!files.value.some((f) => f["_id"] === filePath)) continue;
const deleteButton = m(
"button",
{
title: "Delete file",
onclick: (e) => {
e.redraw = false;
store
.deleteResource("uploads", filePath)
.then(() => {
notifications.push("success", "File deleted");
store.fulfill(Date.now(), Date.now());
m.redraw();
})
.catch((err) => {
notifications.push("error", err.message);
store.fulfill(Date.now(), Date.now());
});
},
},
getIcon("delete-instance")
);
rows.push([
m("td", u["FileName"]),
m("td", u["FileType"]),
m("td", new Date(u["LastUpload"]).toLocaleString()),
m(
"td",
m(
"a",
{
href: `api/uploads/blob/${encodeURIComponent(filePath)}`,
},
"Ready"
)
),
m("td", deleteButton),
]);
}
}
if (minTime < 60000) {
setTimeout(() => {
store.fulfill(0, timestamp + 5000);
}, 10000);
}
const addButton = m(
"button",
{
title: "Fetch a new file",
onclick: () => {
taskQueue.stageUpload({
name: "upload",
devices: [deviceId],
});
},
},
getIcon("add-instance")
);
const empty = !rows.length;
rows.push([m("td", { colspan: headers.length }), m("td", addButton)]);
const tbody = m(
"tbody",
rows.map((r) => m("tr", r)),
empty
? m("tr.empty", m("td", { colspan: headers.length }, "No uploads"))
: null
);
return m("table.table", thead, tbody);
},
};
};
export default component;
+1 -1
View File
@@ -106,7 +106,7 @@ body {
& > .drawer-wrapper {
position: fixed;
z-index: 10;
z-index: 101;
width: 720px;
margin-left: -360px;
top: 0;
+47
View File
@@ -184,6 +184,44 @@ function renderStagingDownload(task: StageTask): Children {
];
}
function renderStagingUpload(task: StageTask): Children {
if (!task.fileName || !task.fileType) invalid.add(task);
else invalid.delete(task);
const typesList = [
"",
"1 Vendor Configuration File",
"2 Vendor Log File",
].map((t) =>
m(
"option",
{ disabled: !t, value: t, selected: (task.fileType || "") === t },
t
)
);
return [
"Fetch ",
m(
"select",
{
onchange: (e) => {
task.fileType = e.target.value;
task.fileName = task.fileType.replace(/\s+/g, "_") + ".txt";
},
},
typesList
),
" as ",
m("input", {
onchange: (e) => {
task.fileName = e.target.value;
},
value: task.fileName || "",
}),
];
}
function renderStaging(staging: Set<StageTask>): Child[] {
const elements: Child[] = [];
@@ -204,6 +242,7 @@ function renderStaging(staging: Set<StageTask>): Child[] {
if (s.name === "setParameterValues")
elms = renderStagingSpv(s, queueFunc, cancelFunc);
else if (s.name === "download") elms = renderStagingDownload(s);
else if (s.name === "upload") elms = renderStagingUpload(s);
const queue = m(
"button.primary",
@@ -323,6 +362,14 @@ function renderQueue(queue: Set<QueueTask>): Child[] {
m(".actions", actions)
)
);
} else if (t.name === "upload") {
details.push(
m(
`div.${t.status}`,
`Fetch file ${t.fileName} (${t.fileType})`,
m(".actions", actions)
)
);
} else {
details.push(m(`div.${t.status}`, t.name, m(".actions", actions)));
}
+1
View File
@@ -47,6 +47,7 @@ const attributes = [
"devices",
"faults",
"files",
"uploads",
"permissions",
"users",
"presets",
+1 -1
View File
@@ -58,11 +58,11 @@ const resources: Resources = {};
for (const r of [
"devices",
"faults",
"files",
"presets",
"provisions",
"virtualParameters",
"files",
"uploads",
"config",
"users",
"permissions",
+8
View File
@@ -89,6 +89,14 @@ export function stageDownload(task: StageTask): void {
staging.add(task);
}
export function stageUpload(task: StageTask): void {
if (queue.size + task.devices.length > MAX_QUEUE) {
notifications.push("error", "Too many tasks in queue");
return;
}
staging.add(task);
}
export function commit(
tasks: QueueTask[],
callback: (