Display spinner animation during loading
This commit is contained in:
1 parent
4871613c67
commit
688811317c
23 files changed
+447
-307
No files matched your search
@@ -64,39 +64,27 @@ const adminPages = [
|
|||||||
|
|
||||||
let state;
|
let state;
|
||||||
|
|
||||||
let lastRenderTimestamp = 0;
|
|
||||||
let pageVisitTimestamp = 0;
|
|
||||||
let fulfillTimeout = null;
|
|
||||||
|
|
||||||
function fulfill(): void {
|
|
||||||
clearTimeout(fulfillTimeout);
|
|
||||||
fulfillTimeout = setTimeout(() => {
|
|
||||||
store.fulfill(lastRenderTimestamp, pageVisitTimestamp, (updated) => {
|
|
||||||
if (updated) m.redraw();
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pagify(pageName, page): RouteResolver {
|
function pagify(pageName, page): RouteResolver {
|
||||||
const component: RouteResolver = {
|
const component: RouteResolver = {
|
||||||
render: () => {
|
render: () => {
|
||||||
lastRenderTimestamp = Date.now();
|
const lastRenderTimestamp = Date.now();
|
||||||
let p;
|
let p;
|
||||||
if (state && state.error) p = m(errorPage.component, state);
|
if (state && state.error) p = m(errorPage.component, state);
|
||||||
else p = m(contextifyComponent(page.component), state);
|
else p = m(contextifyComponent(page.component), state);
|
||||||
fulfill();
|
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
attrs["page"] = pageName;
|
attrs["page"] = pageName;
|
||||||
|
attrs["oncreate"] = attrs["onupdate"] = () => {
|
||||||
|
store.fulfill(lastRenderTimestamp);
|
||||||
|
};
|
||||||
return m(layout, attrs, p);
|
return m(layout, attrs, p);
|
||||||
},
|
},
|
||||||
onmatch: null,
|
onmatch: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
component.onmatch = (args, requestedPath) => {
|
component.onmatch = (args, requestedPath) => {
|
||||||
pageVisitTimestamp = Date.now();
|
store.setTimestamp(Date.now());
|
||||||
if (!page.init) {
|
if (!page.init) {
|
||||||
state = null;
|
state = null;
|
||||||
fulfill();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +95,6 @@ function pagify(pageName, page): RouteResolver {
|
|||||||
if (!st) return void m.route.set("/");
|
if (!st) return void m.route.set("/");
|
||||||
state = st;
|
state = st;
|
||||||
resolve();
|
resolve();
|
||||||
fulfill();
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (!window.username && err.message.indexOf("authorized") >= 0) {
|
if (!window.username && err.message.indexOf("authorized") >= 0) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import tags from "./components/tags";
|
|||||||
import ping from "./components/ping";
|
import ping from "./components/ping";
|
||||||
import deviceLink from "./components/device-link";
|
import deviceLink from "./components/device-link";
|
||||||
import longTextComponent from "./long-text-component";
|
import longTextComponent from "./long-text-component";
|
||||||
|
import loading from "./components/loading";
|
||||||
|
|
||||||
const comps = {
|
const comps = {
|
||||||
parameter,
|
parameter,
|
||||||
@@ -54,6 +55,7 @@ const comps = {
|
|||||||
ping,
|
ping,
|
||||||
"device-link": deviceLink,
|
"device-link": deviceLink,
|
||||||
"long-text": longTextComponent,
|
"long-text": longTextComponent,
|
||||||
|
loading: loading,
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
||||||
|
|||||||
@@ -171,25 +171,29 @@ const component: ClosureComponent = (): Component => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return m(
|
return m(
|
||||||
".all-parameters",
|
"loading",
|
||||||
|
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||||
m(
|
m(
|
||||||
"a.download-csv",
|
".all-parameters",
|
||||||
{
|
|
||||||
href: `api/devices/${encodeURIComponent(
|
|
||||||
device["DeviceID.ID"].value[0]
|
|
||||||
)}.csv`,
|
|
||||||
download: "",
|
|
||||||
style: "float: right;",
|
|
||||||
},
|
|
||||||
"Download"
|
|
||||||
),
|
|
||||||
search,
|
|
||||||
m(
|
|
||||||
".parameter-list",
|
|
||||||
m("table", m("tbody", rows)),
|
|
||||||
m(
|
m(
|
||||||
"m",
|
"a.download-csv",
|
||||||
`Displaying ${filteredKeys.length} out of ${count} parameters.`
|
{
|
||||||
|
href: `api/devices/${encodeURIComponent(
|
||||||
|
device["DeviceID.ID"].value[0]
|
||||||
|
)}.csv`,
|
||||||
|
download: "",
|
||||||
|
style: "float: right;",
|
||||||
|
},
|
||||||
|
"Download"
|
||||||
|
),
|
||||||
|
search,
|
||||||
|
m(
|
||||||
|
".parameter-list",
|
||||||
|
m("table", m("tbody", rows)),
|
||||||
|
m(
|
||||||
|
"m",
|
||||||
|
`Displaying ${filteredKeys.length} out of ${count} parameters.`
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ const component: ClosureComponent = (): Component => {
|
|||||||
.deleteResource("faults", f["_id"])
|
.deleteResource("faults", f["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Fault deleted");
|
notifications.push("success", "Fault deleted");
|
||||||
store.fulfill(Date.now(), Date.now());
|
store.setTimestamp(Date.now());
|
||||||
m.redraw();
|
m.redraw();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(Date.now(), Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -95,7 +95,11 @@ const component: ClosureComponent = (): Component => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m("table.table", thead, tbody);
|
return m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [faults] },
|
||||||
|
m("table.table", thead, tbody)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2021 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 { VnodeDOM, ClosureComponent, Component } from "mithril";
|
||||||
|
|
||||||
|
const component: ClosureComponent = (): Component => {
|
||||||
|
let overlay: HTMLElement;
|
||||||
|
let dom: Element;
|
||||||
|
let loading = false;
|
||||||
|
|
||||||
|
function apply(vnode: VnodeDOM): void {
|
||||||
|
if (!loading) {
|
||||||
|
if (overlay) overlay.parentElement.remove();
|
||||||
|
if (dom) dom.classList.remove("loading");
|
||||||
|
overlay = null;
|
||||||
|
dom = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dom && dom !== vnode.dom) dom.classList.remove("loading");
|
||||||
|
|
||||||
|
dom = vnode.dom;
|
||||||
|
dom.classList.add("loading");
|
||||||
|
|
||||||
|
if (!overlay) {
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.style.position = "relative";
|
||||||
|
wrapper.style.pointerEvents = "none";
|
||||||
|
overlay = document.createElement("div");
|
||||||
|
overlay.classList.add("loading-overlay");
|
||||||
|
overlay.style.position = "absolute";
|
||||||
|
wrapper.appendChild(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = overlay.parentElement;
|
||||||
|
if (wrapper.parentElement !== dom.parentElement)
|
||||||
|
dom.parentNode.appendChild(wrapper);
|
||||||
|
|
||||||
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
|
const domRect = dom.getBoundingClientRect();
|
||||||
|
overlay.style.width = `${dom.scrollWidth}px`;
|
||||||
|
overlay.style.height = `${dom.scrollHeight}px`;
|
||||||
|
overlay.style.left = `${domRect.left - wrapperRect.left}px`;
|
||||||
|
overlay.style.top = `${domRect.top - wrapperRect.top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
view: (vnode) => {
|
||||||
|
const queries = vnode.attrs["queries"];
|
||||||
|
loading = queries.some((q) => q.fulfilling);
|
||||||
|
return vnode.children;
|
||||||
|
},
|
||||||
|
oncreate: apply,
|
||||||
|
onupdate: apply,
|
||||||
|
onremove: () => {
|
||||||
|
if (overlay) overlay.parentElement.remove();
|
||||||
|
if (dom) dom.classList.remove("loading");
|
||||||
|
overlay = null;
|
||||||
|
dom = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default component;
|
||||||
@@ -38,6 +38,11 @@ const component: ClosureComponent = (): Component => {
|
|||||||
return m(
|
return m(
|
||||||
"tr",
|
"tr",
|
||||||
{
|
{
|
||||||
|
oncreate: (vn) => {
|
||||||
|
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||||
|
? ""
|
||||||
|
: "none";
|
||||||
|
},
|
||||||
onupdate: (vn) => {
|
onupdate: (vn) => {
|
||||||
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||||
? ""
|
? ""
|
||||||
@@ -49,7 +54,11 @@ const component: ClosureComponent = (): Component => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return m("table.parameter-list", rows);
|
return m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||||
|
m("table.parameter-list", rows)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -149,7 +149,14 @@ const component: ClosureComponent = (): Component => {
|
|||||||
let label;
|
let label;
|
||||||
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
if (vnode.attrs["label"]) label = m("h2", vnode.attrs["label"]);
|
||||||
|
|
||||||
return [label, m("table.table", thead, m("tbody", rows))];
|
return [
|
||||||
|
label,
|
||||||
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||||
|
m("table.table", thead, m("tbody", rows))
|
||||||
|
),
|
||||||
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${deviceId}: Tags updated`
|
`${deviceId}: Tags updated`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
@@ -99,7 +99,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
notifications.push("success", `${deviceId}: Tags updated`);
|
notifications.push("success", `${deviceId}: Tags updated`);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
e.target.disabled = false;
|
e.target.disabled = false;
|
||||||
|
|||||||
+18
-14
@@ -60,12 +60,12 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
.resourceExists("config", id)
|
.resourceExists("config", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Config already exists" });
|
return void resolve({ _id: "Config already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists && !isNew) {
|
if (!exists && !isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Config does not exist" });
|
return void resolve({ _id: "Config does not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
"success",
|
"success",
|
||||||
`Config ${exists ? "updated" : "created"}`
|
`Config ${exists ? "updated" : "created"}`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@@ -87,11 +87,11 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
|||||||
.deleteResource("config", object["_id"])
|
.deleteResource("config", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Config deleted");
|
notifications.push("success", "Config deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -337,11 +337,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
);
|
);
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
}
|
}
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
overlay.close(cb);
|
overlay.close(cb);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -366,14 +366,18 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing config"),
|
m("h1", "Listing config"),
|
||||||
m(
|
m(
|
||||||
".all-parameters",
|
"loading",
|
||||||
search,
|
{ queries: [confs] },
|
||||||
m(
|
m(
|
||||||
".parameter-list",
|
".all-parameters",
|
||||||
{ style: "height: 400px" },
|
search,
|
||||||
renderTable(confs, vnode.state["searchString"])
|
m(
|
||||||
),
|
".parameter-list",
|
||||||
m(".actions-bar", [newConfig].concat(subs))
|
{ style: "height: 400px" },
|
||||||
|
renderTable(confs, vnode.state["searchString"])
|
||||||
|
),
|
||||||
|
m(".actions-bar", [newConfig].concat(subs))
|
||||||
|
)
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
+28
-1
@@ -347,7 +347,6 @@ body {
|
|||||||
table.table {
|
table.table {
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
& table.table {
|
& table.table {
|
||||||
@@ -753,3 +752,31 @@ span.parameter-value {
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
&::after {
|
||||||
|
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEwIDUwYTQwIDQwIDAgMDA4MCAwIDQwIDQyIDAgMDEtODAgMCIgZmlsbD0iI2IxMmQ1YyIgLz48L3N2Zz4=);
|
||||||
|
content: "";
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-4
@@ -42,13 +42,25 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
||||||
|
|
||||||
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
|
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]);
|
||||||
if (!dev.length) return "Loading";
|
if (!dev.value.length) {
|
||||||
|
if (!dev.fulfilling)
|
||||||
|
return m("p.error", `No such device ${vnode.attrs["deviceId"]}`);
|
||||||
|
return m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [dev] },
|
||||||
|
m("div", { style: "height: 100px;" })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const conf = config.ui.device;
|
const conf = config.ui.device;
|
||||||
const cmps = [];
|
const cmps = [];
|
||||||
|
|
||||||
for (const c of Object.values(conf))
|
for (const c of Object.values(conf)) {
|
||||||
cmps.push(m.context({ device: dev[0] }, c["type"], c));
|
cmps.push(
|
||||||
|
m.context({ device: dev.value[0], deviceQuery: dev }, c["type"], c)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return [m("h1", vnode.attrs["deviceId"]), cmps];
|
return [m("h1", vnode.attrs["deviceId"]), cmps];
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-10
@@ -154,14 +154,14 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
.deleteResource("devices", id)
|
.deleteResource("devices", id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", `${id}: Deleted`);
|
notifications.push("success", `${id}: Deleted`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Delete"
|
"Delete"
|
||||||
@@ -186,14 +186,14 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
.updateTags(id, { [tag]: true })
|
.updateTags(id, { [tag]: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", `${id}: Tags updated`);
|
notifications.push("success", `${id}: Tags updated`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Tag"
|
"Tag"
|
||||||
@@ -220,14 +220,14 @@ function renderActions(selected: Set<string>): Children {
|
|||||||
.updateTags(id, { [tag]: false })
|
.updateTags(id, { [tag]: false })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", `${id}: Tags updated`);
|
notifications.push("success", `${id}: Tags updated`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", `${id}: ${err.message}`);
|
notifications.push("error", `${id}: ${err.message}`);
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (--counter === 0) store.fulfill(0, Date.now());
|
if (--counter === 0) store.setTimestamp(Date.now());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Untag"
|
"Untag"
|
||||||
@@ -336,7 +336,7 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing devices"),
|
m("h1", "Listing devices"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m("loading", { queries: [devs, count] }, m(indexTableComponent, attrs)),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ const component: ClosureComponent = (): Component => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
|
|||||||
+7
-3
@@ -187,11 +187,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} faults deleted`
|
`${res.length} faults deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -208,7 +208,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing faults"),
|
m("h1", "Listing faults"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [faults, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-5
@@ -93,7 +93,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
.resourceExists("files", id)
|
.resourceExists("files", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ file: "File already exists" });
|
return void resolve({ file: "File already exists" });
|
||||||
}
|
}
|
||||||
const headers = Object.assign(
|
const headers = Object.assign(
|
||||||
@@ -117,7 +117,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
"success",
|
"success",
|
||||||
`File ${exists ? "updated" : "created"}`
|
`File ${exists ? "updated" : "created"}`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@@ -288,11 +288,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} files deleted`
|
`${res.length} files deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -310,7 +310,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing files"),
|
m("h1", "Listing files"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [files, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-7
@@ -117,7 +117,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
.resourceExists("permissions", id)
|
.resourceExists("permissions", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Permission already exists" });
|
return void resolve({ _id: "Permission already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
.putResource("permissions", id, object)
|
.putResource("permissions", id, object)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Permission created");
|
notifications.push("success", "Permission created");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@@ -136,11 +136,11 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
|||||||
.deleteResource("permissions", object["_id"])
|
.deleteResource("permissions", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Permission deleted");
|
notifications.push("success", "Permission deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -351,11 +351,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} permissions deleted`
|
`${res.length} permissions deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -373,7 +373,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing permissions"),
|
m("h1", "Listing permissions"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [permissions, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+21
-15
@@ -116,21 +116,27 @@ function drawChart(chartData): Children {
|
|||||||
|
|
||||||
legend.push(m("span.legend-total", `Total: ${total}`));
|
legend.push(m("span.legend-total", `Total: ${total}`));
|
||||||
|
|
||||||
return m("div", { class: "pie-chart" }, [
|
return m(
|
||||||
m(
|
"loading",
|
||||||
"svg",
|
{
|
||||||
{
|
queries: Object.values(chartData.slices).map((s) => s["count"]),
|
||||||
// Adding 2 as padding; strokes must not be more than 2
|
},
|
||||||
viewBox: "-102 -102 204 204",
|
m("div", { class: "pie-chart" }, [
|
||||||
width: "204px",
|
m(
|
||||||
height: "204px",
|
"svg",
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
{
|
||||||
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
// Adding 2 as padding; strokes must not be more than 2
|
||||||
},
|
viewBox: "-102 -102 204 204",
|
||||||
paths.concat(links)
|
width: "204px",
|
||||||
),
|
height: "204px",
|
||||||
m(".legend", legend),
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
]);
|
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
||||||
|
},
|
||||||
|
paths.concat(links)
|
||||||
|
),
|
||||||
|
m(".legend", legend),
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const component: ClosureComponent = (): Component => {
|
const component: ClosureComponent = (): Component => {
|
||||||
|
|||||||
+12
-8
@@ -86,12 +86,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.resourceExists("presets", id)
|
.resourceExists("presets", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Preset already exists" });
|
return void resolve({ _id: "Preset already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists && !isNew) {
|
if (!exists && !isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Preset does not exist" });
|
return void resolve({ _id: "Preset does not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
"success",
|
"success",
|
||||||
`Preset ${exists ? "updated" : "created"}`
|
`Preset ${exists ? "updated" : "created"}`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@@ -113,12 +113,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.deleteResource("presets", object["_id"])
|
.deleteResource("presets", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Preset deleted");
|
notifications.push("success", "Preset deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("Undefined action"));
|
reject(new Error("Undefined action"));
|
||||||
@@ -413,11 +413,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} presets deleted`
|
`${res.length} presets deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -435,7 +435,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing presets"),
|
m("h1", "Listing presets"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [presets, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-8
@@ -66,12 +66,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.resourceExists("provisions", id)
|
.resourceExists("provisions", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Provision already exists" });
|
return void resolve({ _id: "Provision already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists && !isNew) {
|
if (!exists && !isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Provision does not exist" });
|
return void resolve({ _id: "Provision does not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
"success",
|
"success",
|
||||||
`Provision ${exists ? "updated" : "created"}`
|
`Provision ${exists ? "updated" : "created"}`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -99,11 +99,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.deleteResource("provisions", object["_id"])
|
.deleteResource("provisions", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Provision deleted");
|
notifications.push("success", "Provision deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -332,11 +332,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} provisions deleted`
|
`${res.length} provisions deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -354,7 +354,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing provisions"),
|
m("h1", "Listing provisions"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [provisions, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+141
-171
@@ -76,14 +76,14 @@ for (const r of [
|
|||||||
}
|
}
|
||||||
|
|
||||||
class QueryResponse {
|
class QueryResponse {
|
||||||
public get fulfilled(): boolean {
|
public get fulfilled(): number {
|
||||||
queries.accessed.set(this, Date.now());
|
queries.accessed.set(this, Date.now());
|
||||||
return !!queries.fulfilled.get(this);
|
return queries.fulfilled.get(this) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get fulfilling(): boolean {
|
public get fulfilling(): boolean {
|
||||||
queries.accessed.set(this, Date.now());
|
queries.accessed.set(this, Date.now());
|
||||||
return queries.fulfilling.has(this);
|
return !(queries.fulfilled.get(this) >= fulfillTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get value(): any {
|
public get value(): any {
|
||||||
@@ -348,18 +348,7 @@ export function fetch(
|
|||||||
return queryResponse;
|
return queryResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _fulfill(
|
export function fulfill(accessTimestamp: number): void {
|
||||||
accessTimestamp: number,
|
|
||||||
_fulfillTimestamp: number
|
|
||||||
): Promise<boolean> {
|
|
||||||
let updated = false;
|
|
||||||
|
|
||||||
if (_fulfillTimestamp > fulfillTimestamp) {
|
|
||||||
for (const resource of Object.values(resources))
|
|
||||||
resource.combinedFilter = null;
|
|
||||||
fulfillTimestamp = _fulfillTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allPromises = [];
|
const allPromises = [];
|
||||||
|
|
||||||
for (const [resourceType, resource] of Object.entries(resources)) {
|
for (const [resourceType, resource] of Object.entries(resources)) {
|
||||||
@@ -373,38 +362,32 @@ function _fulfill(
|
|||||||
|
|
||||||
if (!(fulfillTimestamp <= queries.fulfilled.get(queryResponse))) {
|
if (!(fulfillTimestamp <= queries.fulfilled.get(queryResponse))) {
|
||||||
queries.fulfilling.add(queryResponse);
|
queries.fulfilling.add(queryResponse);
|
||||||
|
let filter = queries.filter.get(queryResponse);
|
||||||
|
filter = unpackExpression(filter);
|
||||||
allPromises.push(
|
allPromises.push(
|
||||||
new Promise((resolve, reject) => {
|
xhrRequest({
|
||||||
updated = true;
|
method: "HEAD",
|
||||||
let filter = queries.filter.get(queryResponse);
|
url:
|
||||||
filter = unpackExpression(filter);
|
`api/${resourceType}/?` +
|
||||||
xhrRequest({
|
m.buildQueryString({
|
||||||
method: "HEAD",
|
filter: memoizedStringify(filter),
|
||||||
url:
|
}),
|
||||||
`api/${resourceType}/?` +
|
extract: (xhr) => {
|
||||||
m.buildQueryString({
|
if (xhr.status === 403) throw new Error("Not authorized");
|
||||||
filter: memoizedStringify(filter),
|
if (!xhr.status) {
|
||||||
}),
|
throw new Error("Server is unreachable");
|
||||||
extract: (xhr) => {
|
} else if (xhr.status !== 200) {
|
||||||
if (xhr.status === 403) throw new Error("Not authorized");
|
throw new Error(
|
||||||
if (!xhr.status) {
|
`Unexpected response status code ${xhr.status}`
|
||||||
throw new Error("Server is unreachable");
|
);
|
||||||
} else if (xhr.status !== 200) {
|
}
|
||||||
throw new Error(
|
return +xhr.getResponseHeader("x-total-count");
|
||||||
`Unexpected response status code ${xhr.status}`
|
},
|
||||||
);
|
background: false,
|
||||||
}
|
}).then((c) => {
|
||||||
return +xhr.getResponseHeader("x-total-count");
|
queries.value.set(queryResponse, c);
|
||||||
},
|
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||||
background: true,
|
queries.fulfilling.delete(queryResponse);
|
||||||
})
|
|
||||||
.then((c) => {
|
|
||||||
queries.value.set(queryResponse, c);
|
|
||||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
|
||||||
queries.fulfilling.delete(queryResponse);
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch((err) => reject(err));
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -429,45 +412,39 @@ function _fulfill(
|
|||||||
const limit = queries.limit.get(queryResponse);
|
const limit = queries.limit.get(queryResponse);
|
||||||
const sort = queries.sort.get(queryResponse);
|
const sort = queries.sort.get(queryResponse);
|
||||||
if (limit) {
|
if (limit) {
|
||||||
|
let filter = queries.filter.get(queryResponse);
|
||||||
|
filter = unpackExpression(filter);
|
||||||
allPromises.push(
|
allPromises.push(
|
||||||
new Promise((resolve, reject) => {
|
xhrRequest({
|
||||||
updated = true;
|
method: "GET",
|
||||||
let filter = queries.filter.get(queryResponse);
|
url:
|
||||||
filter = unpackExpression(filter);
|
`api/${resourceType}/?` +
|
||||||
xhrRequest({
|
m.buildQueryString({
|
||||||
method: "GET",
|
filter: memoizedStringify(filter),
|
||||||
url:
|
limit: 1,
|
||||||
`api/${resourceType}/?` +
|
skip: limit - 1,
|
||||||
m.buildQueryString({
|
sort: JSON.stringify(sort),
|
||||||
filter: memoizedStringify(filter),
|
projection: Object.keys(sort).join(","),
|
||||||
limit: 1,
|
}),
|
||||||
skip: limit - 1,
|
background: true,
|
||||||
sort: JSON.stringify(sort),
|
}).then((res) => {
|
||||||
projection: Object.keys(sort).join(","),
|
if ((res as any[]).length) {
|
||||||
}),
|
// Generate bookmark object
|
||||||
})
|
const bm = Object.keys(sort).reduce((b, k) => {
|
||||||
.then((res) => {
|
if (res[0][k] != null) {
|
||||||
if ((res as any[]).length) {
|
if (typeof res[0][k] === "object") {
|
||||||
// Generate bookmark object
|
if (res[0][k].value != null) b[k] = res[0][k].value[0];
|
||||||
const bm = Object.keys(sort).reduce((b, k) => {
|
} else {
|
||||||
if (res[0][k] != null) {
|
b[k] = res[0][k];
|
||||||
if (typeof res[0][k] === "object") {
|
}
|
||||||
if (res[0][k].value != null)
|
|
||||||
b[k] = res[0][k].value[0];
|
|
||||||
} else {
|
|
||||||
b[k] = res[0][k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b;
|
|
||||||
}, {});
|
|
||||||
queries.bookmark.set(queryResponse, bm);
|
|
||||||
} else {
|
|
||||||
queries.bookmark.delete(queryResponse);
|
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
})
|
return b;
|
||||||
.catch(reject);
|
}, {});
|
||||||
|
queries.bookmark.set(queryResponse, bm);
|
||||||
|
} else {
|
||||||
|
queries.bookmark.delete(queryResponse);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -475,31 +452,82 @@ function _fulfill(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
Promise.all(allPromises)
|
||||||
Promise.all(allPromises)
|
.then(() => {
|
||||||
.then(() => {
|
let updated = false;
|
||||||
const allPromises2 = [];
|
const allPromises2 = [];
|
||||||
for (const [resourceType, toFetch] of Object.entries(toFetchAll)) {
|
for (const [resourceType, toFetch] of Object.entries(toFetchAll)) {
|
||||||
let combinedFilter = null;
|
let combinedFilter = null;
|
||||||
|
|
||||||
|
for (const queryResponse of toFetch) {
|
||||||
|
let filter = queries.filter.get(queryResponse);
|
||||||
|
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
||||||
|
const bookmark = queries.bookmark.get(queryResponse);
|
||||||
|
const sort = queries.sort.get(queryResponse);
|
||||||
|
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
||||||
|
combinedFilter = or(combinedFilter, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [union, diff] = unionDiff(
|
||||||
|
resources[resourceType].combinedFilter,
|
||||||
|
combinedFilter
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!diff) {
|
||||||
for (const queryResponse of toFetch) {
|
for (const queryResponse of toFetch) {
|
||||||
let filter = queries.filter.get(queryResponse);
|
let filter = queries.filter.get(queryResponse);
|
||||||
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
||||||
|
const limit = queries.limit.get(queryResponse);
|
||||||
const bookmark = queries.bookmark.get(queryResponse);
|
const bookmark = queries.bookmark.get(queryResponse);
|
||||||
const sort = queries.sort.get(queryResponse);
|
const sort = queries.sort.get(queryResponse);
|
||||||
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
||||||
combinedFilter = or(combinedFilter, filter);
|
|
||||||
|
queries.value.set(
|
||||||
|
queryResponse,
|
||||||
|
findMatches(resourceType, filter, sort, limit)
|
||||||
|
);
|
||||||
|
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||||
|
queries.fulfilling.delete(queryResponse);
|
||||||
|
updated = true;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const [union, diff] = unionDiff(
|
let deleted = new Set<string>();
|
||||||
resources[resourceType].combinedFilter,
|
if (!resources[resourceType].combinedFilter)
|
||||||
combinedFilter
|
deleted = new Set(resources[resourceType].objects.keys());
|
||||||
);
|
|
||||||
|
const combinedFilterDiff = diff;
|
||||||
|
resources[resourceType].combinedFilter = union;
|
||||||
|
|
||||||
|
allPromises2.push(
|
||||||
|
xhrRequest({
|
||||||
|
method: "GET",
|
||||||
|
url:
|
||||||
|
`api/${resourceType}/?` +
|
||||||
|
m.buildQueryString({
|
||||||
|
filter: memoizedStringify(combinedFilterDiff),
|
||||||
|
}),
|
||||||
|
background: false,
|
||||||
|
}).then((res) => {
|
||||||
|
for (const r of res as any[]) {
|
||||||
|
const id =
|
||||||
|
resourceType === "devices"
|
||||||
|
? r["DeviceID.ID"].value[0]
|
||||||
|
: r["_id"];
|
||||||
|
resources[resourceType].objects.set(id, r);
|
||||||
|
deleted.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const d of deleted) {
|
||||||
|
const obj = resources[resourceType].objects.get(d);
|
||||||
|
if (evaluate(combinedFilterDiff, obj, fulfillTimestamp))
|
||||||
|
resources[resourceType].objects.delete(d);
|
||||||
|
}
|
||||||
|
|
||||||
if (!diff) {
|
|
||||||
for (const queryResponse of toFetch) {
|
for (const queryResponse of toFetch) {
|
||||||
let filter = queries.filter.get(queryResponse);
|
let filter = queries.filter.get(queryResponse);
|
||||||
filter = memoizedEvaluate(filter, null, fulfillTimestamp);
|
filter = unpackExpression(filter);
|
||||||
const limit = queries.limit.get(queryResponse);
|
const limit = queries.limit.get(queryResponse);
|
||||||
const bookmark = queries.bookmark.get(queryResponse);
|
const bookmark = queries.bookmark.get(queryResponse);
|
||||||
const sort = queries.sort.get(queryResponse);
|
const sort = queries.sort.get(queryResponse);
|
||||||
@@ -512,87 +540,29 @@ function _fulfill(
|
|||||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||||
queries.fulfilling.delete(queryResponse);
|
queries.fulfilling.delete(queryResponse);
|
||||||
}
|
}
|
||||||
continue;
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
updated = true;
|
if (updated) m.redraw();
|
||||||
let deleted = new Set<string>();
|
return Promise.all(allPromises2);
|
||||||
if (!resources[resourceType].combinedFilter)
|
})
|
||||||
deleted = new Set(resources[resourceType].objects.keys());
|
.catch((err) => {
|
||||||
|
notifications.push("error", err.message);
|
||||||
const combinedFilterDiff = diff;
|
});
|
||||||
resources[resourceType].combinedFilter = union;
|
|
||||||
|
|
||||||
allPromises2.push(
|
|
||||||
new Promise((resolve2, reject2) => {
|
|
||||||
xhrRequest({
|
|
||||||
method: "GET",
|
|
||||||
url:
|
|
||||||
`api/${resourceType}/?` +
|
|
||||||
m.buildQueryString({
|
|
||||||
filter: memoizedStringify(combinedFilterDiff),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
for (const r of res as any[]) {
|
|
||||||
const id =
|
|
||||||
resourceType === "devices"
|
|
||||||
? r["DeviceID.ID"].value[0]
|
|
||||||
: r["_id"];
|
|
||||||
resources[resourceType].objects.set(id, r);
|
|
||||||
deleted.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const d of deleted) {
|
|
||||||
const obj = resources[resourceType].objects.get(d);
|
|
||||||
if (evaluate(combinedFilterDiff, obj, fulfillTimestamp))
|
|
||||||
resources[resourceType].objects.delete(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const queryResponse of toFetch) {
|
|
||||||
let filter = queries.filter.get(queryResponse);
|
|
||||||
filter = unpackExpression(filter);
|
|
||||||
const limit = queries.limit.get(queryResponse);
|
|
||||||
const bookmark = queries.bookmark.get(queryResponse);
|
|
||||||
const sort = queries.sort.get(queryResponse);
|
|
||||||
if (bookmark) filter = limitFilter(filter, sort, bookmark);
|
|
||||||
|
|
||||||
queries.value.set(
|
|
||||||
queryResponse,
|
|
||||||
findMatches(resourceType, filter, sort, limit)
|
|
||||||
);
|
|
||||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
|
||||||
queries.fulfilling.delete(queryResponse);
|
|
||||||
}
|
|
||||||
resolve2();
|
|
||||||
})
|
|
||||||
.catch(reject2);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Promise.all(allPromises2)
|
|
||||||
.then(() => resolve(updated))
|
|
||||||
.catch(reject);
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fulfill(
|
|
||||||
accessTimestamp: number,
|
|
||||||
_fulfillTimestamp: number,
|
|
||||||
callback?: (updated: boolean) => void
|
|
||||||
): void {
|
|
||||||
_fulfill(accessTimestamp, _fulfillTimestamp).then(callback, (err) => {
|
|
||||||
notifications.push("error", err.message);
|
|
||||||
if (callback) callback(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTimestamp(): number {
|
export function getTimestamp(): number {
|
||||||
return fulfillTimestamp;
|
return fulfillTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setTimestamp(t: number): void {
|
||||||
|
if (t > fulfillTimestamp) {
|
||||||
|
fulfillTimestamp = t;
|
||||||
|
for (const resource of Object.values(resources))
|
||||||
|
resource.combinedFilter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function postTasks(
|
export function postTasks(
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
tasks: QueueTask[]
|
tasks: QueueTask[]
|
||||||
|
|||||||
+13
-9
@@ -85,12 +85,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.resourceExists("users", id)
|
.resourceExists("users", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "User already exists" });
|
return void resolve({ _id: "User already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists && !isNew) {
|
if (!exists && !isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "User does not exist" });
|
return void resolve({ _id: "User does not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +102,13 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.changePassword(id, password)
|
.changePassword(id, password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "User created");
|
notifications.push("success", "User created");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
} else {
|
} else {
|
||||||
notifications.push("success", "User updated");
|
notifications.push("success", "User updated");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -120,11 +120,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.deleteResource("users", object["_id"])
|
.deleteResource("users", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "User deleted");
|
notifications.push("success", "User deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -387,11 +387,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} users deleted`
|
`${res.length} users deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -409,7 +409,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing users"),
|
m("h1", "Listing users"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [users, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.resourceExists("virtualParameters", id)
|
.resourceExists("virtualParameters", id)
|
||||||
.then((exists) => {
|
.then((exists) => {
|
||||||
if (exists && isNew) {
|
if (exists && isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Virtual parameter already exists" });
|
return void resolve({ _id: "Virtual parameter already exists" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exists && !isNew) {
|
if (!exists && !isNew) {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
return void resolve({ _id: "Virtual parameter does not exist" });
|
return void resolve({ _id: "Virtual parameter does not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
"success",
|
"success",
|
||||||
`Virtual parameter ${exists ? "updated" : "created"}`
|
`Virtual parameter ${exists ? "updated" : "created"}`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -99,11 +99,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
|||||||
.deleteResource("virtualParameters", object["_id"])
|
.deleteResource("virtualParameters", object["_id"])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
notifications.push("success", "Virtual parameter deleted");
|
notifications.push("success", "Virtual parameter deleted");
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -332,11 +332,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
"success",
|
"success",
|
||||||
`${res.length} virtual parameters deleted`
|
`${res.length} virtual parameters deleted`
|
||||||
);
|
);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
notifications.push("error", err.message);
|
notifications.push("error", err.message);
|
||||||
store.fulfill(0, Date.now());
|
store.setTimestamp(Date.now());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -354,7 +354,11 @@ export const component: ClosureComponent = (): Component => {
|
|||||||
return [
|
return [
|
||||||
m("h1", "Listing virtual parameters"),
|
m("h1", "Listing virtual parameters"),
|
||||||
m(filterComponent, filterAttrs),
|
m(filterComponent, filterAttrs),
|
||||||
m(indexTableComponent, attrs),
|
m(
|
||||||
|
"loading",
|
||||||
|
{ queries: [virtualParameters, count] },
|
||||||
|
m(indexTableComponent, attrs)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user