| 'use client'; |
| "use client"; |
|
|
| import { useEffect, useState } from "react"; |
|
|
| const API = |
| process.env.NEXT_PUBLIC_API_URL || |
| "http://127.0.0.1:8000"; |
|
|
| export default function BuildCenter() { |
|
|
| const [jobs, setJobs] = useState([]); |
| const [loading, setLoading] = useState(false); |
|
|
| async function refresh() { |
| setLoading(true); |
|
|
| try { |
| const r = await fetch(API + "/api/builds"); |
| const d = await r.json(); |
| setJobs(Array.isArray(d) ? d : (d.jobs || [])); |
| } catch { |
| setJobs([]); |
| } |
|
|
| setLoading(false); |
| } |
|
|
| useEffect(() => { |
| refresh(); |
| const timer = setInterval(refresh, 5000); |
| return () => clearInterval(timer); |
| }, []); |
|
|
| async function build(target) { |
|
|
| await fetch(API + "/api/builds/start", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json" |
| }, |
| body: JSON.stringify({ |
| target |
| }) |
| }); |
|
|
| refresh(); |
| } |
|
|
| return ( |
|
|
| <div className="flex flex-col h-full bg-slate-950 text-white"> |
| |
| <div className="flex gap-2 border-b border-slate-800 p-3"> |
| |
| <button |
| onClick={() => build("web")} |
| className="rounded bg-blue-600 px-4 py-2"> |
| Build Web |
| </button> |
| |
| <button |
| onClick={() => build("android")} |
| className="rounded bg-emerald-600 px-4 py-2"> |
| Build APK |
| </button> |
| |
| <button |
| onClick={refresh} |
| className="rounded bg-slate-700 px-4 py-2"> |
| Refresh |
| </button> |
| |
| </div> |
| |
| <div className="flex-1 overflow-auto"> |
| |
| {loading && ( |
| <div className="p-4"> |
| Loading... |
| </div> |
| )} |
| |
| {jobs.map(job => ( |
| |
| <div |
| key={job.id} |
| className="border-b border-slate-800 p-4"> |
| |
| <div className="font-semibold"> |
| {job.id} |
| </div> |
| |
| <div> |
| {job.status} |
| </div> |
| |
| <div> |
| {job.target} |
| </div> |
| |
| </div> |
| |
| ))} |
| |
| </div> |
| |
| </div> |
|
|
| ); |
|
|
| } |
|
|