diff --git a/packages/web_ui/src/components/ControllerPage.tsx b/packages/web_ui/src/components/ControllerPage.tsx
index 97a2aa43..9e241bb5 100644
--- a/packages/web_ui/src/components/ControllerPage.tsx
+++ b/packages/web_ui/src/components/ControllerPage.tsx
@@ -1,5 +1,5 @@
import React, {useContext, useState} from "react";
-import { Button, Descriptions, Flex, Popconfirm, Space, Tooltip, Typography } from "antd";
+import { Descriptions, Flex, Popconfirm, Space, Tooltip, Typography } from "antd";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import * as lib from "@clusterio/lib";
@@ -19,13 +19,65 @@ import PageLayout from "./PageLayout";
import { notifyErrorHandler } from "../util/notify";
import webUiPackage from "../../package.json";
import { hasNpmButtonPermission, NpmButton } from "./NpmButton";
+import VariableDropdownButton, { VariableDropdownButtonProps } from "./VariableDropdownButton";
const { Title } = Typography;
+type ControllerControlButtonProps = { canRestart?: boolean, restartRequired?: boolean };
+
+function ControllerControlButton({ canRestart, restartRequired }: ControllerControlButtonProps) {
+ const account = useAccount();
+ const control = useContext(ControlContext);
+ const actions: VariableDropdownButtonProps["actions"] = [];
+
+ if (account.hasPermission("core.controller.restart") && canRestart) {
+ actions.push({
+ key: "restart",
+ label: (
+
+ Restart {restartRequired ? : undefined}
+
+ ),
+ onClick: () => {
+ control.send(
+ new lib.ControllerRestartRequest()
+ ).catch(notifyErrorHandler("Error restarting controller"));
+ },
+ });
+ }
+
+ if (account.hasPermission("core.controller.stop")) {
+ actions.push({
+ key: "stop",
+ danger: true,
+ label: (
+
+ Stopping the controller will leave the cluster inoperable until someone with
+ access to the system it runs on manually starts it again.
+ Are you sure you want to stop the controller?
+ >}
+ placement="bottomRight"
+ okText="Stop"
+ okButtonProps={{ danger: true }}
+ onConfirm={() => {
+ control.send(
+ new lib.ControllerStopRequest()
+ ).catch(notifyErrorHandler("Error stopping controller"));
+ }}
+ >
+ Stop
+
+ ),
+ });
+ }
+
+ return ;
+}
+
export default function ControllerPage() {
- let account = useAccount();
- const control = useContext(ControlContext);
+ const account = useAccount();
const [systems] = useSystems();
const system = systems.get("controller");
const [maxLevel, setMaxLevel] = useState("info");
@@ -34,43 +86,7 @@ export default function ControllerPage() {
- {
- account.hasPermission("core.controller.stop")
- &&
- Stopping the controller will leave the cluster inoperable until someone with
- access to the system it runs on manually starts it again.
- Are you sure you want to stop the controller?
- >}
- placement="bottomRight"
- okText="Stop"
- okButtonProps={{ danger: true }}
- onConfirm={() => {
- control.send(
- new lib.ControllerStopRequest()
- ).catch(notifyErrorHandler("Error stopping controller"));
- }}
- >
-
-
- }
- {
- account.hasPermission("core.controller.restart")
- &&
-
-
- }
+
{
hasNpmButtonPermission(true)
&&
diff --git a/packages/web_ui/src/components/HostViewPage.tsx b/packages/web_ui/src/components/HostViewPage.tsx
index bd689ee4..93374cfa 100644
--- a/packages/web_ui/src/components/HostViewPage.tsx
+++ b/packages/web_ui/src/components/HostViewPage.tsx
@@ -22,9 +22,64 @@ import {
import { formatTimestamp } from "../util/time_format";
import { useSystems } from "../model/system";
import { hasNpmButtonPermission, NpmButton } from "./NpmButton";
+import VariableDropdownButton, { VariableDropdownButtonProps } from "./VariableDropdownButton";
const { Title } = Typography;
+type HostControlButtonProps = { hostId: number, canRestart?: boolean, restartRequired?: boolean };
+
+function HostControlButton({ hostId, canRestart, restartRequired }: HostControlButtonProps) {
+ const account = useAccount();
+ const control = useContext(ControlContext);
+ const actions: VariableDropdownButtonProps["actions"] = [];
+
+ if (account.hasPermission("core.host.restart") && canRestart) {
+ actions.push({
+ key: "restart",
+ label: (
+
+ Restart {restartRequired ? : undefined}
+
+ ),
+ onClick: () => {
+ control.sendTo(
+ { hostId },
+ new lib.HostRestartRequest()
+ ).catch(notifyErrorHandler("Error restarting host"));
+ },
+ });
+ }
+
+ if (account.hasPermission("core.host.stop")) {
+ actions.push({
+ key: "stop",
+ danger: true,
+ label: (
+
+ Stopping this host will leave it inaccessible until someone with
+ access to the system it runs on manually starts it again.
+ Are you sure you want to stop the host?
+ >}
+ placement="bottomRight"
+ okText="Stop"
+ okButtonProps={{ danger: true }}
+ onConfirm={() => {
+ control.sendTo(
+ { hostId },
+ new lib.HostStopRequest()
+ ).catch(notifyErrorHandler("Error stopping host"));
+ }}
+ >
+ Stop
+
+ ),
+ });
+ }
+
+ return ;
+}
+
export default function HostViewPage() {
let params = useParams();
let hostId = Number(params.id);
@@ -50,6 +105,7 @@ export default function HostViewPage() {
}
let hostButtons =
+
{
account.hasPermission("core.host.revoke_token")
&&
}
- {
- account.hasPermission("core.host.stop")
- &&
- Stopping this host will leave it inaccessible until someone with
- access to the system it runs on manually starts it again.
- Are you sure you want to stop the host?
- >}
- placement="bottomRight"
- okText="Stop"
- okButtonProps={{ danger: true }}
- onConfirm={() => {
- control.sendTo(
- { hostId },
- new lib.HostStopRequest()
- ).catch(notifyErrorHandler("Error stopping host"));
- }}
- >
-
-
- }
- {
- account.hasPermission("core.host.restart")
- &&
-
-
- }
{
hasNpmButtonPermission(false)
&&
diff --git a/packages/web_ui/src/components/InstanceControlButton.tsx b/packages/web_ui/src/components/InstanceControlButton.tsx
index dcc401b9..2f1636e3 100644
--- a/packages/web_ui/src/components/InstanceControlButton.tsx
+++ b/packages/web_ui/src/components/InstanceControlButton.tsx
@@ -1,14 +1,14 @@
import React, { useContext, useState } from "react";
-import { Button, MenuProps } from "antd";
+import { Popconfirm } from "antd";
import * as lib from "@clusterio/lib";
import ControlContext from "./ControlContext";
import { useAccount } from "../model/account";
import { notifyErrorHandler } from "../util/notify";
-import Dropdown, { DropdownButtonProps } from "antd/es/dropdown";
+import VariableDropdownButton, { VariableDropdownButtonProps } from "./VariableDropdownButton";
-type InstanceControlButtonProps = DropdownButtonProps & {
+type InstanceControlButtonProps = Omit & {
instance: lib.InstanceDetails;
onFinish?: () => void;
};
@@ -34,94 +34,89 @@ export default function InstanceControlButton(props: InstanceControlButtonProps)
}
}
- const onStopStartClick: DropdownButtonProps["onClick"] = (event) => {
- event.stopPropagation();
- setSwitching(true);
+ const actions: VariableDropdownButtonProps["actions"] = [];
- if (showStart) {
- control.sendTo(
- { instanceId },
- new lib.InstanceStartRequest(undefined),
- ).catch(
- notifyErrorHandler("Error starting instance")
- ).finally(actionFinish);
+ if (account.hasPermission("core.instance.start") && showStart !== switching) {
+ actions.push({
+ key: "start",
+ label: "Start",
+ onClick: () => {
+ setSwitching(true);
+ control.sendTo(
+ { instanceId },
+ new lib.InstanceStartRequest(undefined),
+ ).catch(
+ notifyErrorHandler("Error starting instance")
+ ).finally(actionFinish);
+ },
+ });
- } else {
- control.sendTo(
- { instanceId },
- new lib.InstanceStopRequest(),
- ).catch(
- notifyErrorHandler("Error stopping instance")
- ).finally(actionFinish);
+ } else if (account.hasPermission("core.instance.stop")) { // else if because it is stop or start
+ actions.push({
+ key: "stop",
+ label: "Stop",
+ danger: true,
+ onClick: () => {
+ setSwitching(true);
+ control.sendTo(
+ { instanceId },
+ new lib.InstanceStopRequest(),
+ ).catch(
+ notifyErrorHandler("Error stopping instance")
+ ).finally(actionFinish);
+ },
+ });
+ }
- }
- };
-
- const onMenuClick: MenuProps["onClick"] = (event) => {
- event.domEvent.stopPropagation();
- setSwitching(true);
-
- switch (event.key) {
- case "restart":
+ if (account.hasPermission("core.instance.restart")) {
+ actions.push({
+ key: "restart",
+ label: "Restart",
+ disabled: instanceStatus !== "running",
+ onClick: () => {
+ setSwitching(true);
control.sendTo(
{ instanceId },
new lib.InstanceRestartRequest(undefined),
).catch(
notifyErrorHandler("Error restarting instance")
).finally(actionFinish);
- break;
-
- case "kill":
- control.sendTo(
- { instanceId },
- new lib.InstanceKillRequest(),
- ).catch(
- notifyErrorHandler("Error killing instance")
- ).finally(actionFinish);
- break;
-
- default:
- notifyErrorHandler("Unknown control action");
- }
- };
-
- const extraControls: MenuProps["items"] = [];
- if (account.hasPermission("core.instance.restart")) {
- extraControls.push({
- key: "restart",
- label: "Restart",
- disabled: instanceStatus !== "running",
+ },
});
}
+
if (account.hasPermission("core.instance.kill")) {
- extraControls.push({
+ actions.push({
key: "kill",
- label: "Kill",
+ danger: true,
disabled: instanceStatus === "stopped",
+ label: (
+ Killing this instance may leave you save and player data corrupted.
+ Are you sure you want to kill the instance?
+ >}
+ placement="bottomRight"
+ okText="Kill"
+ okButtonProps={{ danger: true }}
+ onConfirm={() => {
+ control.sendTo(
+ { instanceId },
+ new lib.InstanceKillRequest(),
+ ).catch(
+ notifyErrorHandler("Error killing instance")
+ ).finally(actionFinish);
+ }}
+ >
+ Kill
+ ),
});
}
- const buttonProps: DropdownButtonProps = {
- ...props,
- type: "primary",
- loading: switching,
- onClick: onStopStartClick,
- disabled: !(showStart || showStop),
- danger: showStop !== switching, // Used as an XOR, also below
- };
-
- return extraControls.length > 0
- ? [ // This is needed to prevent propagation to the table row
- left, React.cloneElement(right as any, { onClick: (e: any) => e.stopPropagation() }),
- ]}
- >
- {showStart !== switching ? "Start" : "Stop"}
-
- : ;
-
+ return ;
}
diff --git a/packages/web_ui/src/components/VariableDropdownButton.tsx b/packages/web_ui/src/components/VariableDropdownButton.tsx
new file mode 100644
index 00000000..c7078784
--- /dev/null
+++ b/packages/web_ui/src/components/VariableDropdownButton.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { Button, Dropdown } from "antd";
+
+import { MenuItemType } from "antd/es/menu/interface";
+import { DropdownButtonProps } from "antd/es/dropdown";
+
+export type VariableDropdownButtonProps = DropdownButtonProps & {
+ actions: (MenuItemType & { onClick?: () => void })[],
+};
+
+export default function VariableDropdownButton(props: VariableDropdownButtonProps) {
+ const menuActions = props.actions;
+ const mainAction = menuActions.shift();
+ if (!mainAction) {
+ return <>>;
+ }
+
+ const buttonProps: DropdownButtonProps = {
+ ...props,
+ danger: mainAction.danger,
+ onClick: (event) => {
+ event.stopPropagation();
+ if (mainAction.onClick) {
+ mainAction.onClick();
+ }
+ },
+ };
+
+ return menuActions.length === 0
+ ?
+ : e.domEvent.stopPropagation() }}
+ buttonsRender={([left, right]) => [
+ left, React.cloneElement(right as any, { onClick: (e: any) => e.stopPropagation() }),
+ ]}
+ >
+ {mainAction.label}
+ ;
+}