web: Implement Jobs component

This commit is contained in:
2025-11-11 11:54:22 -06:00
parent 3168036bca
commit 18ab2c40ba
9 changed files with 139 additions and 3 deletions

View File

@@ -12,7 +12,8 @@ import { BLANK_SCENARIO } from "../../core/Data/fixtures";
import "tabulator-tables/dist/css/tabulator.min.css";
import "../Common/Forms/Tables.css";
import { useState } from "react";
import Footer from "./Footer";
import { useNavigate } from "react-router";
import Footer from "../Common/Footer";
import * as pako from "pako";
import { offerDownload } from "../Common/io";
import { preprocess } from "../../core/Operations/preprocessing";
@@ -31,6 +32,7 @@ export interface CaseBuilderSectionProps {
}
const CaseBuilder = () => {
const navigate = useNavigate();
const [scenario, setScenario] = useState(() => {
const savedScenario = localStorage.getItem("scenario");
if (!savedScenario) return BLANK_SCENARIO;
@@ -116,7 +118,7 @@ const CaseBuilder = () => {
// Parse response
const data = await response.json();
console.log(data.job_id);
navigate(`/jobs/${data.job_id}`);
};
return (

View File

@@ -4,7 +4,7 @@
* 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 { useRef } from "react";
import FileUploadElement from "../Common/Buttons/FileUploadElement";

View 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;

View 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;
}

View 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;

View File

@@ -9,6 +9,7 @@ import ReactDOM from "react-dom/client";
import reportWebVitals from "./reportWebVitals";
import CaseBuilder from "./components/CaseBuilder/CaseBuilder";
import { BrowserRouter, Navigate, Route, Routes } from "react-router";
import Jobs from "./components/Jobs/Jobs";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement,
@@ -19,6 +20,7 @@ root.render(
<React.StrictMode>
<Routes>
<Route path="/builder" element={<CaseBuilder />} />
<Route path="/jobs/:jobId" element={<Jobs />} />
<Route path="/" element={<Navigate to="/builder" replace />} />
</Routes>
</React.StrictMode>