mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Add instance restart button
This commit is contained in:
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>;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
Reference in new issue
Block a user