mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-10-25 04:45:01 +00:00
This changes url handling in status page. Firstly, instead of using hash for repository, now they are handled by query parameter. Secondly, it adds a query parameter for package base. And lastly, it changes behaviour for clicking on the package link, now it opens status page, which allows to e.g. open a tab in background
217 lines
9.5 KiB
TypeScript
217 lines
9.5 KiB
TypeScript
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import DeleteIcon from "@mui/icons-material/Delete";
|
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
import { Box, Link } from "@mui/material";
|
|
import {
|
|
DataGrid,
|
|
GRID_CHECKBOX_SELECTION_COL_DEF,
|
|
GridActionsCellItem,
|
|
type GridColDef,
|
|
type GridFilterModel,
|
|
type GridRowId,
|
|
useGridApiRef,
|
|
} from "@mui/x-data-grid";
|
|
import DashboardDialog from "components/dialogs/DashboardDialog";
|
|
import KeyImportDialog from "components/dialogs/KeyImportDialog";
|
|
import PackageAddDialog from "components/dialogs/PackageAddDialog";
|
|
import PackageInfoDialog from "components/dialogs/PackageInfoDialog";
|
|
import PackageRebuildDialog from "components/dialogs/PackageRebuildDialog";
|
|
import PackageTableToolbar from "components/table/PackageTableToolbar";
|
|
import StatusCell from "components/table/StatusCell";
|
|
import { useDebounce } from "hooks/useDebounce";
|
|
import { navigate, packageHref } from "hooks/useLocationQuery";
|
|
import { usePackageTable } from "hooks/usePackageTable";
|
|
import { useRepository } from "hooks/useRepository";
|
|
import type { PackageRow } from "models/PackageRow";
|
|
import React, { useMemo } from "react";
|
|
|
|
function createListColumn(
|
|
field: keyof PackageRow,
|
|
headerName: string,
|
|
options: { flex?: number; minWidth?: number; width?: number },
|
|
): GridColDef<PackageRow> {
|
|
return {
|
|
field,
|
|
headerName,
|
|
...options,
|
|
renderCell: params =>
|
|
<Box sx={{ whiteSpace: "pre-line" }}>{((params.row[field] as string[]) ?? []).join("\n")}</Box>,
|
|
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
|
valueGetter: (value: string[]) => (value ?? []).join(" "),
|
|
};
|
|
}
|
|
|
|
export default function PackageTable(): React.JSX.Element {
|
|
const table = usePackageTable();
|
|
const { currentRepository } = useRepository();
|
|
const { handleRemove, handleUpdate, isAuthorized } = table;
|
|
const apiRef = useGridApiRef();
|
|
const debouncedSearch = useDebounce(table.searchText, 300);
|
|
|
|
const effectiveFilterModel: GridFilterModel = useMemo(
|
|
() => ({
|
|
...table.filterModel,
|
|
quickFilterValues: debouncedSearch ? debouncedSearch.split(/\s+/) : undefined,
|
|
}),
|
|
[table.filterModel, debouncedSearch],
|
|
);
|
|
|
|
const columns: GridColDef<PackageRow>[] = useMemo(
|
|
() => [
|
|
{
|
|
field: "base",
|
|
flex: 1,
|
|
headerName: "package base",
|
|
minWidth: 150,
|
|
renderCell: params =>
|
|
<Link
|
|
href={packageHref(currentRepository?.key ?? "", params.row.base)}
|
|
onClick={event => {
|
|
const modified = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
|
|
if (event.button === 0 && !modified) {
|
|
event.preventDefault();
|
|
navigate(packageHref(currentRepository?.key ?? "", params.row.base));
|
|
}
|
|
}}
|
|
tabIndex={params.tabIndex}
|
|
underline="hover"
|
|
>
|
|
{params.value as string}
|
|
</Link>,
|
|
},
|
|
{ align: "right", field: "version", headerAlign: "right", headerName: "version", width: 180 },
|
|
createListColumn("packages", "packages", { flex: 1, minWidth: 120 }),
|
|
createListColumn("groups", "groups", { width: 150 }),
|
|
createListColumn("licenses", "licenses", { width: 150 }),
|
|
{ field: "packager", headerName: "packager", width: 150 },
|
|
{ align: "right", field: "timestamp", headerName: "last update", headerAlign: "right", width: 180 },
|
|
{
|
|
align: "center",
|
|
field: "status",
|
|
headerAlign: "center",
|
|
headerName: "status",
|
|
renderCell: params =>
|
|
<StatusCell isHeld={params.row.isHeld} status={params.row.status} />,
|
|
width: 120,
|
|
},
|
|
...isAuthorized ? [{
|
|
field: "actions",
|
|
type: "actions",
|
|
headerName: "actions",
|
|
width: 100,
|
|
disableExport: true,
|
|
getActions: ({ row }) => [
|
|
<GridActionsCellItem
|
|
key="update"
|
|
icon={<PlayArrowIcon />}
|
|
label={`Update ${row.base}`}
|
|
title={`Update ${row.base}`}
|
|
onClick={() => void handleUpdate([row.base])}
|
|
/>,
|
|
<GridActionsCellItem
|
|
key="remove"
|
|
icon={<DeleteIcon />}
|
|
label={`Remove ${row.base}`}
|
|
title={`Remove ${row.base}`}
|
|
onClick={() => void handleRemove([row.base])}
|
|
/>,
|
|
],
|
|
} satisfies GridColDef<PackageRow>] : [],
|
|
],
|
|
[currentRepository, handleRemove, handleUpdate, isAuthorized],
|
|
);
|
|
|
|
return <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
|
<PackageTableToolbar
|
|
actions={{
|
|
onAddClick: () => table.setDialogOpen("add"),
|
|
onDashboardClick: () => table.setDialogOpen("dashboard"),
|
|
onExportClick: () => apiRef.current?.exportDataAsCsv(),
|
|
onKeyImportClick: () => table.setDialogOpen("keyImport"),
|
|
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
|
onRefreshDatabaseClick: () => void table.handleRefreshDatabase(),
|
|
onReloadClick: table.handleReload,
|
|
onRemoveClick: () => void table.handleRemove(table.selectionModel)
|
|
.then(() => table.setSelectionModel([])),
|
|
onUpdateClick: () => void table.handleUpdate(table.selectionModel)
|
|
.then(() => table.setSelectionModel([])),
|
|
}}
|
|
isAuthorized={table.isAuthorized}
|
|
onSearchChange={table.setSearchText}
|
|
searchText={table.searchText}
|
|
selectedPackageCount={table.selectionModel.length}
|
|
status={table.status}
|
|
/>
|
|
|
|
<DataGrid
|
|
apiRef={apiRef}
|
|
checkboxSelection
|
|
localeText={{ checkboxSelectionHeaderName: "selection" }}
|
|
columnVisibilityModel={table.columnVisibility}
|
|
columns={columns}
|
|
density="compact"
|
|
disableRowSelectionOnClick
|
|
filterModel={effectiveFilterModel}
|
|
getRowHeight={() => "auto"}
|
|
initialState={{
|
|
sorting: { sortModel: [{ field: "base", sort: "asc" }] },
|
|
}}
|
|
loading={table.isLoading}
|
|
onCellClick={(params, event) => {
|
|
// Don't open info dialog when clicking checkbox, actions, or link
|
|
if (params.field === GRID_CHECKBOX_SELECTION_COL_DEF.field || params.field === "actions") {
|
|
return;
|
|
}
|
|
if ((event.target as HTMLElement).closest("a")) {
|
|
return;
|
|
}
|
|
table.setSelectedPackage(String(params.id));
|
|
}}
|
|
onColumnVisibilityModelChange={table.setColumnVisibility}
|
|
onFilterModelChange={table.setFilterModel}
|
|
onPaginationModelChange={table.setPaginationModel}
|
|
onRowSelectionModelChange={model => {
|
|
if (model.type === "exclude") {
|
|
const excludeIds = new Set([...model.ids].map(String));
|
|
table.setSelectionModel(table.rows.map(row => row.id).filter(id => !excludeIds.has(id)));
|
|
} else {
|
|
table.setSelectionModel([...model.ids].map(String));
|
|
}
|
|
}}
|
|
paginationModel={table.paginationModel}
|
|
rowSelectionModel={{ type: "include", ids: new Set<GridRowId>(table.selectionModel) }}
|
|
rows={table.rows}
|
|
sx={{ flex: 1 }}
|
|
/>
|
|
|
|
<DashboardDialog onClose={() => table.setDialogOpen(null)} open={table.dialogOpen === "dashboard"} />
|
|
<PackageAddDialog onClose={() => table.setDialogOpen(null)} open={table.dialogOpen === "add"} />
|
|
<PackageRebuildDialog onClose={() => table.setDialogOpen(null)} open={table.dialogOpen === "rebuild"} />
|
|
<KeyImportDialog onClose={() => table.setDialogOpen(null)} open={table.dialogOpen === "keyImport"} />
|
|
<PackageInfoDialog
|
|
key={`${currentRepository?.key}/${table.selectedPackage}`}
|
|
onClose={() => table.setSelectedPackage(null)}
|
|
open={table.selectedPackage !== null}
|
|
packageBase={table.selectedPackage}
|
|
/>
|
|
</Box>;
|
|
}
|