Merge pull request #371 from Hornwitser/account-button

Account button
This commit is contained in:
Hornwitser authored and GitHub committed 2021-03-20 03:11:16 +01:00
commit 73c713462c
10 files changed
+169 -16

No files matched your search

+3 -2
View File
@@ -178,7 +178,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
setTimeout(timeout) {
this._timeout = timeout;
if (this._reconnectId && this.timeout < this._reconnectDelay / 2) {
if (this._reconnectId && this._timeout < this._reconnectDelay / 2) {
clearTimeout(this._reconnectId);
this.reconnect();
}
@@ -280,6 +280,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
this._lastReceivedSeq = null;
this._sessionToken = null;
this._sendBuffer.length = 0;
this._state = "new";
this._connected = false;
this.emit("close");
return;
@@ -401,7 +402,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
}
this._startedReconnect = null;
this._connected = true;
this.emit("connect");
this.emit("connect", data);
} else if (type === "continue") {
logger.verbose("SOCKET | resuming existing session");
+7
View File
@@ -102,6 +102,13 @@ const serverHandshake = ajv.compile({
properties: {
"session_token": { type: "string" },
"heartbeat_interval": { type: "number" },
"account": {
additionalProperties: false,
required: ["name"],
properties: {
"name": { type: "string" },
},
},
},
},
},
+9 -2
View File
@@ -1371,8 +1371,10 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
*
* @param {string} sessionToken -
* the session token to send to the client.
* @param {Object=} additionalData -
* extra properties to send along the ready message.
*/
ready(sessionToken) {
ready(sessionToken, additionalData) {
this._heartbeatInterval = masterConfig.get("master.heartbeat_interval");
this._socket.send(JSON.stringify({
seq: null,
@@ -1380,6 +1382,7 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
data: {
session_token: sessionToken,
heartbeat_interval: this._heartbeatInterval,
...additionalData,
},
}));
@@ -1650,6 +1653,7 @@ async function handleHandshake(message, socket, req, attachHandler) {
let connector = new WebSocketServerConnector(socket, sessionId);
activeConnectors.set(sessionId, connector);
let additionalReadyData = {};
if (type === "register_slave") {
let connection = slaveConnections.get(data.id);
if (connection) {
@@ -1664,9 +1668,12 @@ async function handleHandshake(message, socket, req, attachHandler) {
} else if (type === "register_control") {
logger.verbose(`SOCKET | registered control from ${req.socket.remoteAddress}`);
controlConnections.push(new ControlConnection(data, connector, user));
additionalReadyData["account"] = {
name: user.name,
};
}
connector.ready(sessionToken);
connector.ready(sessionToken, additionalReadyData);
}
wss.on("connection", (socket, req) => {
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#2a2a2a" />
<title>Clusterio</title>
<script>
window.webRoot = new URL("__WEB_ROOT__", document.location).pathname;
+1
View File
@@ -87,6 +87,7 @@ export default async function bootstrap() {
wsUrl.protocol = wsUrl.protocol.replace("http", "ws");
let controlConnector = new ControlConnector(wsUrl, 10);
controlConnector.setTimeout(15);
let control = new Control(controlConnector, plugins);
ReactDOM.render(<App control={control} plugins={plugins}/>, document.getElementById("root"));
+14 -6
View File
@@ -31,6 +31,7 @@ export default function App(props) {
function clearToken() {
setToken(null);
connector.token = null;
localStorage.removeItem("master_token");
}
@@ -54,17 +55,24 @@ export default function App(props) {
connector.on("close", onClose);
connector.on("error", onError);
if (token && !connected) {
connector.token = token;
connector.connect();
}
return () => {
connector.off("error", onError);
connector.off("close", onClose);
connector.off("connect", onConnect);
};
});
}, [token, props.control]);
useEffect(() => {
if (token && !connected) {
if (props.control.loggingOut) {
clearToken();
props.control.loggingOut = false;
} else {
connector.token = token;
connector.connect();
}
}
}, [token, connected]);
let page;
if (connected) {
+21 -3
View File
@@ -1,8 +1,10 @@
import React, { useEffect, useState } from "react";
import { Switch, Route, useHistory } from "react-router-dom";
import { Layout, Menu } from "antd";
import { Dropdown, Layout, Menu } from "antd";
import UserOutlined from "@ant-design/icons/UserOutlined";
import webUiPackage from "../../package.json";
import { useAccount } from "../model/account";
import ErrorBoundary from "./ErrorBoundary";
import ErrorPage from "./ErrorPage";
@@ -40,6 +42,7 @@ const sidebar = [
export default function SiteLayout(props) {
let history = useHistory();
let [sidebarPath, setSidebarPath] = useState(null);
let account = useAccount();
function SetSidebar(setSidebarProps) {
useEffect(() => {
@@ -48,14 +51,29 @@ export default function SiteLayout(props) {
return null;
}
let accountMenu = <Menu>
<Menu.ItemGroup title={account.name}/>
<Menu.Divider/>
<Menu.Item danger onClick={() => { account.logOut(); }}>Log out</Menu.Item>
</Menu>;
return <Layout style={{ minHeight: "100vh" }}>
<Header className="header">
<div className="site-logo" />
<span className="site-name" style={{ top: -10 }}>Clusterio</span>
<span style={{ position: "absolute", top: 10, left: 120, color: "#f00" }}>{ webUiPackage.version }</span>
<span className="site-name">Clusterio</span>
<span className="site-version">{ webUiPackage.version }</span>
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={["1"]}>
<Menu.Item key="1">Dashboard</Menu.Item>
</Menu>
<Dropdown
className="account-dropdown-header"
placement="bottomRight"
trigger="click"
overlay={accountMenu}
>
<UserOutlined/>
</Dropdown>
</Header>
<Layout className="site-layout">
<Sider
+53
View File
@@ -64,15 +64,24 @@ body {
.header {
padding-left: 250px;
padding-right: 64px;
}
.site-name {
position: absolute;
top: -10px;
left: 112px;
font-size: 18pt;
font-weight: 400;
}
.site-version {
position: absolute;
top: 10px;
left: 120px;
color: #f00;
}
.site-logo {
position: absolute;
left: 36px;
@@ -82,6 +91,50 @@ body {
background-size: 64px;
}
@media not all and (min-width: 576px) {
.header {
padding-left: 88px;
}
.site-logo {
left: 12px;
}
.site-name {
display: none;
}
.site-version {
display: none;
}
}
.account-dropdown-header {
position: absolute;
right: 0;
top: 0;
width: 64px;
height: 64px;
font-size: 18px;
line-height: 72px;
text-align: center;
}
.account-dropdown-header::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: transparent;
transition: all 0.3s;
content: "";
}
.account-dropdown-header:hover::after {
background: rgba(255, 255, 255, 0.1);
}
.error-traceback code {
display: block;
margin: 0;
+38
View File
@@ -0,0 +1,38 @@
import { useContext, useEffect, useState } from "react";
import { notifyErrorHandler } from "../util/notify";
import ControlContext from "../components/ControlContext";
/**
* @typedef {Object} Account
* @property {?string} name - Name of the currently logged in account.
* @property {function()} logout - Logs out of the web interface.
*/
export function useAccount() {
let control = useContext(ControlContext);
let [name, setName] = useState(control.accountName);
useEffect(() => {
function onConnect(data) {
setName(data.account.name);
}
function onClose() {
setName(null);
}
control.connector.on("connect", onConnect);
control.connector.on("close", onClose);
return () => {
control.connector.off("connect", onConnect);
control.connector.off("close", onClose);
};
}, [control]);
return {
name,
logOut() {
control.loggingOut = true;
control.shutdown(notifyErrorHandler("Error logging out"));
},
};
}
+22 -3
View File
@@ -49,13 +49,30 @@ export class Control extends libLink.Link {
libPlugin.attachPluginMessages(this, plugin);
}
/**
* Flag indicating the connection is in the process of logging out.
* @type {boolean}
*/
this.loggingOut = false;
/**
* Name of the account this control link is connected as.
* @type {?string}
*/
this.accountName = null;
this.instanceLogHandlers = new Map();
this.connector.on("connect", () => {
this.connector.on("connect", data => {
this.accountName = data.account.name;
this.updateLogSubscriptions().catch(err => logger.error(
`Unexpected error updating log subscriptions:\n${err.stack}`
));
});
this.connector.on("close", () => {
this.accountName = null;
});
}
async logMessageEventHandler(message) {
@@ -105,6 +122,10 @@ export class Control extends libLink.Link {
}
async updateLogSubscriptions() {
if (!this.connector.connected) {
return;
}
await libLink.messages.setLogSubscriptions.send(this, {
all: false,
master: false,
@@ -120,8 +141,6 @@ export class Control extends libLink.Link {
}
async shutdown() {
this.connector.setTimeout(30);
try {
await libLink.messages.prepareDisconnect.send(this);
} catch (err) {