mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-10-08 06:10:27 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16afb50c19 | ||
|
|
23c834e186 |
@@ -28,7 +28,6 @@ from ahriman import __version__
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.application.help_formatter import _HelpFormatter
|
||||
from ahriman.core.module_loader import implementations
|
||||
from ahriman.core.utils import enum_values
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
|
||||
|
||||
@@ -75,7 +74,7 @@ Start web service (requires additional configuration):
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path, default=Path("ahriman.pid"))
|
||||
parser.add_argument("--log-handler", help="explicit log handler specification. If none set, the handler will be "
|
||||
"guessed from environment",
|
||||
type=LogHandler, choices=enum_values(LogHandler))
|
||||
type=LogHandler, choices=tuple(LogHandler))
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||
parser.add_argument("--report", help="force enable or disable reporting to web service",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
|
||||
@@ -23,7 +23,7 @@ from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.application.handlers.update import Update
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import enum_values, extract_user
|
||||
from ahriman.core.utils import extract_user
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@@ -85,7 +85,7 @@ class Add(Handler):
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||
type=PackageSource, choices=tuple(PackageSource), default=PackageSource.Auto)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build",
|
||||
action="append")
|
||||
|
||||
@@ -22,7 +22,7 @@ import argparse
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import enum_values, extract_user
|
||||
from ahriman.core.utils import extract_user
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
@@ -88,7 +88,7 @@ class Rebuild(Handler):
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
|
||||
action="store_true")
|
||||
parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set",
|
||||
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
|
||||
type=BuildStatusEnum, choices=tuple(BuildStatusEnum))
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
return parser
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InitializeError, MissingArchitectureError
|
||||
from ahriman.core.utils import enum_values
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.sign_settings import SignSettings
|
||||
from ahriman.models.user import User
|
||||
@@ -112,7 +111,7 @@ class Setup(Handler):
|
||||
parser.add_argument("--server", help="server to be used for devtools. If none set, local files will be used")
|
||||
parser.add_argument("--sign-key", help="sign key id")
|
||||
parser.add_argument("--sign-target", help="sign options", action="append",
|
||||
type=SignSettings.from_option, choices=enum_values(SignSettings))
|
||||
type=SignSettings.from_option, choices=tuple(SignSettings))
|
||||
parser.add_argument("--web-port", help="port of the web service", type=int)
|
||||
parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications",
|
||||
type=Path)
|
||||
|
||||
@@ -28,7 +28,7 @@ from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import EventStatsPrinter, PackageStatsPrinter, RepositoryStatsPrinter
|
||||
from ahriman.core.utils import enum_values, pretty_datetime
|
||||
from ahriman.core.utils import pretty_datetime
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
@@ -85,7 +85,7 @@ class Statistics(Handler):
|
||||
parser.add_argument("package", help="fetch only events for the specified package", nargs="?")
|
||||
parser.add_argument("--chart", help="create updates chart and save it to the specified path", type=Path)
|
||||
parser.add_argument("-e", "--event", help="event type filter",
|
||||
type=EventType, choices=enum_values(EventType), default=EventType.PackageUpdated)
|
||||
type=EventType, choices=tuple(EventType), default=EventType.PackageUpdated)
|
||||
parser.add_argument("--from-date", help="only fetch events which are newer than the date")
|
||||
parser.add_argument("--limit", help="limit response by specified amount of events", type=int, default=-1)
|
||||
parser.add_argument("--offset", help="skip specified amount of events", type=int, default=0)
|
||||
|
||||
@@ -26,7 +26,6 @@ from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import PackagePrinter, StatusPrinter
|
||||
from ahriman.core.types import Comparable
|
||||
from ahriman.core.utils import enum_values
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@@ -93,7 +92,7 @@ class Status(Handler):
|
||||
parser.add_argument("--info", help="show additional package information",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.add_argument("-s", "--status", help="filter packages by status",
|
||||
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
|
||||
type=BuildStatusEnum, choices=tuple(BuildStatusEnum))
|
||||
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import argparse
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import enum_values
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@@ -97,7 +96,7 @@ class StatusUpdate(Handler):
|
||||
"If no packages supplied, service status will be updated",
|
||||
nargs="*")
|
||||
parser.add_argument("-s", "--status", help="new package build status",
|
||||
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||
type=BuildStatusEnum, choices=tuple(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||
parser.set_defaults(action=Action.Update, lock=None, quiet=True, report=False, unsafe=True)
|
||||
return parser
|
||||
|
||||
@@ -115,7 +114,7 @@ class StatusUpdate(Handler):
|
||||
parser = root.add_parser("repo-status-update", help="update repository status",
|
||||
description="update repository status on the status page")
|
||||
parser.add_argument("-s", "--status", help="new status",
|
||||
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||
type=BuildStatusEnum, choices=tuple(BuildStatusEnum), default=BuildStatusEnum.Success)
|
||||
parser.set_defaults(action=Action.Update, lock=None, package=[], quiet=True, report=False, unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import PasswordError
|
||||
from ahriman.core.formatters import UserPrinter
|
||||
from ahriman.core.utils import enum_values
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.user import User
|
||||
@@ -89,7 +88,7 @@ class Users(Handler):
|
||||
"-p", "--password", help="user password. Blank password will be treated as empty password, "
|
||||
"which is in particular must be used for OAuth2 authorization type.")
|
||||
parser.add_argument("-R", "--role", help="user access level",
|
||||
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
|
||||
type=UserAccess, choices=tuple(UserAccess), default=UserAccess.Read)
|
||||
parser.set_defaults(action=Action.Update, architecture="", exit_code=False, lock=None, quiet=True,
|
||||
report=False, repository="")
|
||||
return parser
|
||||
@@ -110,8 +109,7 @@ class Users(Handler):
|
||||
parser.add_argument("username", help="filter users by username", nargs="?")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
|
||||
action="store_true")
|
||||
parser.add_argument("-R", "--role", help="filter users by role", type=UserAccess,
|
||||
choices=enum_values(UserAccess))
|
||||
parser.add_argument("-R", "--role", help="filter users by role", type=UserAccess, choices=tuple(UserAccess))
|
||||
parser.set_defaults(action=Action.List, architecture="", lock=None, quiet=True, report=False, repository="",
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
@@ -31,7 +31,6 @@ import subprocess
|
||||
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping
|
||||
from dataclasses import asdict
|
||||
from enum import Enum
|
||||
from filelock import FileLock
|
||||
from pathlib import Path
|
||||
from pwd import getpwuid
|
||||
@@ -46,7 +45,6 @@ __all__ = [
|
||||
"check_output",
|
||||
"check_user",
|
||||
"dataclass_view",
|
||||
"enum_values",
|
||||
"extract_user",
|
||||
"filelock",
|
||||
"filter_json",
|
||||
@@ -242,19 +240,6 @@ def dataclass_view(instance: Any) -> dict[str, Any]:
|
||||
return asdict(instance, dict_factory=lambda fields: {key: value for key, value in fields if value is not None})
|
||||
|
||||
|
||||
def enum_values(enum: type[Enum]) -> list[str]:
|
||||
"""
|
||||
generate list of enumeration values from the source
|
||||
|
||||
Args:
|
||||
enum(type[Enum]): source enumeration class
|
||||
|
||||
Returns:
|
||||
list[str]: available enumeration values as string
|
||||
"""
|
||||
return [str(key.value) for key in enum] # explicit str conversion for typing
|
||||
|
||||
|
||||
def extract_user() -> str | None:
|
||||
"""
|
||||
extract user from system environment
|
||||
|
||||
@@ -221,15 +221,6 @@ def test_dataclass_view_without_none(package_ahriman: Package) -> None:
|
||||
assert Package.from_json(result) == package_ahriman
|
||||
|
||||
|
||||
def test_enum_values() -> None:
|
||||
"""
|
||||
must correctly generate choices from enumeration classes
|
||||
"""
|
||||
values = enum_values(PackageSource)
|
||||
for value in values:
|
||||
assert PackageSource(value).value == value
|
||||
|
||||
|
||||
def test_extract_user() -> None:
|
||||
"""
|
||||
must extract user from system environment
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
* 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,
|
||||
@@ -56,6 +59,7 @@ function createListColumn(
|
||||
|
||||
export default function PackageTable(): React.JSX.Element {
|
||||
const table = usePackageTable();
|
||||
const { handleRemove, handleUpdate, isAuthorized } = table;
|
||||
const apiRef = useGridApiRef();
|
||||
const debouncedSearch = useDebounce(table.searchText, 300);
|
||||
|
||||
@@ -96,8 +100,31 @@ export default function PackageTable(): React.JSX.Element {
|
||||
<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>] : [],
|
||||
],
|
||||
[],
|
||||
[handleRemove, handleUpdate, isAuthorized],
|
||||
);
|
||||
|
||||
return <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
@@ -110,8 +137,10 @@ export default function PackageTable(): React.JSX.Element {
|
||||
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
||||
onRefreshDatabaseClick: () => void table.handleRefreshDatabase(),
|
||||
onReloadClick: table.handleReload,
|
||||
onRemoveClick: () => void table.handleRemove(),
|
||||
onUpdateClick: () => void table.handleUpdate(),
|
||||
onRemoveClick: () => void table.handleRemove(table.selectionModel)
|
||||
.then(() => table.setSelectionModel([])),
|
||||
onUpdateClick: () => void table.handleUpdate(table.selectionModel)
|
||||
.then(() => table.setSelectionModel([])),
|
||||
}}
|
||||
isAuthorized={table.isAuthorized}
|
||||
hasSelection={table.selectionModel.length > 0}
|
||||
@@ -123,6 +152,7 @@ export default function PackageTable(): React.JSX.Element {
|
||||
<DataGrid
|
||||
apiRef={apiRef}
|
||||
checkboxSelection
|
||||
localeText={{ checkboxSelectionHeaderName: "selection" }}
|
||||
columnVisibilityModel={table.columnVisibility}
|
||||
columns={columns}
|
||||
density="compact"
|
||||
@@ -134,8 +164,8 @@ export default function PackageTable(): React.JSX.Element {
|
||||
}}
|
||||
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) {
|
||||
// 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")) {
|
||||
|
||||
@@ -24,29 +24,27 @@ import { useClient } from "hooks/useClient";
|
||||
import { useNotification } from "hooks/useNotification";
|
||||
import { useRepository } from "hooks/useRepository";
|
||||
import type { RepositoryId } from "models/RepositoryId";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export interface UsePackageActionsResult {
|
||||
handleRefreshDatabase: () => Promise<void>;
|
||||
handleReload: () => void;
|
||||
handleRemove: () => Promise<void>;
|
||||
handleUpdate: () => Promise<void>;
|
||||
handleRemove: (packages: string[]) => Promise<void>;
|
||||
handleUpdate: (packages: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export function usePackageActions(
|
||||
selectionModel: string[],
|
||||
setSelectionModel: (model: string[]) => void,
|
||||
): UsePackageActionsResult {
|
||||
export function usePackageActions(): UsePackageActionsResult {
|
||||
const client = useClient();
|
||||
const { currentRepository } = useRepository();
|
||||
const { showSuccess, showError } = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const invalidate = (repository: RepositoryId): void => {
|
||||
const invalidate = useCallback((repository: RepositoryId): void => {
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(repository) });
|
||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(repository) });
|
||||
};
|
||||
}, [queryClient]);
|
||||
|
||||
const performAction = async (
|
||||
const performAction = useCallback(async (
|
||||
action: (repository: RepositoryId) => Promise<string>,
|
||||
errorMessage: string,
|
||||
): Promise<void> => {
|
||||
@@ -57,11 +55,10 @@ export function usePackageActions(
|
||||
const successMessage = await action(currentRepository);
|
||||
showSuccess("Success", successMessage);
|
||||
invalidate(currentRepository);
|
||||
setSelectionModel([]);
|
||||
} catch (exception) {
|
||||
showError("Action failed", `${errorMessage}: ${ApiError.errorDetail(exception)}`);
|
||||
}
|
||||
};
|
||||
}, [currentRepository, invalidate, showError, showSuccess]);
|
||||
|
||||
const handleReload = (): void => {
|
||||
if (currentRepository !== null) {
|
||||
@@ -69,14 +66,16 @@ export function usePackageActions(
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = (): Promise<void> => performAction(async (repository): Promise<string> => {
|
||||
if (selectionModel.length === 0) {
|
||||
await client.service.servicePackageUpdate(repository, { packages: [] });
|
||||
return "Repository update has been run";
|
||||
}
|
||||
await client.service.servicePackageAdd(repository, { packages: selectionModel });
|
||||
return `Run update for packages ${selectionModel.join(", ")}`;
|
||||
}, "Packages update failed");
|
||||
const handleUpdate = useCallback((packages: string[]): Promise<void> => {
|
||||
return performAction(async (repository): Promise<string> => {
|
||||
if (packages.length === 0) {
|
||||
await client.service.servicePackageUpdate(repository, { packages: [] });
|
||||
return "Repository update has been run";
|
||||
}
|
||||
await client.service.servicePackageAdd(repository, { packages });
|
||||
return `Run update for packages ${packages.join(", ")}`;
|
||||
}, "Packages update failed");
|
||||
}, [client, performAction]);
|
||||
|
||||
const handleRefreshDatabase = (): Promise<void> => performAction(async (repository): Promise<string> => {
|
||||
await client.service.servicePackageUpdate(repository, {
|
||||
@@ -89,15 +88,15 @@ export function usePackageActions(
|
||||
return "Pacman database update has been requested";
|
||||
}, "Could not update pacman databases");
|
||||
|
||||
const handleRemove = (): Promise<void> => {
|
||||
if (selectionModel.length === 0) {
|
||||
const handleRemove = useCallback((packages: string[]): Promise<void> => {
|
||||
if (packages.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return performAction(async (repository): Promise<string> => {
|
||||
await client.service.servicePackageRemove(repository, selectionModel);
|
||||
return `Packages ${selectionModel.join(", ")} have been removed`;
|
||||
await client.service.servicePackageRemove(repository, packages);
|
||||
return `Packages ${packages.join(", ")} have been removed`;
|
||||
}, "Could not remove packages");
|
||||
};
|
||||
}, [client, performAction]);
|
||||
|
||||
return {
|
||||
handleRefreshDatabase,
|
||||
|
||||
@@ -30,8 +30,8 @@ export interface UsePackageTableResult {
|
||||
filterModel: GridFilterModel;
|
||||
handleRefreshDatabase: () => Promise<void>;
|
||||
handleReload: () => void;
|
||||
handleRemove: () => Promise<void>;
|
||||
handleUpdate: () => Promise<void>;
|
||||
handleRemove: (packages: string[]) => Promise<void>;
|
||||
handleUpdate: (packages: string[]) => Promise<void>;
|
||||
isAuthorized: boolean;
|
||||
isLoading: boolean;
|
||||
paginationModel: { page: number; pageSize: number };
|
||||
@@ -52,7 +52,7 @@ export interface UsePackageTableResult {
|
||||
export function usePackageTable(): UsePackageTableResult {
|
||||
const { rows, isLoading, isAuthorized, status } = usePackageData();
|
||||
const tableState = useTableState();
|
||||
const actions = usePackageActions(tableState.selectionModel, tableState.setSelectionModel);
|
||||
const actions = usePackageActions();
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
|
||||
Reference in New Issue
Block a user