Add instance restart button

This commit is contained in:
Cooldude2606 committed 2025-07-14 22:34:41 +01:00
1 parent 2baa2e50ac
commit e0278d0331
5 files changed
+134 -85

No files matched your search

@@ -0,0 +1,127 @@
import React, { useContext, useState } from "react";
import { Button, MenuProps } 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";
type InstanceControlButtonProps = DropdownButtonProps & {
instance: lib.InstanceDetails;
onFinish?: () => void;
};
export const InstanceControlButtonPermissions = [
"core.instance.start", "core.instance.restart", "core.instance.stop", "core.instance.kill",
] as const;
export default function InstanceControlButton(props: InstanceControlButtonProps) {
const account = useAccount();
const control = useContext(ControlContext);
const [switching, setSwitching] = useState<boolean>(false);
const instanceId = props.instance.id;
const instanceStatus = props.instance.status;
const showStart = ["stopped", "stopping"].includes(instanceStatus);
const showStop = ["running", "starting"].includes(instanceStatus);
function actionFinish() {
setSwitching(false);
if (props.onFinish) {
props.onFinish();
}
}
const onStopStartClick: DropdownButtonProps["onClick"] = (event) => {
event.stopPropagation();
setSwitching(true);
if (showStart) {
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);
}
};
const onMenuClick: MenuProps["onClick"] = (event) => {
event.domEvent.stopPropagation();
setSwitching(true);
switch (event.key) {
case "restart":
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({
key: "kill",
label: "Kill",
disabled: instanceStatus === "stopped",
});
}
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
? <Dropdown.Button
{...buttonProps}
menu={{ items: extraControls, onClick: onMenuClick }}
buttonsRender={([left, right]) => [ // 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"}
</Dropdown.Button>
: <Button {...buttonProps}>
{showStart !== switching ? "Start" : "Stop"}
</Button>;
}
@@ -8,7 +8,7 @@ import type { ColumnsType } from "antd/es/table";
import { useAccount } from "../model/account";
import { useHosts } from "../model/host";
import InstanceStatusTag from "./InstanceStatusTag";
import StartStopInstanceButton from "./StartStopInstanceButton";
import InstanceControlButton, { InstanceControlButtonPermissions } from "./InstanceControlButton";
import * as lib from "@clusterio/lib";
import Link from "./Link";
import { instancePublicAddress } from "../util/instance";
@@ -98,13 +98,10 @@ export default function InstanceList(props: InstanceListProps) {
},
];
if (account.hasAnyPermission("core.instance.start", "core.instance.stop")) {
if (account.hasAnyPermission(...InstanceControlButtonPermissions)) {
columns.push({
key: "action",
render: (_, instance) => <StartStopInstanceButton
buttonProps={{ size: "small" }}
instance={instance}
/>,
render: (_, instance) => <InstanceControlButton size="small" instance={instance} />,
responsive: ["sm"],
align: "right",
width: 100,
@@ -15,7 +15,7 @@ import InstanceConfigTree from "./InstanceConfigTree";
import LogConsole, { SelectMaxLogLevel } from "./LogConsole";
import InstanceRcon from "./InstanceRcon";
import AssignInstanceModal from "./AssignInstanceModal";
import StartStopInstanceButton from "./StartStopInstanceButton";
import InstanceControlButton, { InstanceControlButtonPermissions } from "./InstanceControlButton";
import LoadScenarioModal from "./LoadScenarioModal";
import SavesList from "./SavesList";
import { notifyErrorHandler } from "../util/notify";
@@ -91,13 +91,6 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) {
label: "Extract players from save",
});
}
if (account.hasPermission("core.instance.kill")) {
instanceButtonMenuItems.push({
disabled: ["unknown", "unassigned", "stopped"].includes(instance.status!),
key: "kill",
label: "Kill process",
});
}
if (account.hasPermission("core.instance.delete")) {
if (instanceButtonMenuItems.length) {
instanceButtonMenuItems.push({ type: "divider" });
@@ -130,12 +123,6 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) {
new lib.InstanceExtractPlayersRequest(),
).catch(notifyErrorHandler("Error extracting player data"));
} else if (key === "kill") {
control.sendTo(
{ instanceId },
new lib.InstanceKillRequest(),
).catch(notifyErrorHandler("Error killing instance"));
} else if (key === "delete") {
Modal.confirm({
autoFocusButton: "cancel",
@@ -155,8 +142,8 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) {
};
return <Space>
{
account.hasAnyPermission("core.instance.start", "core.instance.stop")
&& <StartStopInstanceButton instance={instance} />
account.hasAnyPermission(...InstanceControlButtonPermissions)
&& <InstanceControlButton instance={instance} />
}
<Button
onClick={() => {
@@ -173,7 +160,6 @@ function InstanceButtons(props: { instance: lib.InstanceDetails }) {
{account.hasAnyPermission(
"core.instance.export_data",
"core.instance.extract_players",
"core.instance.kill",
"core.instance.delete",
)
&& <Dropdown placement="bottomRight" trigger={["click"]} menu={instanceButtonsMenuProps}>
@@ -1,61 +0,0 @@
import React, { useContext, useState } from "react";
import { Button } from "antd";
import * as lib from "@clusterio/lib";
import ControlContext from "./ControlContext";
import { notifyErrorHandler } from "../util/notify";
import { ButtonProps } from "antd/es/button";
type StartStopInstanceButtonProps = {
instance: lib.InstanceDetails;
onFinish?: () => void;
buttonProps?: ButtonProps;
};
export default function StartStopInstanceButton(props: StartStopInstanceButtonProps) {
let control = useContext(ControlContext);
let [switching, setSwitching] = useState<boolean>(false);
function onClick(event: React.MouseEvent<HTMLElement>) {
event.stopPropagation();
setSwitching(true);
let action;
if (props.instance.status === "stopped") {
action = control.sendTo(
{ instanceId: props.instance.id },
new lib.InstanceStartRequest(undefined),
).catch(
notifyErrorHandler("Error starting instance")
);
} else if (["starting", "running"].includes(props.instance.status)) {
action = control.sendTo(
{ instanceId: props.instance.id },
new lib.InstanceStopRequest(),
).catch(
notifyErrorHandler("Error stopping instance")
);
} else {
setSwitching(false);
return;
}
action.finally(() => {
setSwitching(false);
if (props.onFinish) {
props.onFinish();
}
});
};
return <Button
{...(props.buttonProps || {})}
loading={switching}
type="primary"
disabled={!["starting", "running", "stopped"].includes(props.instance.status)}
onClick={onClick}
>
{props.instance.status === "stopped" ? "Start" : "Stop"}
</Button>;
}
+1 -1
View File
@@ -22,7 +22,7 @@ export { default as AssignInstanceModal } from "./components/AssignInstanceModal
export { default as InstanceList } from "./components/InstanceList";
export { default as InstanceRcon } from "./components/InstanceRcon";
export { default as InstanceStatusTag, statusColors } from "./components/InstanceStatusTag";
export { default as StartStopInstanceButton } from "./components/StartStopInstanceButton";
export { default as InstanceControlButton, InstanceControlButtonPermissions } from "./components/InstanceControlButton";
export { default as PageHeader } from "./components/PageHeader";
export { default as PageLayout } from "./components/PageLayout";