mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 11:03:37 +00:00
upload ai slop
This commit is contained in:
299
frontend/src/components/dialogs/PackageInfoDialog.tsx
Normal file
299
frontend/src/components/dialogs/PackageInfoDialog.tsx
Normal file
@@ -0,0 +1,299 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Dialog, DialogTitle, DialogContent, DialogActions, Button,
|
||||
Grid, Typography, Link, Box, Tab, Tabs, IconButton, Chip, FormControlLabel, Checkbox,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import BuildLogsTab from "components/package/BuildLogsTab";
|
||||
import ChangesTab from "components/package/ChangesTab";
|
||||
import EventsTab from "components/package/EventsTab";
|
||||
import AutoRefreshControl from "components/common/AutoRefreshControl";
|
||||
import { useRepository } from "hooks/useRepository";
|
||||
import { useAuth } from "hooks/useAuth";
|
||||
import { useNotification } from "hooks/useNotification";
|
||||
import { useAutoRefresh } from "hooks/useAutoRefresh";
|
||||
import { Client } from "api/client/AhrimanClient";
|
||||
import { ApiError } from "api/client/ApiError";
|
||||
import { QueryKeys } from "api/QueryKeys";
|
||||
import { StatusHeaderStyles } from "theme/status/StatusHeaderStyles";
|
||||
import { formatTimestamp } from "components/common/formatTimestamp";
|
||||
import type { AutoRefreshInterval } from "api/types/AutoRefreshInterval";
|
||||
import type { Dependencies } from "api/types/Dependencies";
|
||||
import type { PackageProperties } from "api/types/PackageProperties";
|
||||
import type { PackageStatus } from "api/types/PackageStatus";
|
||||
import type { Patch } from "api/types/Patch";
|
||||
import type { RepositoryId } from "api/types/RepositoryId";
|
||||
|
||||
interface PackageInfoDialogProps {
|
||||
packageBase: string | null;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
autorefreshIntervals: AutoRefreshInterval[];
|
||||
}
|
||||
|
||||
function listToString(items: string[]): React.ReactNode {
|
||||
const unique = [...new Set(items)].sort();
|
||||
return unique.map((item, i) => (
|
||||
<React.Fragment key={item}>
|
||||
{item}
|
||||
{i < unique.length - 1 && <br />}
|
||||
</React.Fragment>
|
||||
));
|
||||
}
|
||||
|
||||
export default function PackageInfoDialog({ packageBase, open, onClose, autorefreshIntervals }: PackageInfoDialogProps): React.JSX.Element {
|
||||
const { current } = useRepository();
|
||||
const { enabled: authEnabled, username } = useAuth();
|
||||
const { showSuccess, showError } = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
const hasAuth = !authEnabled || username !== null;
|
||||
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [refreshDb, setRefreshDb] = useState(true);
|
||||
|
||||
const defaultInterval = autorefreshIntervals.find((i) => i.is_active)?.interval ?? 0;
|
||||
const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval);
|
||||
|
||||
const repo = current as RepositoryId;
|
||||
|
||||
const { data: packageData } = useQuery<PackageStatus[]>({
|
||||
queryKey: packageBase && repo ? QueryKeys.package(packageBase, repo) : ["package-none"],
|
||||
queryFn: () => Client.fetchPackage(packageBase!, repo),
|
||||
enabled: !!packageBase && !!repo && open,
|
||||
refetchInterval: autoRefresh.refetchInterval,
|
||||
});
|
||||
|
||||
const { data: dependencies } = useQuery<Dependencies>({
|
||||
queryKey: packageBase && repo ? QueryKeys.dependencies(packageBase, repo) : ["deps-none"],
|
||||
queryFn: () => Client.fetchDependencies(packageBase!, repo),
|
||||
enabled: !!packageBase && !!repo && open,
|
||||
});
|
||||
|
||||
const { data: patches = [] } = useQuery<Patch[]>({
|
||||
queryKey: packageBase ? QueryKeys.patches(packageBase) : ["patches-none"],
|
||||
queryFn: () => Client.fetchPatches(packageBase!),
|
||||
enabled: !!packageBase && open,
|
||||
});
|
||||
|
||||
const description: PackageStatus | undefined = packageData?.[0];
|
||||
const pkg = description?.package;
|
||||
const status = description?.status;
|
||||
|
||||
const headerStyle = status ? StatusHeaderStyles[status.status] : {};
|
||||
|
||||
// Flatten depends from all sub-packages
|
||||
const allDepends: string[] = pkg
|
||||
? Object.values(pkg.packages).flatMap((p: PackageProperties) => {
|
||||
const pkgNames = Object.keys(pkg.packages);
|
||||
const deps = (p.depends ?? []).filter((d: string) => !pkgNames.includes(d));
|
||||
const makeDeps = (p.make_depends ?? []).filter((d: string) => !pkgNames.includes(d)).map((d: string) => `${d} (make)`);
|
||||
const optDeps = (p.opt_depends ?? []).filter((d: string) => !pkgNames.includes(d)).map((d: string) => `${d} (optional)`);
|
||||
return [...deps, ...makeDeps, ...optDeps];
|
||||
})
|
||||
: [];
|
||||
|
||||
const implicitDepends: string[] = dependencies
|
||||
? Object.values(dependencies.paths).flat()
|
||||
: [];
|
||||
|
||||
const groups: string[] = pkg
|
||||
? Object.values(pkg.packages).flatMap((p: PackageProperties) => p.groups ?? [])
|
||||
: [];
|
||||
|
||||
const licenses: string[] = pkg
|
||||
? Object.values(pkg.packages).flatMap((p: PackageProperties) => p.licenses ?? [])
|
||||
: [];
|
||||
|
||||
const upstreamUrls: string[] = pkg
|
||||
? [...new Set(Object.values(pkg.packages).map((p: PackageProperties) => p.url).filter((u): u is string => !!u))].sort()
|
||||
: [];
|
||||
|
||||
const aurUrl = pkg?.remote.web_url;
|
||||
|
||||
const packagesList: string[] = pkg
|
||||
? Object.entries(pkg.packages).map(([name, p]) => `${name}${p.description ? ` (${p.description})` : ""}`)
|
||||
: [];
|
||||
|
||||
const handleUpdate = async (): Promise<void> => {
|
||||
if (!packageBase || !repo) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await Client.addPackages(repo, { packages: [packageBase], refresh: refreshDb });
|
||||
showSuccess("Success", `Run update for packages ${packageBase}`);
|
||||
} catch (e) {
|
||||
const detail = e instanceof ApiError ? e.detail : String(e);
|
||||
showError("Action failed", `Package update failed: ${detail}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async (): Promise<void> => {
|
||||
if (!packageBase || !repo) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await Client.removePackages(repo, [packageBase]);
|
||||
showSuccess("Success", `Packages ${packageBase} have been removed`);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
const detail = e instanceof ApiError ? e.detail : String(e);
|
||||
showError("Action failed", `Could not remove package: ${detail}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePatch = async (key: string): Promise<void> => {
|
||||
if (!packageBase) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await Client.deletePatch(packageBase, key);
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(packageBase) });
|
||||
} catch (e) {
|
||||
const detail = e instanceof ApiError ? e.detail : String(e);
|
||||
showError("Action failed", `Could not delete variable: ${detail}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReload = (): void => {
|
||||
if (!packageBase || !repo) {
|
||||
return;
|
||||
}
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.package(packageBase, repo) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.logs(packageBase, repo) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.changes(packageBase, repo) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.events(repo, packageBase) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.dependencies(packageBase, repo) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(packageBase) });
|
||||
};
|
||||
|
||||
const handleClose = (): void => {
|
||||
setTabIndex(0);
|
||||
setRefreshDb(true);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="lg" fullWidth>
|
||||
<DialogTitle sx={headerStyle}>
|
||||
{pkg && status
|
||||
? `${pkg.base} ${status.status} at ${formatTimestamp(status.timestamp)}`
|
||||
: packageBase ?? ""}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{pkg && (
|
||||
<>
|
||||
<Grid container spacing={1} sx={{ mt: 1 }}>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">packages</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{listToString(packagesList)}</Typography></Grid>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">version</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{pkg.version}</Typography></Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">packager</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{pkg.packager ?? ""}</Typography></Grid>
|
||||
<Grid item xs={4} md={1} />
|
||||
<Grid item xs={8} md={5} />
|
||||
</Grid>
|
||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">groups</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{listToString(groups)}</Typography></Grid>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">licenses</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{listToString(licenses)}</Typography></Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">upstream</Typography></Grid>
|
||||
<Grid item xs={8} md={5}>
|
||||
{upstreamUrls.map((url) => (
|
||||
<Link key={url} href={url} target="_blank" rel="noopener" underline="hover" display="block" variant="body2">
|
||||
{url}
|
||||
</Link>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">AUR</Typography></Grid>
|
||||
<Grid item xs={8} md={5}>
|
||||
{aurUrl && (
|
||||
<Link href={aurUrl} target="_blank" rel="noopener" underline="hover" variant="body2">AUR link</Link>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">depends</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{listToString(allDepends)}</Typography></Grid>
|
||||
<Grid item xs={4} md={1}><Typography variant="body2" color="text.secondary" align="right">implicitly depends</Typography></Grid>
|
||||
<Grid item xs={8} md={5}><Typography variant="body2">{listToString(implicitDepends)}</Typography></Grid>
|
||||
</Grid>
|
||||
|
||||
{patches.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>Environment variables</Typography>
|
||||
{patches.map((patch) => (
|
||||
<Box key={patch.key} sx={{ display: "flex", alignItems: "center", gap: 1, mb: 0.5 }}>
|
||||
<Chip label={patch.key} size="small" />
|
||||
<Typography variant="body2">=</Typography>
|
||||
<Typography variant="body2" sx={{ fontFamily: "monospace" }}>{JSON.stringify(patch.value)}</Typography>
|
||||
{hasAuth && (
|
||||
<IconButton size="small" color="error" onClick={() => void handleDeletePatch(patch.key)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider", mt: 2 }}>
|
||||
<Tabs value={tabIndex} onChange={(_, v: number) => setTabIndex(v)}>
|
||||
<Tab label="Build logs" />
|
||||
<Tab label="Changes" />
|
||||
<Tab label="Events" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{tabIndex === 0 && packageBase && repo && (
|
||||
<BuildLogsTab packageBase={packageBase} repo={repo} refetchInterval={autoRefresh.refetchInterval} />
|
||||
)}
|
||||
{tabIndex === 1 && packageBase && repo && (
|
||||
<ChangesTab packageBase={packageBase} repo={repo} />
|
||||
)}
|
||||
{tabIndex === 2 && packageBase && repo && (
|
||||
<EventsTab packageBase={packageBase} repo={repo} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ flexWrap: "wrap", gap: 1 }}>
|
||||
{hasAuth && (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={refreshDb} onChange={(_, checked) => setRefreshDb(checked)} size="small" />}
|
||||
label="update pacman databases"
|
||||
/>
|
||||
<Button onClick={() => void handleUpdate()} variant="contained" color="success" startIcon={<PlayArrowIcon />} size="small">
|
||||
update
|
||||
</Button>
|
||||
<Button onClick={() => void handleRemove()} variant="contained" color="error" startIcon={<DeleteIcon />} size="small">
|
||||
remove
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={handleReload} variant="outlined" color="secondary" startIcon={<RefreshIcon />} size="small">
|
||||
reload
|
||||
</Button>
|
||||
<AutoRefreshControl
|
||||
intervals={autorefreshIntervals}
|
||||
enabled={autoRefresh.enabled}
|
||||
currentInterval={autoRefresh.interval}
|
||||
onToggle={autoRefresh.setEnabled}
|
||||
onIntervalChange={autoRefresh.setInterval}
|
||||
/>
|
||||
<Button onClick={handleClose} variant="contained" startIcon={<CloseIcon />} size="small">
|
||||
close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user