Display spinner animation during loading
This commit is contained in:
1 parent
4871613c67
commit
688811317c
23 files changed
+295
-155
No files matched your search
@@ -64,39 +64,27 @@ const adminPages = [
|
||||
|
||||
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 {
|
||||
const component: RouteResolver = {
|
||||
render: () => {
|
||||
lastRenderTimestamp = Date.now();
|
||||
const lastRenderTimestamp = Date.now();
|
||||
let p;
|
||||
if (state && state.error) p = m(errorPage.component, state);
|
||||
else p = m(contextifyComponent(page.component), state);
|
||||
fulfill();
|
||||
const attrs = {};
|
||||
attrs["page"] = pageName;
|
||||
attrs["oncreate"] = attrs["onupdate"] = () => {
|
||||
store.fulfill(lastRenderTimestamp);
|
||||
};
|
||||
return m(layout, attrs, p);
|
||||
},
|
||||
onmatch: null,
|
||||
};
|
||||
|
||||
component.onmatch = (args, requestedPath) => {
|
||||
pageVisitTimestamp = Date.now();
|
||||
store.setTimestamp(Date.now());
|
||||
if (!page.init) {
|
||||
state = null;
|
||||
fulfill();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -107,7 +95,6 @@ function pagify(pageName, page): RouteResolver {
|
||||
if (!st) return void m.route.set("/");
|
||||
state = st;
|
||||
resolve();
|
||||
fulfill();
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!window.username && err.message.indexOf("authorized") >= 0) {
|
||||
|
||||
@@ -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 loading from "./components/loading";
|
||||
|
||||
const comps = {
|
||||
parameter,
|
||||
@@ -54,6 +55,7 @@ const comps = {
|
||||
ping,
|
||||
"device-link": deviceLink,
|
||||
"long-text": longTextComponent,
|
||||
loading: loading,
|
||||
};
|
||||
|
||||
const contextifiedComponents = new WeakMap<ComponentTypes, ComponentTypes>();
|
||||
|
||||
@@ -171,6 +171,9 @@ const component: ClosureComponent = (): Component => {
|
||||
});
|
||||
|
||||
return m(
|
||||
"loading",
|
||||
{ queries: [vnode.attrs["deviceQuery"]] },
|
||||
m(
|
||||
".all-parameters",
|
||||
m(
|
||||
"a.download-csv",
|
||||
@@ -192,6 +195,7 @@ const component: ClosureComponent = (): Component => {
|
||||
`Displaying ${filteredKeys.length} out of ${count} parameters.`
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -67,12 +67,12 @@ const component: ClosureComponent = (): Component => {
|
||||
.deleteResource("faults", f["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Fault deleted");
|
||||
store.fulfill(Date.now(), Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
m.redraw();
|
||||
})
|
||||
.catch((err) => {
|
||||
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(
|
||||
"tr",
|
||||
{
|
||||
oncreate: (vn) => {
|
||||
(vn.dom as HTMLElement).style.display = (p as VnodeDOM).dom
|
||||
? ""
|
||||
: "none";
|
||||
},
|
||||
onupdate: (vn) => {
|
||||
(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;
|
||||
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(() => {
|
||||
e.target.disabled = false;
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
e.target.disabled = false;
|
||||
|
||||
@@ -64,7 +64,7 @@ const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${deviceId}: Tags updated`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
e.target.disabled = false;
|
||||
@@ -99,7 +99,7 @@ const component: ClosureComponent = (): Component => {
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
e.target.disabled = false;
|
||||
|
||||
+11
-7
@@ -60,12 +60,12 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
||||
.resourceExists("config", id)
|
||||
.then((exists) => {
|
||||
if (exists && isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Config already exists" });
|
||||
}
|
||||
|
||||
if (!exists && !isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Config does not exist" });
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
||||
"success",
|
||||
`Config ${exists ? "updated" : "created"}`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
@@ -87,11 +87,11 @@ function putActionHandler(action, _object, isNew?): Promise<ValidationErrors> {
|
||||
.deleteResource("config", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Config deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
@@ -337,11 +337,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
);
|
||||
overlay.close(cb);
|
||||
}
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
},
|
||||
onError: (err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
overlay.close(cb);
|
||||
},
|
||||
},
|
||||
@@ -365,6 +365,9 @@ export const component: ClosureComponent = (): Component => {
|
||||
|
||||
return [
|
||||
m("h1", "Listing config"),
|
||||
m(
|
||||
"loading",
|
||||
{ queries: [confs] },
|
||||
m(
|
||||
".all-parameters",
|
||||
search,
|
||||
@@ -374,6 +377,7 @@ export const component: ClosureComponent = (): Component => {
|
||||
renderTable(confs, vnode.state["searchString"])
|
||||
),
|
||||
m(".actions-bar", [newConfig].concat(subs))
|
||||
)
|
||||
),
|
||||
];
|
||||
},
|
||||
|
||||
+28
-1
@@ -347,7 +347,6 @@ body {
|
||||
table.table {
|
||||
border-spacing: 0;
|
||||
text-align: left;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
|
||||
& table.table {
|
||||
@@ -753,3 +752,31 @@ span.parameter-value {
|
||||
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) => {
|
||||
document.title = `${vnode.attrs["deviceId"]} - Devices - GenieACS`;
|
||||
|
||||
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]).value;
|
||||
if (!dev.length) return "Loading";
|
||||
const dev = store.fetch("devices", vnode.attrs["deviceFilter"]);
|
||||
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 cmps = [];
|
||||
|
||||
for (const c of Object.values(conf))
|
||||
cmps.push(m.context({ device: dev[0] }, c["type"], c));
|
||||
for (const c of Object.values(conf)) {
|
||||
cmps.push(
|
||||
m.context({ device: dev.value[0], deviceQuery: dev }, c["type"], c)
|
||||
);
|
||||
}
|
||||
|
||||
return [m("h1", vnode.attrs["deviceId"]), cmps];
|
||||
},
|
||||
|
||||
+10
-10
@@ -154,14 +154,14 @@ function renderActions(selected: Set<string>): Children {
|
||||
.deleteResource("devices", id)
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Deleted`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
if (--counter === 0) store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
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"
|
||||
@@ -186,14 +186,14 @@ function renderActions(selected: Set<string>): Children {
|
||||
.updateTags(id, { [tag]: true })
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Tags updated`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
if (--counter === 0) store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
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"
|
||||
@@ -220,14 +220,14 @@ function renderActions(selected: Set<string>): Children {
|
||||
.updateTags(id, { [tag]: false })
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Tags updated`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
if (--counter === 0) store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
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"
|
||||
@@ -336,7 +336,7 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing devices"),
|
||||
m(filterComponent, filterAttrs),
|
||||
m(indexTableComponent, attrs),
|
||||
m("loading", { queries: [devs, count] }, m(indexTableComponent, attrs)),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -465,7 +465,7 @@ const component: ClosureComponent = (): Component => {
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
|
||||
+7
-3
@@ -187,11 +187,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} faults deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -208,7 +208,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing faults"),
|
||||
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)
|
||||
.then((exists) => {
|
||||
if (exists) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ file: "File already exists" });
|
||||
}
|
||||
const headers = Object.assign(
|
||||
@@ -117,7 +117,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
||||
"success",
|
||||
`File ${exists ? "updated" : "created"}`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
@@ -288,11 +288,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} files deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -310,7 +310,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing files"),
|
||||
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)
|
||||
.then((exists) => {
|
||||
if (exists) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Permission already exists" });
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
||||
.putResource("permissions", id, object)
|
||||
.then(() => {
|
||||
notifications.push("success", "Permission created");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
@@ -136,11 +136,11 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
|
||||
.deleteResource("permissions", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Permission deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
@@ -351,11 +351,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} permissions deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -373,7 +373,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing permissions"),
|
||||
m(filterComponent, filterAttrs),
|
||||
m(indexTableComponent, attrs),
|
||||
m(
|
||||
"loading",
|
||||
{ queries: [permissions, count] },
|
||||
m(indexTableComponent, attrs)
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -116,7 +116,12 @@ function drawChart(chartData): Children {
|
||||
|
||||
legend.push(m("span.legend-total", `Total: ${total}`));
|
||||
|
||||
return m("div", { class: "pie-chart" }, [
|
||||
return m(
|
||||
"loading",
|
||||
{
|
||||
queries: Object.values(chartData.slices).map((s) => s["count"]),
|
||||
},
|
||||
m("div", { class: "pie-chart" }, [
|
||||
m(
|
||||
"svg",
|
||||
{
|
||||
@@ -130,7 +135,8 @@ function drawChart(chartData): Children {
|
||||
paths.concat(links)
|
||||
),
|
||||
m(".legend", legend),
|
||||
]);
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
const component: ClosureComponent = (): Component => {
|
||||
|
||||
+12
-8
@@ -86,12 +86,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.resourceExists("presets", id)
|
||||
.then((exists) => {
|
||||
if (exists && isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Preset already exists" });
|
||||
}
|
||||
|
||||
if (!exists && !isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Preset does not exist" });
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
"success",
|
||||
`Preset ${exists ? "updated" : "created"}`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
@@ -113,12 +113,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.deleteResource("presets", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Preset deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
} else {
|
||||
reject(new Error("Undefined action"));
|
||||
@@ -413,11 +413,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} presets deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -435,7 +435,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing presets"),
|
||||
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)
|
||||
.then((exists) => {
|
||||
if (exists && isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Provision already exists" });
|
||||
}
|
||||
|
||||
if (!exists && !isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Provision does not exist" });
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
"success",
|
||||
`Provision ${exists ? "updated" : "created"}`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -99,11 +99,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.deleteResource("provisions", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Provision deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
@@ -332,11 +332,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} provisions deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -354,7 +354,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing provisions"),
|
||||
m(filterComponent, filterAttrs),
|
||||
m(indexTableComponent, attrs),
|
||||
m(
|
||||
"loading",
|
||||
{ queries: [provisions, count] },
|
||||
m(indexTableComponent, attrs)
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
+26
-56
@@ -76,14 +76,14 @@ for (const r of [
|
||||
}
|
||||
|
||||
class QueryResponse {
|
||||
public get fulfilled(): boolean {
|
||||
public get fulfilled(): number {
|
||||
queries.accessed.set(this, Date.now());
|
||||
return !!queries.fulfilled.get(this);
|
||||
return queries.fulfilled.get(this) || 0;
|
||||
}
|
||||
|
||||
public get fulfilling(): boolean {
|
||||
queries.accessed.set(this, Date.now());
|
||||
return queries.fulfilling.has(this);
|
||||
return !(queries.fulfilled.get(this) >= fulfillTimestamp);
|
||||
}
|
||||
|
||||
public get value(): any {
|
||||
@@ -348,18 +348,7 @@ export function fetch(
|
||||
return queryResponse;
|
||||
}
|
||||
|
||||
function _fulfill(
|
||||
accessTimestamp: number,
|
||||
_fulfillTimestamp: number
|
||||
): Promise<boolean> {
|
||||
let updated = false;
|
||||
|
||||
if (_fulfillTimestamp > fulfillTimestamp) {
|
||||
for (const resource of Object.values(resources))
|
||||
resource.combinedFilter = null;
|
||||
fulfillTimestamp = _fulfillTimestamp;
|
||||
}
|
||||
|
||||
export function fulfill(accessTimestamp: number): void {
|
||||
const allPromises = [];
|
||||
|
||||
for (const [resourceType, resource] of Object.entries(resources)) {
|
||||
@@ -373,11 +362,9 @@ function _fulfill(
|
||||
|
||||
if (!(fulfillTimestamp <= queries.fulfilled.get(queryResponse))) {
|
||||
queries.fulfilling.add(queryResponse);
|
||||
allPromises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
updated = true;
|
||||
let filter = queries.filter.get(queryResponse);
|
||||
filter = unpackExpression(filter);
|
||||
allPromises.push(
|
||||
xhrRequest({
|
||||
method: "HEAD",
|
||||
url:
|
||||
@@ -396,15 +383,11 @@ function _fulfill(
|
||||
}
|
||||
return +xhr.getResponseHeader("x-total-count");
|
||||
},
|
||||
background: true,
|
||||
})
|
||||
.then((c) => {
|
||||
background: false,
|
||||
}).then((c) => {
|
||||
queries.value.set(queryResponse, c);
|
||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||
queries.fulfilling.delete(queryResponse);
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -429,11 +412,9 @@ function _fulfill(
|
||||
const limit = queries.limit.get(queryResponse);
|
||||
const sort = queries.sort.get(queryResponse);
|
||||
if (limit) {
|
||||
allPromises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
updated = true;
|
||||
let filter = queries.filter.get(queryResponse);
|
||||
filter = unpackExpression(filter);
|
||||
allPromises.push(
|
||||
xhrRequest({
|
||||
method: "GET",
|
||||
url:
|
||||
@@ -445,15 +426,14 @@ function _fulfill(
|
||||
sort: JSON.stringify(sort),
|
||||
projection: Object.keys(sort).join(","),
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
background: true,
|
||||
}).then((res) => {
|
||||
if ((res as any[]).length) {
|
||||
// Generate bookmark object
|
||||
const bm = Object.keys(sort).reduce((b, k) => {
|
||||
if (res[0][k] != null) {
|
||||
if (typeof res[0][k] === "object") {
|
||||
if (res[0][k].value != null)
|
||||
b[k] = res[0][k].value[0];
|
||||
if (res[0][k].value != null) b[k] = res[0][k].value[0];
|
||||
} else {
|
||||
b[k] = res[0][k];
|
||||
}
|
||||
@@ -465,9 +445,6 @@ function _fulfill(
|
||||
} else {
|
||||
queries.bookmark.delete(queryResponse);
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -475,9 +452,9 @@ function _fulfill(
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Promise.all(allPromises)
|
||||
.then(() => {
|
||||
let updated = false;
|
||||
const allPromises2 = [];
|
||||
for (const [resourceType, toFetch] of Object.entries(toFetchAll)) {
|
||||
let combinedFilter = null;
|
||||
@@ -511,11 +488,11 @@ function _fulfill(
|
||||
);
|
||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||
queries.fulfilling.delete(queryResponse);
|
||||
updated = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
updated = true;
|
||||
let deleted = new Set<string>();
|
||||
if (!resources[resourceType].combinedFilter)
|
||||
deleted = new Set(resources[resourceType].objects.keys());
|
||||
@@ -524,7 +501,6 @@ function _fulfill(
|
||||
resources[resourceType].combinedFilter = union;
|
||||
|
||||
allPromises2.push(
|
||||
new Promise((resolve2, reject2) => {
|
||||
xhrRequest({
|
||||
method: "GET",
|
||||
url:
|
||||
@@ -532,8 +508,8 @@ function _fulfill(
|
||||
m.buildQueryString({
|
||||
filter: memoizedStringify(combinedFilterDiff),
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
background: false,
|
||||
}).then((res) => {
|
||||
for (const r of res as any[]) {
|
||||
const id =
|
||||
resourceType === "devices"
|
||||
@@ -564,28 +540,14 @@ function _fulfill(
|
||||
queries.fulfilled.set(queryResponse, fulfillTimestamp);
|
||||
queries.fulfilling.delete(queryResponse);
|
||||
}
|
||||
resolve2();
|
||||
})
|
||||
.catch(reject2);
|
||||
})
|
||||
);
|
||||
}
|
||||
Promise.all(allPromises2)
|
||||
.then(() => resolve(updated))
|
||||
.catch(reject);
|
||||
if (updated) m.redraw();
|
||||
return Promise.all(allPromises2);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
export function fulfill(
|
||||
accessTimestamp: number,
|
||||
_fulfillTimestamp: number,
|
||||
callback?: (updated: boolean) => void
|
||||
): void {
|
||||
_fulfill(accessTimestamp, _fulfillTimestamp).then(callback, (err) => {
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
if (callback) callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -593,6 +555,14 @@ export function getTimestamp(): number {
|
||||
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(
|
||||
deviceId: string,
|
||||
tasks: QueueTask[]
|
||||
|
||||
+13
-9
@@ -85,12 +85,12 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.resourceExists("users", id)
|
||||
.then((exists) => {
|
||||
if (exists && isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "User already exists" });
|
||||
}
|
||||
|
||||
if (!exists && !isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "User does not exist" });
|
||||
}
|
||||
|
||||
@@ -102,13 +102,13 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.changePassword(id, password)
|
||||
.then(() => {
|
||||
notifications.push("success", "User created");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
} else {
|
||||
notifications.push("success", "User updated");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
@@ -120,11 +120,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.deleteResource("users", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "User deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
@@ -387,11 +387,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} users deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -409,7 +409,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing users"),
|
||||
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)
|
||||
.then((exists) => {
|
||||
if (exists && isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Virtual parameter already exists" });
|
||||
}
|
||||
|
||||
if (!exists && !isNew) {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
return void resolve({ _id: "Virtual parameter does not exist" });
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
"success",
|
||||
`Virtual parameter ${exists ? "updated" : "created"}`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -99,11 +99,11 @@ function putActionHandler(action, _object, isNew): Promise<ValidationErrors> {
|
||||
.deleteResource("virtualParameters", object["_id"])
|
||||
.then(() => {
|
||||
notifications.push("success", "Virtual parameter deleted");
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
@@ -332,11 +332,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
"success",
|
||||
`${res.length} virtual parameters deleted`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
})
|
||||
.catch((err) => {
|
||||
notifications.push("error", err.message);
|
||||
store.fulfill(0, Date.now());
|
||||
store.setTimestamp(Date.now());
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -354,7 +354,11 @@ export const component: ClosureComponent = (): Component => {
|
||||
return [
|
||||
m("h1", "Listing virtual parameters"),
|
||||
m(filterComponent, filterAttrs),
|
||||
m(indexTableComponent, attrs),
|
||||
m(
|
||||
"loading",
|
||||
{ queries: [virtualParameters, count] },
|
||||
m(indexTableComponent, attrs)
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user