Skip to content

Commit b7e92be

Browse files
remake the runner config page & add change status modal
1 parent 6adf82b commit b7e92be

File tree

6 files changed

+457
-101
lines changed

6 files changed

+457
-101
lines changed

src/common/ChangeStatusModal.jsx

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,63 @@
1-
import { useState } from "react";
1+
import { useState, useCallback, useEffect } from "react";
22
import { STATUS_STYLES } from "../services/utils";
33
import Modal from "./Modal";
44
import {
55
commandGeneratorGraph,
66
statusChangeTextGeneratorGraph,
77
} from "../components/context/utils";
88
import { useCopyToClipboard } from "@uidotdev/usehooks";
9+
import { RunnerOptionsFormSection } from "./RunnerOptionsFormSection";
10+
import { autosubmitApiV4 } from "../services/autosubmitApiV4";
11+
12+
const ConfirmModal = ({ show, onConfirm, onCancel, message }) => {
13+
return (
14+
<Modal show={show} onClose={onCancel}>
15+
<div className="bg-white text-black py-6 px-6 rounded-lg w-96">
16+
<p className="mb-4">{message}</p>
17+
<div className="flex justify-end gap-4">
18+
<button
19+
className="bg-gray-300 text-black px-4 py-2 rounded hover:bg-gray-400"
20+
onClick={onCancel}
21+
>
22+
Cancel
23+
</button>
24+
<button
25+
className="bg-success text-white px-4 py-2 rounded hover:bg-success/90"
26+
onClick={onConfirm}
27+
>
28+
Confirm
29+
</button>
30+
</div>
31+
</div>
32+
</Modal>
33+
);
34+
};
935

1036
export function ChangeStatusModal({ expid, selectedJobs, show, onHide }) {
1137
const copyToClipboard = useCopyToClipboard()[1];
1238
const [targetStatus, setTargetStatus] = useState("WAITING");
1339
const [type, setType] = useState("cmd");
1440
const [copied, setCopied] = useState("Copy to clipboard");
41+
const [runnerProfileOptions, setRunnerProfileOptions] = useState({
42+
profile_name: "",
43+
profile_params: {},
44+
});
45+
46+
const [
47+
submitCommand,
48+
{
49+
isLoading: submitCommandLoading,
50+
isSuccess: isSubmitCommandSuccess,
51+
isError: isSubmitCommandError,
52+
},
53+
] = autosubmitApiV4.endpoints.runJobSetStatusCommand.useMutation();
54+
55+
// Hide modal on successful mutation
56+
useEffect(() => {
57+
if (isSubmitCommandSuccess) {
58+
onHide(true);
59+
}
60+
}, [isSubmitCommandSuccess]);
1561

1662
const handleCopy = () => {
1763
let content = "";
@@ -26,10 +72,33 @@ export function ChangeStatusModal({ expid, selectedJobs, show, onHide }) {
2672
setCopied("Copy to clipboard");
2773
}, 2000);
2874
};
75+
76+
const handleRunnerSelectionChange = useCallback((selection) => {
77+
setRunnerProfileOptions(selection);
78+
}, []);
79+
80+
const handleSubmitCommand = () => {
81+
const body = {
82+
expid,
83+
profile_name: runnerProfileOptions.profile_name,
84+
profile_params: runnerProfileOptions.profile_params,
85+
command_params: {
86+
final_status: targetStatus,
87+
job_names_list: [...selectedJobs],
88+
},
89+
};
90+
submitCommand(body);
91+
};
92+
93+
const [showConfirm, setShowConfirm] = useState(false);
94+
2995
return (
30-
<Modal show={show} onClose={onHide}>
96+
<Modal show={show} onClose={() => onHide()}>
3197
<div className="bg-white text-black py-6 px-6 rounded-lg relative w-[40rem] max-w-[95vw]">
32-
<div className="cursor-pointer absolute top-4 right-4" onClick={onHide}>
98+
<div
99+
className="cursor-pointer absolute top-4 right-4"
100+
onClick={() => onHide()}
101+
>
33102
<i className="fa-solid fa-xmark"></i>
34103
</div>
35104
<div className="flex flex-col gap-2">
@@ -73,6 +142,46 @@ export function ChangeStatusModal({ expid, selectedJobs, show, onHide }) {
73142
.split("\n")
74143
.map((item, index) => <p key={index}>{item}</p>)}
75144
</div>
145+
146+
<div className="py-2 flex flex-col items-center">
147+
<RunnerOptionsFormSection
148+
onSelectionChange={handleRunnerSelectionChange}
149+
/>
150+
151+
{isSubmitCommandError && (
152+
<div className="text-red-600 mt-2">
153+
Error running the set job status command. Please try again.
154+
</div>
155+
)}
156+
157+
<button
158+
className="mt-2 bg-success text-white px-4 py-2 rounded hover:bg-success/90 disabled:bg-success/70"
159+
disabled={
160+
!runnerProfileOptions.profile_name || submitCommandLoading
161+
}
162+
onClick={() => setShowConfirm(true)}
163+
>
164+
{submitCommandLoading ? (
165+
<span>
166+
<i className="fa fa-spinner fa-spin me-2" /> RUNNING...
167+
</span>
168+
) : (
169+
"RUN COMMAND"
170+
)}
171+
</button>
172+
173+
<ConfirmModal
174+
show={showConfirm}
175+
onCancel={() => {
176+
setShowConfirm(false);
177+
}}
178+
onConfirm={() => {
179+
handleSubmitCommand();
180+
setShowConfirm(false);
181+
}}
182+
message={`Are you sure you want to run the status change command on ${selectedJobs.length} jobs?`}
183+
/>
184+
</div>
76185
</div>
77186
</div>
78187
</Modal>
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { useState, useMemo, useEffect } from "react";
2+
import { autosubmitApiV4 } from "../services/autosubmitApiV4";
3+
import { cn } from "../services/utils";
4+
5+
export const RunnerOptionsFormSection = ({ onSelectionChange }) => {
6+
const {
7+
data: runnerProfiles,
8+
error: runnerProfilesError,
9+
isLoading: runnerProfilesLoading,
10+
} = autosubmitApiV4.endpoints.getRunnersConfigProfiles.useQuery();
11+
12+
const activeRunnerProfiles = useMemo(
13+
() => (runnerProfiles ? Object.keys(runnerProfiles) : []),
14+
[runnerProfiles]
15+
);
16+
17+
const [selectedProfileName, setSelectedProfileName] = useState("");
18+
const [runnerConfig, setRunnerConfig] = useState({});
19+
20+
const selectedProfile = useMemo(() => {
21+
if (!selectedProfileName || !runnerProfiles) return null;
22+
return runnerProfiles[selectedProfileName];
23+
}, [selectedProfileName, runnerProfiles]);
24+
25+
const isSSHRunner = selectedProfile?.RUNNER_TYPE === "SSH";
26+
const hasModules = selectedProfile?.MODULE_LOADER_TYPE !== "NO_MODULE";
27+
28+
const handleProfileNameChange = (profileName) => {
29+
const updatedSelection = { profile_name: profileName, profile_params: {} };
30+
setSelectedProfileName(updatedSelection.profile_name);
31+
setRunnerConfig(updatedSelection.profile_params);
32+
onSelectionChange?.(updatedSelection);
33+
};
34+
35+
const handleConfigChange = (field, value) => {
36+
const updatedConfig = { ...runnerConfig, [field]: value };
37+
setRunnerConfig(updatedConfig);
38+
onSelectionChange?.({
39+
profile_name: selectedProfileName,
40+
profile_params: updatedConfig,
41+
});
42+
};
43+
44+
const handleSSHChange = (field, value) => {
45+
const updatedSSH = { ...runnerConfig.SSH, [field]: value };
46+
const updatedConfig = { ...runnerConfig, SSH: updatedSSH };
47+
setRunnerConfig(updatedConfig);
48+
onSelectionChange?.({
49+
profile_name: selectedProfileName,
50+
profile_params: updatedConfig,
51+
});
52+
};
53+
54+
// Render loading/error states
55+
if (runnerProfilesLoading) {
56+
return <div className="text-gray-500">Loading runner profiles...</div>;
57+
}
58+
59+
if (runnerProfilesError) {
60+
return <div className="text-red-500">Error loading runner profiles</div>;
61+
}
62+
63+
return (
64+
<div className="flex flex-col w-full gap-2">
65+
<div className="flex gap-2 items-center">
66+
<label className="font-semibold text-lg">Runner profile:</label>
67+
<select
68+
className="px-2 py-1 border rounded text-sm"
69+
value={selectedProfileName}
70+
onChange={(e) => handleProfileNameChange(e.target.value)}
71+
>
72+
<option value="" disabled>
73+
Select Runner profile
74+
</option>
75+
{activeRunnerProfiles.map((runner) => (
76+
<option key={runner} value={runner}>
77+
{runner}
78+
</option>
79+
))}
80+
</select>
81+
</div>
82+
83+
{selectedProfile && (
84+
<div className="flex flex-col border rounded py-4 px-4 gap-3">
85+
<div className="flex items-center">
86+
<label className="font-semibold min-w-[150px]">Runner type:</label>
87+
{selectedProfile.RUNNER_TYPE ? (
88+
<div className="ml-2">{selectedProfile.RUNNER_TYPE}</div>
89+
) : (
90+
<input
91+
type="text"
92+
className="ml-2 px-2 py-1 border rounded text-sm flex-1"
93+
placeholder="Enter runner type"
94+
value={runnerConfig.RUNNER_TYPE || ""}
95+
onChange={(e) =>
96+
handleConfigChange("RUNNER_TYPE", e.target.value)
97+
}
98+
/>
99+
)}
100+
</div>
101+
102+
{isSSHRunner && (
103+
<div className="flex flex-col border rounded py-3 px-4 gap-3 bg-gray-50 dark:bg-gray-800">
104+
<div className="font-semibold text-sm">SSH Configuration:</div>
105+
106+
<div className="flex items-center">
107+
<label className="font-medium min-w-[120px] text-sm">
108+
Host:
109+
</label>
110+
{selectedProfile.SSH?.HOST ? (
111+
<div className="ml-2">{selectedProfile.SSH.HOST}</div>
112+
) : (
113+
<input
114+
type="text"
115+
className="ml-2 px-2 py-1 border rounded text-sm flex-1"
116+
placeholder="Enter SSH host"
117+
value={runnerConfig.SSH?.HOST || ""}
118+
onChange={(e) => handleSSHChange("HOST", e.target.value)}
119+
/>
120+
)}
121+
</div>
122+
123+
<div className="flex items-center">
124+
<label className="font-medium min-w-[120px] text-sm">
125+
Username:
126+
</label>
127+
{selectedProfile.SSH?.USERNAME ? (
128+
<div className="ml-2">{selectedProfile.SSH.USERNAME}</div>
129+
) : (
130+
<input
131+
type="text"
132+
className="ml-2 px-2 py-1 border rounded text-sm flex-1"
133+
placeholder="Enter SSH username"
134+
value={runnerConfig.SSH?.USERNAME || ""}
135+
onChange={(e) =>
136+
handleSSHChange("USERNAME", e.target.value)
137+
}
138+
/>
139+
)}
140+
</div>
141+
142+
<div className="flex items-center">
143+
<label className="font-medium min-w-[120px] text-sm">
144+
Port:
145+
</label>
146+
{selectedProfile.SSH?.PORT ? (
147+
<div className="ml-2">{selectedProfile.SSH.PORT}</div>
148+
) : (
149+
<input
150+
type="number"
151+
className="ml-2 px-2 py-1 border rounded text-sm w-24"
152+
placeholder="22"
153+
value={runnerConfig.SSH?.PORT || ""}
154+
onChange={(e) => handleSSHChange("PORT", e.target.value)}
155+
/>
156+
)}
157+
</div>
158+
</div>
159+
)}
160+
161+
<div className="flex items-center">
162+
<label className="font-semibold min-w-[150px]">
163+
Module Loader type:
164+
</label>
165+
{selectedProfile.MODULE_LOADER_TYPE ? (
166+
<div className="ml-2">{selectedProfile.MODULE_LOADER_TYPE}</div>
167+
) : (
168+
<input
169+
type="text"
170+
className="ml-2 px-2 py-1 border rounded text-sm flex-1"
171+
placeholder="Enter module loader type"
172+
value={runnerConfig.MODULE_LOADER_TYPE || ""}
173+
onChange={(e) =>
174+
handleConfigChange("MODULE_LOADER_TYPE", e.target.value)
175+
}
176+
/>
177+
)}
178+
</div>
179+
180+
{hasModules && (
181+
<div className="flex items-center">
182+
<label className="font-semibold min-w-[150px]">Modules:</label>
183+
{selectedProfile.MODULES ? (
184+
<div className="ml-2">
185+
{Array.isArray(selectedProfile.MODULES)
186+
? selectedProfile.MODULES.join(", ")
187+
: selectedProfile.MODULES}
188+
</div>
189+
) : (
190+
<input
191+
type="text"
192+
className="ml-2 px-2 py-1 border rounded text-sm flex-1"
193+
placeholder="Enter modules to load"
194+
value={runnerConfig.MODULES || ""}
195+
onChange={(e) =>
196+
handleConfigChange("MODULES", e.target.value)
197+
}
198+
/>
199+
)}
200+
</div>
201+
)}
202+
</div>
203+
)}
204+
</div>
205+
);
206+
};

src/pages/ExperimentGraph.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ const ExperimentGraph = () => {
4646
}, [jobs, selectedJobIds]);
4747

4848
const [showModal, setShowModal] = useState(false);
49-
const toggleModal = () => {
49+
const toggleModal = (refresh = false) => {
5050
setShowModal(!showModal);
51+
if (refresh === true) {
52+
refetch();
53+
}
5154
};
5255

5356
const { data, isFetching, refetch, isError } =

src/pages/ExperimentTree.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ const ExperimentTree = () => {
4949
});
5050

5151
const [showModal, setShowModal] = useState(false);
52-
const toggleModal = () => {
52+
const toggleModal = ( refresh = false ) => {
5353
setShowModal(!showModal);
54+
if (refresh === true) {
55+
refetch();
56+
}
5457
};
5558

5659
const { data, isFetching, refetch, isError } =

0 commit comments

Comments
 (0)