mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 11:03:37 +00:00
refactor: reorder arguments in web ui
This commit is contained in:
@@ -23,7 +23,6 @@ import {
|
||||
GRID_CHECKBOX_SELECTION_COL_DEF,
|
||||
type GridColDef,
|
||||
type GridFilterModel,
|
||||
type GridRenderCellParams,
|
||||
type GridRowId,
|
||||
useGridApiRef,
|
||||
} from "@mui/x-data-grid";
|
||||
@@ -44,8 +43,6 @@ interface PackageTableProps {
|
||||
autoRefreshIntervals: AutoRefreshInterval[];
|
||||
}
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
||||
|
||||
function createListColumn(
|
||||
field: keyof PackageRow,
|
||||
headerName: string,
|
||||
@@ -55,10 +52,10 @@ function createListColumn(
|
||||
field,
|
||||
headerName,
|
||||
...options,
|
||||
valueGetter: (value: string[]) => (value ?? []).join(" "),
|
||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
||||
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(" "),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,36 +76,30 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
||||
() => [
|
||||
{
|
||||
field: "base",
|
||||
headerName: "package base",
|
||||
flex: 1,
|
||||
headerName: "package base",
|
||||
minWidth: 150,
|
||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
||||
renderCell: params =>
|
||||
params.row.webUrl ?
|
||||
<Link href={params.row.webUrl} target="_blank" rel="noopener noreferrer" underline="hover">
|
||||
<Link href={params.row.webUrl} rel="noopener noreferrer" target="_blank" underline="hover">
|
||||
{params.value as string}
|
||||
</Link>
|
||||
: params.value as string,
|
||||
},
|
||||
{ field: "version", headerName: "version", width: 180, align: "right", headerAlign: "right" },
|
||||
{ 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 },
|
||||
{
|
||||
field: "timestamp",
|
||||
headerName: "last update",
|
||||
width: 180,
|
||||
align: "right",
|
||||
headerAlign: "right",
|
||||
},
|
||||
{
|
||||
field: "status",
|
||||
headerName: "status",
|
||||
width: 120,
|
||||
align: "center",
|
||||
field: "status",
|
||||
headerAlign: "center",
|
||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
||||
<StatusCell status={params.row.status} isHeld={params.row.isHeld} />,
|
||||
headerName: "status",
|
||||
renderCell: params =>
|
||||
<StatusCell isHeld={params.row.isHeld} status={params.row.status} />,
|
||||
width: 120,
|
||||
},
|
||||
],
|
||||
[],
|
||||
@@ -116,56 +107,42 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
||||
|
||||
return <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
<PackageTableToolbar
|
||||
hasSelection={table.selectionModel.length > 0}
|
||||
isAuthorized={table.isAuthorized}
|
||||
status={table.status}
|
||||
searchText={table.searchText}
|
||||
onSearchChange={table.setSearchText}
|
||||
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(),
|
||||
onUpdateClick: () => void table.handleUpdate(),
|
||||
}}
|
||||
autoRefresh={{
|
||||
autoRefreshIntervals,
|
||||
currentInterval: table.autoRefreshInterval,
|
||||
onIntervalChange: table.onAutoRefreshIntervalChange,
|
||||
}}
|
||||
actions={{
|
||||
onDashboardClick: () => table.setDialogOpen("dashboard"),
|
||||
onAddClick: () => table.setDialogOpen("add"),
|
||||
onUpdateClick: () => void table.handleUpdate(),
|
||||
onRefreshDatabaseClick: () => void table.handleRefreshDatabase(),
|
||||
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
||||
onRemoveClick: () => void table.handleRemove(),
|
||||
onKeyImportClick: () => table.setDialogOpen("keyImport"),
|
||||
onReloadClick: table.handleReload,
|
||||
onExportClick: () => apiRef.current?.exportDataAsCsv(),
|
||||
}}
|
||||
isAuthorized={table.isAuthorized}
|
||||
hasSelection={table.selectionModel.length > 0}
|
||||
onSearchChange={table.setSearchText}
|
||||
searchText={table.searchText}
|
||||
status={table.status}
|
||||
/>
|
||||
|
||||
<DataGrid
|
||||
apiRef={apiRef}
|
||||
rows={table.rows}
|
||||
columns={columns}
|
||||
loading={table.isLoading}
|
||||
getRowHeight={() => "auto"}
|
||||
checkboxSelection
|
||||
disableRowSelectionOnClick
|
||||
rowSelectionModel={{ type: "include", ids: new Set<GridRowId>(table.selectionModel) }}
|
||||
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}
|
||||
onPaginationModelChange={table.setPaginationModel}
|
||||
pageSizeOptions={PAGE_SIZE_OPTIONS}
|
||||
columnVisibilityModel={table.columnVisibility}
|
||||
onColumnVisibilityModelChange={table.setColumnVisibility}
|
||||
columns={columns}
|
||||
density="compact"
|
||||
disableRowSelectionOnClick
|
||||
filterModel={effectiveFilterModel}
|
||||
onFilterModelChange={table.setFilterModel}
|
||||
getRowHeight={() => "auto"}
|
||||
initialState={{
|
||||
sorting: { sortModel: [{ field: "base", sort: "asc" }] },
|
||||
}}
|
||||
loading={table.isLoading}
|
||||
onCellClick={(params, event) => {
|
||||
// Don't open info dialog when clicking checkbox or link
|
||||
if (params.field === GRID_CHECKBOX_SELECTION_COL_DEF.field) {
|
||||
@@ -176,22 +153,32 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
||||
}
|
||||
table.setSelectedPackage(String(params.id));
|
||||
}}
|
||||
sx={{
|
||||
flex: 1,
|
||||
"& .MuiDataGrid-row": { cursor: "pointer" },
|
||||
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));
|
||||
}
|
||||
}}
|
||||
density="compact"
|
||||
paginationModel={table.paginationModel}
|
||||
rowSelectionModel={{ type: "include", ids: new Set<GridRowId>(table.selectionModel) }}
|
||||
rows={table.rows}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
|
||||
<DashboardDialog open={table.dialogOpen === "dashboard"} onClose={() => table.setDialogOpen(null)} />
|
||||
<PackageAddDialog open={table.dialogOpen === "add"} onClose={() => table.setDialogOpen(null)} />
|
||||
<PackageRebuildDialog open={table.dialogOpen === "rebuild"} onClose={() => table.setDialogOpen(null)} />
|
||||
<KeyImportDialog open={table.dialogOpen === "keyImport"} onClose={() => table.setDialogOpen(null)} />
|
||||
<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
|
||||
packageBase={table.selectedPackage}
|
||||
open={table.selectedPackage !== null}
|
||||
onClose={() => table.setSelectedPackage(null)}
|
||||
autoRefreshIntervals={autoRefreshIntervals}
|
||||
onClose={() => table.setSelectedPackage(null)}
|
||||
open={table.selectedPackage !== null}
|
||||
packageBase={table.selectedPackage}
|
||||
/>
|
||||
</Box>;
|
||||
}
|
||||
|
||||
@@ -43,47 +43,47 @@ export interface AutoRefreshProps {
|
||||
}
|
||||
|
||||
export interface ToolbarActions {
|
||||
onDashboardClick: () => void;
|
||||
onAddClick: () => void;
|
||||
onUpdateClick: () => void;
|
||||
onRefreshDatabaseClick: () => void;
|
||||
onRebuildClick: () => void;
|
||||
onRemoveClick: () => void;
|
||||
onKeyImportClick: () => void;
|
||||
onReloadClick: () => void;
|
||||
onDashboardClick: () => void;
|
||||
onExportClick: () => void;
|
||||
onKeyImportClick: () => void;
|
||||
onRebuildClick: () => void;
|
||||
onRefreshDatabaseClick: () => void;
|
||||
onReloadClick: () => void;
|
||||
onRemoveClick: () => void;
|
||||
onUpdateClick: () => void;
|
||||
}
|
||||
|
||||
interface PackageTableToolbarProps {
|
||||
actions: ToolbarActions;
|
||||
autoRefresh: AutoRefreshProps;
|
||||
hasSelection: boolean;
|
||||
isAuthorized: boolean;
|
||||
status?: BuildStatus;
|
||||
searchText: string;
|
||||
onSearchChange: (text: string) => void;
|
||||
autoRefresh: AutoRefreshProps;
|
||||
actions: ToolbarActions;
|
||||
searchText: string;
|
||||
status?: BuildStatus;
|
||||
}
|
||||
|
||||
export default function PackageTableToolbar({
|
||||
actions,
|
||||
autoRefresh,
|
||||
hasSelection,
|
||||
isAuthorized,
|
||||
status,
|
||||
searchText,
|
||||
onSearchChange,
|
||||
autoRefresh,
|
||||
actions,
|
||||
searchText,
|
||||
status,
|
||||
}: PackageTableToolbarProps): React.JSX.Element {
|
||||
const [packagesAnchorEl, setPackagesAnchorEl] = useState<HTMLElement | null>(null);
|
||||
|
||||
return <Box sx={{ display: "flex", gap: 1, mb: 1, flexWrap: "wrap", alignItems: "center" }}>
|
||||
return <Box sx={{ alignItems: "center", display: "flex", flexWrap: "wrap", gap: 1, mb: 1 }}>
|
||||
<Tooltip title="System health">
|
||||
<IconButton
|
||||
aria-label="System health"
|
||||
onClick={actions.onDashboardClick}
|
||||
sx={{
|
||||
borderColor: status ? StatusColors[status] : undefined,
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
color: status ? StatusColors[status] : undefined,
|
||||
}}
|
||||
>
|
||||
@@ -94,16 +94,16 @@ export default function PackageTableToolbar({
|
||||
{isAuthorized &&
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<InventoryIcon />}
|
||||
onClick={event => setPackagesAnchorEl(event.currentTarget)}
|
||||
startIcon={<InventoryIcon />}
|
||||
variant="contained"
|
||||
>
|
||||
packages
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={packagesAnchorEl}
|
||||
open={Boolean(packagesAnchorEl)}
|
||||
onClose={() => setPackagesAnchorEl(null)}
|
||||
open={Boolean(packagesAnchorEl)}
|
||||
>
|
||||
<MenuItem onClick={() => {
|
||||
setPackagesAnchorEl(null); actions.onAddClick();
|
||||
@@ -126,58 +126,58 @@ export default function PackageTableToolbar({
|
||||
<ReplayIcon fontSize="small" sx={{ mr: 1 }} /> rebuild
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => {
|
||||
<MenuItem disabled={!hasSelection} onClick={() => {
|
||||
setPackagesAnchorEl(null); actions.onRemoveClick();
|
||||
}} disabled={!hasSelection}>
|
||||
}}>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> remove
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Button variant="contained" color="info" startIcon={<VpnKeyIcon />} onClick={actions.onKeyImportClick}>
|
||||
<Button color="info" onClick={actions.onKeyImportClick} startIcon={<VpnKeyIcon />} variant="contained">
|
||||
import key
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
<Button variant="outlined" color="secondary" startIcon={<RefreshIcon />} onClick={actions.onReloadClick}>
|
||||
<Button color="secondary" onClick={actions.onReloadClick} startIcon={<RefreshIcon />} variant="outlined">
|
||||
reload
|
||||
</Button>
|
||||
|
||||
<AutoRefreshControl
|
||||
intervals={autoRefresh.autoRefreshIntervals}
|
||||
currentInterval={autoRefresh.currentInterval}
|
||||
intervals={autoRefresh.autoRefreshIntervals}
|
||||
onIntervalChange={autoRefresh.onIntervalChange}
|
||||
/>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
aria-label="Search packages"
|
||||
placeholder="search packages..."
|
||||
value={searchText}
|
||||
onChange={event => onSearchChange(event.target.value)}
|
||||
placeholder="search packages..."
|
||||
size="small"
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: searchText ?
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label="Clear search" onClick={() => onSearchChange("")} size="small">
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
: undefined,
|
||||
startAdornment:
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
,
|
||||
endAdornment: searchText ?
|
||||
<InputAdornment position="end">
|
||||
<IconButton size="small" aria-label="Clear search" onClick={() => onSearchChange("")}>
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
sx={{ minWidth: 200 }}
|
||||
value={searchText}
|
||||
/>
|
||||
|
||||
<Tooltip title="Export CSV">
|
||||
<IconButton size="small" aria-label="Export CSV" onClick={actions.onExportClick}>
|
||||
<IconButton aria-label="Export CSV" onClick={actions.onExportClick} size="small">
|
||||
<FileDownloadIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -24,11 +24,11 @@ import type React from "react";
|
||||
import { StatusColors } from "theme/StatusColors";
|
||||
|
||||
interface StatusCellProps {
|
||||
status: BuildStatus;
|
||||
isHeld?: boolean;
|
||||
status: BuildStatus;
|
||||
}
|
||||
|
||||
export default function StatusCell({ status, isHeld }: StatusCellProps): React.JSX.Element {
|
||||
export default function StatusCell({ isHeld, status }: StatusCellProps): React.JSX.Element {
|
||||
return <Chip
|
||||
icon={isHeld ? <PauseCircleIcon /> : undefined}
|
||||
label={status}
|
||||
|
||||
Reference in New Issue
Block a user