mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14:53 +09:00
10 files changed
+169
-16
No files matched your search
@@ -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");
|
||||
|
||||
@@ -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" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user