Implement authentication and access control
This commit is contained in:
1 parent
e1bdcda191
commit
327cb78f1a
27 files changed
+600
-167
No files matched your search
+10
-4
@@ -4,11 +4,15 @@ import m from "mithril";
|
||||
|
||||
import layout from "./layout";
|
||||
import * as store from "./store";
|
||||
import * as loginPage from "./login-page";
|
||||
import * as overviewPage from "./overview-page";
|
||||
import * as devicesPage from "./devices-page";
|
||||
import * as devicePage from "./device-page";
|
||||
import * as errorPage from "./error-page";
|
||||
import * as faultsPage from "./faults-page";
|
||||
import Authorizer from "../common/authorizer";
|
||||
|
||||
window.authorizer = new Authorizer(window.permissionSets);
|
||||
|
||||
let state;
|
||||
|
||||
@@ -29,17 +33,18 @@ function pagify(pageName, page) {
|
||||
const component = {
|
||||
render: () => {
|
||||
lastRenderTimestamp = Date.now();
|
||||
let c;
|
||||
if (state && state.error) c = m(errorPage.component, state);
|
||||
else c = m(layout, { page: pageName }, m(page.component, state));
|
||||
let p;
|
||||
if (state && state.error) p = m(errorPage.component, state);
|
||||
else p = m(page.component, state);
|
||||
fulfill();
|
||||
return c;
|
||||
return m(layout, { page: pageName }, p);
|
||||
}
|
||||
};
|
||||
|
||||
component.onmatch = args => {
|
||||
pageVisitTimestamp = Date.now();
|
||||
if (!page.init) {
|
||||
state = null;
|
||||
fulfill();
|
||||
return;
|
||||
}
|
||||
@@ -64,6 +69,7 @@ function pagify(pageName, page) {
|
||||
}
|
||||
|
||||
m.route(document.body, "/overview", {
|
||||
"/login": pagify("login", loginPage),
|
||||
"/overview": pagify("overview", overviewPage),
|
||||
"/devices": pagify("devices", devicesPage),
|
||||
"/devices/:id": pagify("devices", devicePage),
|
||||
|
||||
@@ -50,11 +50,12 @@ const component = {
|
||||
title: "Delete device",
|
||||
onclick: () => {
|
||||
if (!confirm("Deleting this device. Are you sure?")) return;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
|
||||
store
|
||||
.deleteResource("devices", device["DeviceID.ID"].value[0])
|
||||
.deleteResource("devices", deviceId)
|
||||
.then(() => {
|
||||
notifications.push("success", "Device deleted");
|
||||
notifications.push("success", `${deviceId}: Device deleted`);
|
||||
m.route.set("/devices");
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@@ -31,23 +31,33 @@ const component = {
|
||||
};
|
||||
|
||||
taskQueue
|
||||
.commit([task], (deviceId, connectionRequestStatus, tasks2) => {
|
||||
for (let t of tasks2)
|
||||
if (t.status === "stale") taskQueue.deleteTask(t);
|
||||
if (connectionRequestStatus !== "OK")
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
else if (tasks2[0].status === "stale")
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
);
|
||||
else if (tasks2[0].status === "fault")
|
||||
notifications.push("error", `${deviceId}: Refresh faulted`);
|
||||
else notifications.push("success", `${deviceId}: Summoned`);
|
||||
})
|
||||
.commit(
|
||||
[task],
|
||||
(deviceId, err, connectionRequestStatus, tasks2) => {
|
||||
if (err)
|
||||
return notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${err.message}`
|
||||
);
|
||||
|
||||
for (let t of tasks2)
|
||||
if (t.status === "stale") taskQueue.deleteTask(t);
|
||||
|
||||
if (connectionRequestStatus !== "OK")
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
else if (tasks2[0].status === "stale")
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
);
|
||||
else if (tasks2[0].status === "fault")
|
||||
notifications.push("error", `${deviceId}: Refresh faulted`);
|
||||
else notifications.push("success", `${deviceId}: Summoned`);
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
store.fulfill(0, Date.now());
|
||||
|
||||
@@ -29,14 +29,18 @@ const component = {
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
store
|
||||
.updateTags(device["DeviceID.ID"].value[0], { [tag]: false })
|
||||
.updateTags(deviceId, { [tag]: false })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("success", `Tag '${tag}' unassigned`);
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => notifications.push("error", err.message));
|
||||
.catch(err => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
"✕"
|
||||
@@ -50,6 +54,7 @@ const component = {
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
const deviceId = device["DeviceID.ID"].value[0];
|
||||
const tag = prompt(`Enter tag to assign to device:`);
|
||||
if (!tag) {
|
||||
e.target.disabled = false;
|
||||
@@ -57,13 +62,16 @@ const component = {
|
||||
}
|
||||
|
||||
store
|
||||
.updateTags(device["DeviceID.ID"].value[0], { [tag]: true })
|
||||
.updateTags(deviceId, { [tag]: true })
|
||||
.then(() => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("success", `Tag '${tag}' assigned`);
|
||||
notifications.push("success", `${deviceId}: Tags updated`);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => notifications.push("error", err.message));
|
||||
.catch(err => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("error", `${deviceId}: ${err.message}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
"🞢"
|
||||
|
||||
@@ -229,11 +229,17 @@ i {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .logo {
|
||||
margin: 10px;
|
||||
display: inline;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
& > .user-menu {
|
||||
float: right;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
@@ -308,6 +314,10 @@ h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
p.error {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
/* Remove button padding in FF */
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
|
||||
@@ -8,11 +8,14 @@ import * as store from "./store";
|
||||
import * as components from "./components";
|
||||
|
||||
function init(args) {
|
||||
return new Promise(resolve => {
|
||||
resolve({
|
||||
deviceId: args.id,
|
||||
deviceFilter: new Filter(["=", "DeviceID.ID", args.id])
|
||||
});
|
||||
if (!window.authorizer.hasAccess("devices", 2))
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
);
|
||||
|
||||
return Promise.resolve({
|
||||
deviceId: args.id,
|
||||
deviceFilter: new Filter(["=", "DeviceID.ID", args.id])
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+49
-35
@@ -12,7 +12,10 @@ import * as taskQueue from "./task-queue";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
function init(args) {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.authorizer.hasAccess("devices", 2))
|
||||
return reject(new Error("You are not authorized to view this page"));
|
||||
|
||||
let filter = new Filter(args.filter);
|
||||
let indexParameters = Object.values(config.get("ui.index")).map(p =>
|
||||
Object.assign({}, p, {
|
||||
@@ -177,14 +180,21 @@ function renderActions(selected) {
|
||||
const ids = Array.from(selected);
|
||||
if (!confirm(`Deleting ${ids.length} devices. Are you sure?`)) return;
|
||||
|
||||
Promise.all(ids.map(id => store.deleteResource("devices", id)))
|
||||
.then(() => {
|
||||
notifications.push("success", `${ids.size} devices deleted`);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
});
|
||||
let counter = 1;
|
||||
for (let id of ids) {
|
||||
++counter;
|
||||
store
|
||||
.deleteResource("devices", id)
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Deleted`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", `${id}: ${err.message}`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
}
|
||||
},
|
||||
"Delete"
|
||||
@@ -202,19 +212,21 @@ function renderActions(selected) {
|
||||
const tag = prompt(`Enter tag to assign to ${ids.length} devices:`);
|
||||
if (!tag) return;
|
||||
|
||||
Promise.all(
|
||||
ids.map(id => {
|
||||
store.updateTags(id, { [tag]: true });
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
notifications.push(
|
||||
"success",
|
||||
`Tag '${tag}' assigned to ${ids.length} devices`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => notifications.push("error", err.message));
|
||||
let counter = 1;
|
||||
for (let id of ids) {
|
||||
++counter;
|
||||
store
|
||||
.updateTags(id, { [tag]: true })
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Tags updated`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", `${id}: ${err.message}`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
}
|
||||
},
|
||||
"Tag"
|
||||
@@ -234,19 +246,21 @@ function renderActions(selected) {
|
||||
);
|
||||
if (!tag) return;
|
||||
|
||||
Promise.all(
|
||||
ids.map(id => {
|
||||
store.updateTags(id, { [tag]: false });
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
notifications.push(
|
||||
"success",
|
||||
`Tag '${tag}' unassigned from ${ids.length} devices`
|
||||
);
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => notifications.push("error", err.message));
|
||||
let counter = 1;
|
||||
for (let id of ids) {
|
||||
++counter;
|
||||
store
|
||||
.updateTags(id, { [tag]: false })
|
||||
.then(() => {
|
||||
notifications.push("success", `${id}: Tags updated`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", `${id}: ${err.message}`);
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
});
|
||||
}
|
||||
if (--counter === 0) store.fulfill(0, Date.now());
|
||||
}
|
||||
},
|
||||
"Untag"
|
||||
|
||||
+30
-25
@@ -243,41 +243,46 @@ const component = {
|
||||
t => t.status === "queued"
|
||||
);
|
||||
taskQueue
|
||||
.commit(tasks, (deviceId, connectionRequestStatus, tasks2) => {
|
||||
if (connectionRequestStatus !== "OK") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let t of tasks2)
|
||||
if (t.status === "stale") {
|
||||
notifications.push(
|
||||
.commit(
|
||||
tasks,
|
||||
(deviceId, err, connectionRequestStatus, tasks2) => {
|
||||
if (err)
|
||||
return notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
`${deviceId}: ${err.message}`
|
||||
);
|
||||
return;
|
||||
} else if (t.status === "fault") {
|
||||
|
||||
if (connectionRequestStatus !== "OK") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: Task(s) faulted`
|
||||
`${deviceId}: ${connectionRequestStatus}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.push(
|
||||
"success",
|
||||
`${deviceId}: Task(s) committed`
|
||||
);
|
||||
})
|
||||
for (let t of tasks2)
|
||||
if (t.status === "stale") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: No contact from device`
|
||||
);
|
||||
return;
|
||||
} else if (t.status === "fault") {
|
||||
notifications.push(
|
||||
"error",
|
||||
`${deviceId}: Task(s) faulted`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.push(
|
||||
"success",
|
||||
`${deviceId}: Task(s) committed`
|
||||
);
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
store.fulfill(0, Date.now());
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
const component = {
|
||||
view: function(vnode) {
|
||||
document.title = "Error! - GenieACS";
|
||||
return vnode.attrs.error;
|
||||
return m("p.error", vnode.attrs.error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,10 +9,13 @@ import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
function init(args) {
|
||||
return new Promise(resolve => {
|
||||
let filter = new Filter(args.filter);
|
||||
resolve({ filter: filter });
|
||||
});
|
||||
if (!window.authorizer.hasAccess("devices", 2))
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
);
|
||||
|
||||
let filter = new Filter(args.filter);
|
||||
return Promise.resolve({ filter: filter });
|
||||
}
|
||||
|
||||
function renderTable(faultsResponse, total, selected, showMoreCallback) {
|
||||
|
||||
@@ -4,12 +4,14 @@ import m from "mithril";
|
||||
|
||||
import menu from "./menu";
|
||||
import drawerComponent from "./drawer-component";
|
||||
import userMenu from "./user-menu";
|
||||
|
||||
const layout = {
|
||||
view: vnode => {
|
||||
return [
|
||||
m("#header", [
|
||||
m("img.logo", { src: "logo.svg" }),
|
||||
m(userMenu),
|
||||
m(menu, { page: vnode.attrs.page }),
|
||||
m(drawerComponent)
|
||||
]),
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
function init(args) {
|
||||
return Promise.resolve(args);
|
||||
}
|
||||
|
||||
const component = {
|
||||
view: vnode => {
|
||||
if (window.username) m.route.set(vnode.attrs.continue || "/");
|
||||
|
||||
document.title = "Login - GenieACS";
|
||||
return [
|
||||
m("h1", "Log in"),
|
||||
m(
|
||||
"form",
|
||||
m(
|
||||
"p",
|
||||
m("label", { for: "username" }, "Username"),
|
||||
m("input", {
|
||||
name: "username",
|
||||
type: "text",
|
||||
value: vnode.state.username,
|
||||
oncreate: vnode2 => {
|
||||
vnode2.dom.focus();
|
||||
},
|
||||
oninput: m.withAttr("value", v => (vnode.state.username = v))
|
||||
})
|
||||
),
|
||||
m(
|
||||
"p",
|
||||
m("label", { for: "password" }, "Password"),
|
||||
m("input", {
|
||||
name: "password",
|
||||
type: "password",
|
||||
value: vnode.state.password,
|
||||
oninput: m.withAttr("value", v => (vnode.state.password = v))
|
||||
})
|
||||
),
|
||||
m(
|
||||
"p",
|
||||
m(
|
||||
"button.primary",
|
||||
{
|
||||
type: "submit",
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
store
|
||||
.logIn(vnode.state.username, vnode.state.password)
|
||||
.then(() => {
|
||||
location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
notifications.push("error", err.message);
|
||||
e.target.disabled = false;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
"Login"
|
||||
)
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
export { init, component };
|
||||
+16
-7
@@ -6,26 +6,35 @@ const menu = {
|
||||
view: vnode => {
|
||||
const active = { [vnode.attrs.page]: "active" };
|
||||
|
||||
return m(
|
||||
"nav",
|
||||
m("ul", [
|
||||
let tabs = [];
|
||||
if (window.authorizer.hasAccess("devices", 1))
|
||||
tabs.push(
|
||||
m(
|
||||
"li",
|
||||
{ class: active["overview"] },
|
||||
m("a", { href: "#!/overview" }, "Overview")
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if (window.authorizer.hasAccess("devices", 2))
|
||||
tabs.push(
|
||||
m(
|
||||
"li",
|
||||
{ class: active["devices"] },
|
||||
m("a", { href: "#!/devices" }, "Devices")
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if (window.authorizer.hasAccess("faults", 2))
|
||||
tabs.push(
|
||||
m(
|
||||
"li",
|
||||
{ class: active["faults"] },
|
||||
m("a", { href: "#!/faults" }, "Faults")
|
||||
)
|
||||
])
|
||||
);
|
||||
);
|
||||
|
||||
return m("nav", m("ul", tabs));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,9 +24,12 @@ function queryCharts(charts) {
|
||||
}
|
||||
|
||||
const init = function() {
|
||||
return new Promise(resolve => {
|
||||
resolve({ charts: queryCharts(CHARTS) });
|
||||
});
|
||||
if (!window.authorizer.hasAccess("devices", 1))
|
||||
return Promise.reject(
|
||||
new Error("You are not authorized to view this page")
|
||||
);
|
||||
|
||||
return Promise.resolve({ charts: queryCharts(CHARTS) });
|
||||
};
|
||||
|
||||
const component = {
|
||||
|
||||
+44
-36
@@ -323,43 +323,33 @@ function getTimestamp() {
|
||||
return fulfillTimestamp;
|
||||
}
|
||||
|
||||
function postTasks(tasks, callback) {
|
||||
function postTasks(deviceId, tasks) {
|
||||
for (let t of tasks) {
|
||||
t.status = "pending";
|
||||
t.device = deviceId;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let devices = {};
|
||||
for (let t of tasks) {
|
||||
t.status = "pending";
|
||||
devices[t.device] = devices[t.device] || [];
|
||||
devices[t.device].push(t);
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let [deviceId, tasks2] of Object.entries(devices))
|
||||
promises.push(
|
||||
m.request({
|
||||
method: "POST",
|
||||
url: `/api/devices/${encodeURIComponent(deviceId)}/tasks`,
|
||||
data: tasks2,
|
||||
extract: xhr => {
|
||||
const connectionRequestStatus = xhr.getResponseHeader(
|
||||
"Connection-Request"
|
||||
);
|
||||
let st = JSON.parse(xhr.response);
|
||||
for (let [i, t] of st.entries()) {
|
||||
tasks2[i]._id = t._id;
|
||||
tasks2[i].status = t.status;
|
||||
tasks2[i].fault = t.fault;
|
||||
}
|
||||
if (callback) callback(deviceId, connectionRequestStatus, tasks2);
|
||||
m
|
||||
.request({
|
||||
method: "POST",
|
||||
url: `/api/devices/${encodeURIComponent(deviceId)}/tasks`,
|
||||
data: tasks,
|
||||
extract: xhr => {
|
||||
if (xhr.status !== 200) throw new Error(xhr.response);
|
||||
const connectionRequestStatus = xhr.getResponseHeader(
|
||||
"Connection-Request"
|
||||
);
|
||||
let st = JSON.parse(xhr.response);
|
||||
for (let [i, t] of st.entries()) {
|
||||
tasks[i]._id = t._id;
|
||||
tasks[i].status = t.status;
|
||||
tasks[i].fault = t.fault;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Promise.all(promises)
|
||||
.then(() => resolve(tasks))
|
||||
.catch(err => {
|
||||
callback = null;
|
||||
reject(err);
|
||||
});
|
||||
resolve(connectionRequestStatus);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -394,6 +384,22 @@ function evaluateExpression(exp, device) {
|
||||
return exps.get(device);
|
||||
}
|
||||
|
||||
function logIn(username, password) {
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: "/login",
|
||||
background: true,
|
||||
data: { username, password }
|
||||
});
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
return m.request({
|
||||
method: "POST",
|
||||
url: "/logout"
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
count,
|
||||
fetch,
|
||||
@@ -403,5 +409,7 @@ export {
|
||||
postTasks,
|
||||
updateTags,
|
||||
deleteResource,
|
||||
evaluateExpression
|
||||
evaluateExpression,
|
||||
logIn,
|
||||
logOut
|
||||
};
|
||||
+23
-7
@@ -37,16 +37,32 @@ function stageSpv(task) {
|
||||
}
|
||||
|
||||
function commit(tasks, callback) {
|
||||
let devices = {};
|
||||
for (let t of tasks) {
|
||||
t.status = "pending";
|
||||
queue.add(t);
|
||||
devices[t.device] = devices[t.device] || [];
|
||||
devices[t.device].push(t);
|
||||
queueTask(t);
|
||||
}
|
||||
|
||||
return store.postTasks(tasks, (deviceId, connectionRequestStatus, tasks2) => {
|
||||
for (let t of tasks2)
|
||||
if (t.status === "pending") t.status = "stale";
|
||||
else if (t.status === "done") queue.delete(t);
|
||||
if (callback) callback(deviceId, connectionRequestStatus, tasks2);
|
||||
return new Promise(resolve => {
|
||||
let counter = 1;
|
||||
for (let [deviceId, tasks2] of Object.entries(devices))
|
||||
store
|
||||
.postTasks(deviceId, tasks)
|
||||
.then(connectionRequestStatus => {
|
||||
for (let t of tasks2)
|
||||
if (t.status === "pending") t.status = "stale";
|
||||
else if (t.status === "done") queue.delete(t);
|
||||
callback(deviceId, null, connectionRequestStatus, tasks2);
|
||||
if (--counter === 0) resolve();
|
||||
})
|
||||
.catch(err => {
|
||||
for (let t of tasks2) t.status = "stale";
|
||||
callback(deviceId, err, null, tasks2);
|
||||
if (--counter === 0) resolve();
|
||||
});
|
||||
|
||||
if (--counter === 0) resolve();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
|
||||
import m from "mithril";
|
||||
|
||||
import * as store from "./store";
|
||||
import * as notifications from "./notifications";
|
||||
|
||||
const userMenu = {
|
||||
view: () => {
|
||||
if (window.username)
|
||||
return m(
|
||||
"div.user-menu",
|
||||
window.username,
|
||||
m(
|
||||
"button",
|
||||
{
|
||||
onclick: e => {
|
||||
e.target.disabled = true;
|
||||
store
|
||||
.logOut()
|
||||
.then(() => {
|
||||
location.hash = "";
|
||||
location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
e.target.disabled = false;
|
||||
notifications.push("error", err.message);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
"Log out"
|
||||
)
|
||||
);
|
||||
else
|
||||
return m(
|
||||
"div.user-menu",
|
||||
m(
|
||||
"a",
|
||||
{
|
||||
href: "#!/login?" + m.buildQueryString({ continue: m.route.get() })
|
||||
},
|
||||
"Log in"
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default userMenu;
|
||||
Reference in new issue
Block a user