mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
web: Implement Jobs component
This commit is contained in:
@@ -12,7 +12,8 @@ import { BLANK_SCENARIO } from "../../core/Data/fixtures";
|
|||||||
import "tabulator-tables/dist/css/tabulator.min.css";
|
import "tabulator-tables/dist/css/tabulator.min.css";
|
||||||
import "../Common/Forms/Tables.css";
|
import "../Common/Forms/Tables.css";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Footer from "./Footer";
|
import { useNavigate } from "react-router";
|
||||||
|
import Footer from "../Common/Footer";
|
||||||
import * as pako from "pako";
|
import * as pako from "pako";
|
||||||
import { offerDownload } from "../Common/io";
|
import { offerDownload } from "../Common/io";
|
||||||
import { preprocess } from "../../core/Operations/preprocessing";
|
import { preprocess } from "../../core/Operations/preprocessing";
|
||||||
@@ -31,6 +32,7 @@ export interface CaseBuilderSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CaseBuilder = () => {
|
const CaseBuilder = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [scenario, setScenario] = useState(() => {
|
const [scenario, setScenario] = useState(() => {
|
||||||
const savedScenario = localStorage.getItem("scenario");
|
const savedScenario = localStorage.getItem("scenario");
|
||||||
if (!savedScenario) return BLANK_SCENARIO;
|
if (!savedScenario) return BLANK_SCENARIO;
|
||||||
@@ -116,7 +118,7 @@ const CaseBuilder = () => {
|
|||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(data.job_id);
|
navigate(`/jobs/${data.job_id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Released under the modified BSD license. See COPYING.md for more details.
|
* Released under the modified BSD license. See COPYING.md for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./Header.module.css";
|
import styles from "../Common/Header.module.css";
|
||||||
import SiteHeaderButton from "../Common/Buttons/SiteHeaderButton";
|
import SiteHeaderButton from "../Common/Buttons/SiteHeaderButton";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import FileUploadElement from "../Common/Buttons/FileUploadElement";
|
import FileUploadElement from "../Common/Buttons/FileUploadElement";
|
||||||
|
|||||||
20
web/frontend/src/components/Jobs/Header.tsx
Normal file
20
web/frontend/src/components/Jobs/Header.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||||
|
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
* Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "../Common/Header.module.css";
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
return (
|
||||||
|
<div className={styles.HeaderBox}>
|
||||||
|
<div className={styles.HeaderContent}>
|
||||||
|
<h1>UnitCommitment.jl</h1>
|
||||||
|
<h2>Solver</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
||||||
19
web/frontend/src/components/Jobs/Jobs.module.css
Normal file
19
web/frontend/src/components/Jobs/Jobs.module.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||||
|
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
* Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.SolverLog {
|
||||||
|
white-space: preserve;
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--contrast-0);
|
||||||
|
color: var(--contrast-100);
|
||||||
|
border: var(--box-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow);
|
||||||
|
height: 40em;
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
93
web/frontend/src/components/Jobs/Jobs.tsx
Normal file
93
web/frontend/src/components/Jobs/Jobs.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||||
|
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
* Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import Header from "./Header";
|
||||||
|
import Footer from "../Common/Footer";
|
||||||
|
import SectionHeader from "../Common/SectionHeader/SectionHeader";
|
||||||
|
import styles from "./Jobs.module.css";
|
||||||
|
import formStyles from "../Common/Forms/Form.module.css";
|
||||||
|
|
||||||
|
interface JobData {
|
||||||
|
log: string;
|
||||||
|
solution: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Jobs = () => {
|
||||||
|
const { jobId } = useParams();
|
||||||
|
const [jobData, setJobData] = useState<JobData | null>(null);
|
||||||
|
const logRef = useRef<HTMLDivElement>(null);
|
||||||
|
const previousLogRef = useRef<string>("");
|
||||||
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchJobData = async () => {
|
||||||
|
const backendUrl = process.env.REACT_APP_BACKEND_URL;
|
||||||
|
const response = await fetch(`${backendUrl}/jobs/${jobId}/view`);
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.solution) {
|
||||||
|
// Stop polling if solution exists
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
intervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse solution
|
||||||
|
data.solution = JSON.parse(data.solution);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data
|
||||||
|
setJobData(data);
|
||||||
|
console.log(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch immediately
|
||||||
|
fetchJobData();
|
||||||
|
|
||||||
|
// Set up polling every second
|
||||||
|
intervalRef.current = setInterval(fetchJobData, 1000);
|
||||||
|
|
||||||
|
// Cleanup interval on unmount
|
||||||
|
return () => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [jobId]);
|
||||||
|
|
||||||
|
// Auto-scroll to the bottom when log content changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (jobData?.log && jobData.log !== previousLogRef.current) {
|
||||||
|
previousLogRef.current = jobData.log;
|
||||||
|
if (logRef.current) {
|
||||||
|
logRef.current.scrollTop = logRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [jobData?.log]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<div className="content">
|
||||||
|
<SectionHeader title="Optimization log"></SectionHeader>
|
||||||
|
<div className={formStyles.FormWrapper}>
|
||||||
|
<div className={styles.SolverLog} ref={logRef}>
|
||||||
|
{jobData ? jobData.log : "Loading..."}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Jobs;
|
||||||
@@ -9,6 +9,7 @@ import ReactDOM from "react-dom/client";
|
|||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import CaseBuilder from "./components/CaseBuilder/CaseBuilder";
|
import CaseBuilder from "./components/CaseBuilder/CaseBuilder";
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router";
|
import { BrowserRouter, Navigate, Route, Routes } from "react-router";
|
||||||
|
import Jobs from "./components/Jobs/Jobs";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLElement,
|
document.getElementById("root") as HTMLElement,
|
||||||
@@ -19,6 +20,7 @@ root.render(
|
|||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/builder" element={<CaseBuilder />} />
|
<Route path="/builder" element={<CaseBuilder />} />
|
||||||
|
<Route path="/jobs/:jobId" element={<Jobs />} />
|
||||||
<Route path="/" element={<Navigate to="/builder" replace />} />
|
<Route path="/" element={<Navigate to="/builder" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
|||||||
Reference in New Issue
Block a user