/* * Copyright (c) 2021-2026 ahriman team. * * This file is part of ahriman * (see https://github.com/arcan1s/ahriman). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { Box, Dialog, DialogContent, Tab, Tabs } from "@mui/material"; import { skipToken, useQuery, useQueryClient } from "@tanstack/react-query"; import { ApiError } from "api/client/ApiError"; import DialogHeader from "components/common/DialogHeader"; import BuildLogsTab from "components/package/BuildLogsTab"; import ChangesTab from "components/package/ChangesTab"; import EventsTab from "components/package/EventsTab"; import PackageDetailsGrid from "components/package/PackageDetailsGrid"; import PackageInfoActions from "components/package/PackageInfoActions"; import PackagePatchesList from "components/package/PackagePatchesList"; import { QueryKeys } from "hooks/QueryKeys"; import { useAuth } from "hooks/useAuth"; import { useAutoRefresh } from "hooks/useAutoRefresh"; import { useClient } from "hooks/useClient"; import { useNotification } from "hooks/useNotification"; import { useRepository } from "hooks/useRepository"; import type { AutoRefreshInterval } from "models/AutoRefreshInterval"; import type { Dependencies } from "models/Dependencies"; import type { PackageStatus } from "models/PackageStatus"; import type { Patch } from "models/Patch"; import React, { useState } from "react"; import { StatusHeaderStyles } from "theme/StatusColors"; import { defaultInterval } from "utils"; interface PackageInfoDialogProps { packageBase: string | null; open: boolean; onClose: () => void; autoRefreshIntervals: AutoRefreshInterval[]; } export default function PackageInfoDialog({ packageBase, open, onClose, autoRefreshIntervals, }: PackageInfoDialogProps): React.JSX.Element { const client = useClient(); const { current } = useRepository(); const { isAuthorized } = useAuth(); const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const [localPackageBase, setLocalPackageBase] = useState(packageBase); if (packageBase !== null && packageBase !== localPackageBase) { setLocalPackageBase(packageBase); } const [tabIndex, setTabIndex] = useState(0); const [refreshDatabase, setRefreshDatabase] = useState(true); const handleClose = (): void => { setTabIndex(0); setRefreshDatabase(true); onClose(); }; const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval(autoRefreshIntervals)); const { data: packageData } = useQuery({ queryKey: localPackageBase && current ? QueryKeys.package(localPackageBase, current) : ["packages"], queryFn: localPackageBase && current ? () => client.fetch.fetchPackage(localPackageBase, current) : skipToken, enabled: open, refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false, }); const { data: dependencies } = useQuery({ queryKey: localPackageBase && current ? QueryKeys.dependencies(localPackageBase, current) : ["dependencies"], queryFn: localPackageBase && current ? () => client.fetch.fetchPackageDependencies(localPackageBase, current) : skipToken, enabled: open, }); const { data: patches = [] } = useQuery({ queryKey: localPackageBase ? QueryKeys.patches(localPackageBase) : ["patches"], queryFn: localPackageBase ? () => client.fetch.fetchPackagePatches(localPackageBase) : skipToken, enabled: open, }); const description: PackageStatus | undefined = packageData?.[0]; const pkg = description?.package; const status = description?.status; const headerStyle = status ? StatusHeaderStyles[status.status] : {}; const handleUpdate: () => Promise = async () => { if (!localPackageBase || !current) { return; } try { await client.service.servicePackageAdd(current, { packages: [localPackageBase], refresh: refreshDatabase }); showSuccess("Success", `Run update for packages ${localPackageBase}`); } catch (exception) { showError("Action failed", `Package update failed: ${ApiError.errorDetail(exception)}`); } }; const handleRemove: () => Promise = async () => { if (!localPackageBase || !current) { return; } try { await client.service.servicePackageRemove(current, [localPackageBase]); showSuccess("Success", `Packages ${localPackageBase} have been removed`); onClose(); } catch (exception) { showError("Action failed", `Could not remove package: ${ApiError.errorDetail(exception)}`); } }; const handleDeletePatch: (key: string) => Promise = async key => { if (!localPackageBase) { return; } try { await client.service.servicePackagePatchRemove(localPackageBase, key); void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(localPackageBase) }); } catch (exception) { showError("Action failed", `Could not delete variable: ${ApiError.errorDetail(exception)}`); } }; return {pkg && status ? `${pkg.base} ${status.status} at ${new Date(status.timestamp * 1000).toISOStringShort()}` : localPackageBase ?? ""} {pkg && <> void handleDeletePatch(key)} /> setTabIndex(index)}> {tabIndex === 0 && localPackageBase && current && } {tabIndex === 1 && localPackageBase && current && } {tabIndex === 2 && localPackageBase && current && } } void handleUpdate()} onRemove={() => void handleRemove()} autoRefreshIntervals={autoRefreshIntervals} autoRefreshInterval={autoRefresh.interval} onAutoRefreshIntervalChange={autoRefresh.setInterval} /> ; }