@@ -168,6 +157,54 @@ const Header: React.FC = () => {
)}
+ {/* Slide-out menu */}
+
+
+
+
+
+
+
+ {/* Overlay to close menu when clicking outside */}
+ {isMenuOpen && (
+
setIsMenuOpen(false)}
+ >
+ )}
);
};
diff --git a/src/components/pages/errored.tsx b/src/components/pages/errored.tsx
new file mode 100644
index 0000000..93491df
--- /dev/null
+++ b/src/components/pages/errored.tsx
@@ -0,0 +1,265 @@
+import React, { useState, useEffect, useRef, useCallback } from "react";
+import { useMediaQuery } from "react-responsive";
+import usePackageData from "../../hooks/usePackageData";
+import { useLocation } from "wouter";
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "../ui/table";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "../ui/dialog";
+import { Button } from "../ui/button";
+import { Input } from "../ui/input";
+import { Checkbox } from "../ui/checkbox";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
+
+const Errored: React.FC = () => {
+ const { errPackages, loading, fetchErrPackages } = usePackageData();
+ const isMobile = useMediaQuery({ maxWidth: 767 });
+ const [, setPageSize] = useState(isMobile ? 100 : 250);
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [, setLocation] = useLocation();
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const [, setSelectedPackage] = useState(null);
+ const [rebuildData, setRebuildData] = useState({
+ packageName: "",
+ version: "",
+ buildType: "v3",
+ rebuild: false,
+ });
+
+ const isInitialMount = useRef(true);
+
+ const checkLoginStatus = async () => {
+ const r = await fetch("/api/isloggedin");
+ let loggedIn = false;
+ if (r.status === 200) {
+ loggedIn = true;
+ }
+ setIsLoggedIn(loggedIn);
+ };
+
+ useEffect(() => {
+ setPageSize(isMobile ? 100 : 250);
+ }, [isMobile]);
+
+ const loadPackages = useCallback(() => {
+ fetchErrPackages();
+ }, [fetchErrPackages]);
+
+ useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ loadPackages();
+ checkLoginStatus();
+ }
+ }, [loadPackages]);
+
+ const handleRebuildClick = (pkg: any) => {
+ const firstPackage = Object.values(pkg.Packages)[0] as any;
+ setSelectedPackage(pkg);
+ setRebuildData({
+ packageName: pkg.Name,
+ version: firstPackage.NewVersion || firstPackage.Version,
+ buildType: "lto",
+ rebuild: false,
+ });
+ setIsDialogOpen(true);
+ };
+
+ const handleDialogSubmit = async () => {
+ try {
+ const response = await fetch("/api/auth/triggerbuild", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(rebuildData),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ setIsDialogOpen(false);
+ setLocation(`/queue?search=${rebuildData.packageName}`)
+ } else {
+ alert(data.error);
+ }
+ } catch (error) {
+ alert("An error occurred while submitting the build request.");
+ }
+ };
+
+ return (
+
+ {/* Table */}
+
+
+
+
+
+
+ Name
+ Version
+ Status
+ {isLoggedIn && Action}
+
+
+
+ {loading ? (
+
+
+ Loading...
+
+
+ ) : Object.keys(errPackages).length === 0 ? (
+
+
+ No packages found.
+
+
+ ) : (
+ errPackages.map((pkg) => {
+ const firstPackage = Object.values(pkg.Packages)[0];
+ return (
+
+
+
+
+
+ {pkg.Name}
+
+
+ {firstPackage.Status}
+
+
+
+
+ {firstPackage.Version}
+
+ {firstPackage.NewVersion && (
+
+ → {firstPackage.NewVersion}
+
+ )}
+
+
+
+
+
+
+ {firstPackage.Version}
+
+ {firstPackage.NewVersion && (
+
+ → {firstPackage.NewVersion}
+
+ )}
+
+
+
+
+ {firstPackage.Status}
+
+
+ {isLoggedIn && (
+
+
+
+ )}
+
+ );
+ })
+ )}
+
+
+
+
+
+
+ {/* Rebuild Dialog */}
+
+
+ );
+};
+
+export default Errored;
\ No newline at end of file
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..32053fc
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "../../lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/hooks/usePackageData.ts b/src/hooks/usePackageData.ts
index 57669b6..0cf4b93 100644
--- a/src/hooks/usePackageData.ts
+++ b/src/hooks/usePackageData.ts
@@ -34,8 +34,10 @@ const usePackageData = () => {
const [stats, setStats] = useState(null);
const [lastUpdated, setLastUpdated] = useState('');
const [packages, setPackages] = useState([]);
+ const [pkg, setPackage] = useState(null);
const [totalCount, setTotalCount] = useState(0);
const [loading, setLoading] = useState(false);
+ const [errPackages, setErrPackages] = useState([]);
const fetchStatsAttempted = useRef(false);
const fetchStats = useCallback(async () => {
@@ -83,6 +85,34 @@ const usePackageData = () => {
setLoading(false);
}
}, []);
+
+ const fetchPackage = useCallback(async (name: string) => {
+ setLoading(true);
+ try {
+ const response = await fetch(`/api/package/${name}`);
+ const data: PackageData = await response.json();
+ setPackage(data);
+ } catch (error) {
+ console.error("Error fetching package:", error);
+ setPackage(null);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ const fetchErrPackages = useCallback(async () => {
+ setLoading(true);
+ try {
+ const response = await fetch('/api/errpackages');
+ const data: PackageData[] = await response.json();
+ setErrPackages(data);
+ } catch (error) {
+ console.error("Error fetching errpackages:", error);
+ setErrPackages([]);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
return {
stats,
@@ -92,6 +122,10 @@ const usePackageData = () => {
totalCount,
loading,
fetchPackages,
+ pkg,
+ fetchPackage,
+ errPackages,
+ fetchErrPackages,
};
};