diff --git a/frontend/package.json b/frontend/package.json index 8bba20d7..81b367a7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,8 +1,34 @@ { + "dependencies": { + "@emotion/react": ">=11.14.0 <11.15.0", + "@emotion/styled": ">=11.14.0 <11.15.0", + "@mui/icons-material": ">=7.3.0 <7.4.0", + "@mui/material": ">=7.3.0 <7.4.0", + "@mui/x-data-grid": ">=8.28.0 <8.29.0", + "@tanstack/react-query": ">=5.94.0 <5.95.0", + "chart.js": ">=4.5.0 <4.6.0", + "react": ">=19.2.0 <19.3.0", + "react-chartjs-2": ">=5.3.0 <5.4.0", + "react-dom": ">=19.2.0 <19.3.0", + "react-syntax-highlighter": ">=16.1.0 <16.2.0" + }, + "devDependencies": { + "@eslint/js": ">=9.39.0 <9.40.0", + "@stylistic/eslint-plugin": ">=5.10.0 <5.11.0", + "@types/react": ">=19.2.0 <19.3.0", + "@types/react-dom": ">=19.2.0 <19.3.0", + "@types/react-syntax-highlighter": ">=15.5.0 <15.6.0", + "@vitejs/plugin-react": ">=6.0.0 <6.1.0", + "eslint": ">=9.39.0 <9.40.0", + "eslint-plugin-react-hooks": ">=7.0.0 <7.1.0", + "eslint-plugin-react-refresh": ">=0.5.0 <0.6.0", + "eslint-plugin-simple-import-sort": ">=12.1.0 <12.2.0", + "typescript": ">=5.9.0 <5.10.0", + "typescript-eslint": ">=8.57.0 <8.58.0", + "vite": ">=8.0.0 <8.1.0" + }, "name": "ahriman-frontend", "private": true, - "type": "module", - "version": "2.20.0", "scripts": { "build": "tsc && vite build", "dev": "vite", @@ -10,32 +36,6 @@ "lint:fix": "eslint --fix src/", "preview": "vite preview" }, - "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.1", - "@mui/icons-material": "^7.3.9", - "@mui/material": "^7.3.9", - "@mui/x-data-grid": "^8.27.4", - "@tanstack/react-query": "^5.91.3", - "chart.js": "^4.5.1", - "react": "^19.2.4", - "react-chartjs-2": "^5.3.1", - "react-dom": "^19.2.4", - "react-syntax-highlighter": "^16.1.1" - }, - "devDependencies": { - "@eslint/js": "^9.39.3", - "@stylistic/eslint-plugin": "^5.10.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react": "^6.0.1", - "eslint": "^9.39.3", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", - "eslint-plugin-simple-import-sort": "^12.1.1", - "typescript": "^5.9.3", - "typescript-eslint": "^8.56.1", - "vite": "^8.0.0" - } + "type": "module", + "version": "2.20.0" } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index da1db045..431485a7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,8 +29,8 @@ import type React from "react"; const queryClient = new QueryClient({ defaultOptions: { queries: { - staleTime: 30_000, retry: 1, + staleTime: 30_000, }, }, }); diff --git a/frontend/src/api/client/ApiError.ts b/frontend/src/api/client/ApiError.ts index 1462d48b..539de669 100644 --- a/frontend/src/api/client/ApiError.ts +++ b/frontend/src/api/client/ApiError.ts @@ -18,9 +18,10 @@ * along with this program. If not, see . */ export class ApiError extends Error { + + body: string; status: number; statusText: string; - body: string; constructor(status: number, statusText: string, body: string) { super(`${status} ${statusText}`); diff --git a/frontend/src/api/client/FetchClient.ts b/frontend/src/api/client/FetchClient.ts index d5e1c0eb..3c857d3e 100644 --- a/frontend/src/api/client/FetchClient.ts +++ b/frontend/src/api/client/FetchClient.ts @@ -37,14 +37,14 @@ export class FetchClient { this.client = client; } - async fetchPackageArtifacts(packageBase: string, repository: RepositoryId): Promise { - return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/archives`, { + async fetchPackage(packageBase: string, repository: RepositoryId): Promise { + return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}`, { query: repository.toQuery(), }); } - async fetchPackage(packageBase: string, repository: RepositoryId): Promise { - return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}`, { + async fetchPackageArtifacts(packageBase: string, repository: RepositoryId): Promise { + return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/archives`, { query: repository.toQuery(), }); } diff --git a/frontend/src/api/client/RequestOptions.ts b/frontend/src/api/client/RequestOptions.ts index fca0e4fe..8060c246 100644 --- a/frontend/src/api/client/RequestOptions.ts +++ b/frontend/src/api/client/RequestOptions.ts @@ -18,8 +18,8 @@ * along with this program. If not, see . */ export interface RequestOptions { + json?: unknown; method?: string; query?: Record; - json?: unknown; timeout?: number; } diff --git a/frontend/src/api/client/ServiceClient.ts b/frontend/src/api/client/ServiceClient.ts index 6181a06b..6ccd3392 100644 --- a/frontend/src/api/client/ServiceClient.ts +++ b/frontend/src/api/client/ServiceClient.ts @@ -37,17 +37,17 @@ export class ServiceClient { return this.client.request("/api/v1/service/add", { method: "POST", query: repository.toQuery(), json: data }); } - async servicePackagePatchRemove(packageBase: string, key: string): Promise { - return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches/${encodeURIComponent(key)}`, { - method: "DELETE", + async servicePackageHold(packageBase: string, repository: RepositoryId, isHeld: boolean): Promise { + return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/hold`, { + method: "POST", + query: repository.toQuery(), + json: { is_held: isHeld }, }); } - async servicePackageRollback(repository: RepositoryId, data: RollbackRequest): Promise { - return this.client.request("/api/v1/service/rollback", { - method: "POST", - query: repository.toQuery(), - json: data, + async servicePackagePatchRemove(packageBase: string, key: string): Promise { + return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches/${encodeURIComponent(key)}`, { + method: "DELETE", }); } @@ -67,6 +67,14 @@ export class ServiceClient { }); } + async servicePackageRollback(repository: RepositoryId, data: RollbackRequest): Promise { + return this.client.request("/api/v1/service/rollback", { + method: "POST", + query: repository.toQuery(), + json: data, + }); + } + async servicePackageSearch(query: string): Promise { return this.client.request("/api/v1/service/search", { query: { for: query } }); } @@ -87,14 +95,6 @@ export class ServiceClient { return this.client.request("/api/v1/service/pgp", { method: "POST", json: data }); } - async servicePackageHoldUpdate(packageBase: string, repository: RepositoryId, isHeld: boolean): Promise { - return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/hold`, { - method: "POST", - query: repository.toQuery(), - json: { is_held: isHeld }, - }); - } - async serviceRebuild(repository: RepositoryId, packages: string[]): Promise { return this.client.request("/api/v1/service/rebuild", { method: "POST", diff --git a/frontend/src/components/charts/EventDurationLineChart.tsx b/frontend/src/components/charts/EventDurationLineChart.tsx index 0c1744bb..952950eb 100644 --- a/frontend/src/components/charts/EventDurationLineChart.tsx +++ b/frontend/src/components/charts/EventDurationLineChart.tsx @@ -32,11 +32,11 @@ export default function EventDurationLineChart({ events }: EventDurationLineChar labels: updateEvents.map(event => new Date(event.created * 1000).toISOStringShort()), datasets: [ { - label: "update duration, s", - data: updateEvents.map(event => event.data?.took ?? 0), - borderColor: blue[500], backgroundColor: blue[200], + borderColor: blue[500], cubicInterpolationMode: "monotone" as const, + data: updateEvents.map(event => event.data?.took ?? 0), + label: "update duration, s", tension: 0.4, }, ], diff --git a/frontend/src/components/charts/PackageCountBarChart.tsx b/frontend/src/components/charts/PackageCountBarChart.tsx index 7a0ca8e0..76c97f8c 100644 --- a/frontend/src/components/charts/PackageCountBarChart.tsx +++ b/frontend/src/components/charts/PackageCountBarChart.tsx @@ -29,19 +29,19 @@ interface PackageCountBarChartProps { export default function PackageCountBarChart({ stats }: PackageCountBarChartProps): React.JSX.Element { return counters[label]), backgroundColor: labels.map(label => StatusColors[label]), + data: labels.map(label => counters[label]), + label: "packages in status", }, ], + labels: labels, }; return ; diff --git a/frontend/src/components/common/AutoRefreshControl.tsx b/frontend/src/components/common/AutoRefreshControl.tsx index bf125a2d..479281c4 100644 --- a/frontend/src/components/common/AutoRefreshControl.tsx +++ b/frontend/src/components/common/AutoRefreshControl.tsx @@ -25,14 +25,14 @@ import type { AutoRefreshInterval } from "models/AutoRefreshInterval"; import React, { useState } from "react"; interface AutoRefreshControlProps { - intervals: AutoRefreshInterval[]; currentInterval: number; + intervals: AutoRefreshInterval[]; onIntervalChange: (interval: number) => void; } export default function AutoRefreshControl({ - intervals, currentInterval, + intervals, onIntervalChange, }: AutoRefreshControlProps): React.JSX.Element | null { const [anchorEl, setAnchorEl] = useState(null); @@ -46,25 +46,25 @@ export default function AutoRefreshControl({ return <> setAnchorEl(event.currentTarget)} color={enabled ? "primary" : "default"} + onClick={event => setAnchorEl(event.currentTarget)} + size="small" > {enabled ? : } setAnchorEl(null)} + open={Boolean(anchorEl)} > { onIntervalChange(0); setAnchorEl(null); }} + selected={!enabled} > {!enabled && } @@ -74,11 +74,11 @@ export default function AutoRefreshControl({ {intervals.map(interval => { onIntervalChange(interval.interval); setAnchorEl(null); }} + selected={enabled && interval.interval === currentInterval} > {enabled && interval.interval === currentInterval && } diff --git a/frontend/src/components/common/CodeBlock.tsx b/frontend/src/components/common/CodeBlock.tsx index 02e08e45..2818112a 100644 --- a/frontend/src/components/common/CodeBlock.tsx +++ b/frontend/src/components/common/CodeBlock.tsx @@ -46,27 +46,24 @@ export default function CodeBlock({ return {content} - {content && + {content && } ; diff --git a/frontend/src/components/common/CopyButton.tsx b/frontend/src/components/common/CopyButton.tsx index 1649c930..ba73ed97 100644 --- a/frontend/src/components/common/CopyButton.tsx +++ b/frontend/src/components/common/CopyButton.tsx @@ -40,7 +40,7 @@ export default function CopyButton({ text }: CopyButtonProps): React.JSX.Element }; return - void handleCopy()}> + void handleCopy()} size="small"> {copied ? : } ; diff --git a/frontend/src/components/common/DialogHeader.tsx b/frontend/src/components/common/DialogHeader.tsx index f4271df3..5e27f659 100644 --- a/frontend/src/components/common/DialogHeader.tsx +++ b/frontend/src/components/common/DialogHeader.tsx @@ -28,7 +28,7 @@ interface DialogHeaderProps { } export default function DialogHeader({ children, onClose, sx }: DialogHeaderProps): React.JSX.Element { - return + return {children} diff --git a/frontend/src/components/common/NotificationItem.tsx b/frontend/src/components/common/NotificationItem.tsx index ecbcaaca..23e8f56e 100644 --- a/frontend/src/components/common/NotificationItem.tsx +++ b/frontend/src/components/common/NotificationItem.tsx @@ -35,12 +35,12 @@ export default function NotificationItem({ notification, onClose }: Notification }, []); return ( - onClose(notification.id)}> + onClose(notification.id)} unmountOnExit> setShow(false)} severity={notification.severity} - variant="filled" sx={{ width: "100%", pointerEvents: "auto" }} + variant="filled" > {notification.title} {notification.message && ` - ${notification.message}`} diff --git a/frontend/src/components/common/RepositorySelect.tsx b/frontend/src/components/common/RepositorySelect.tsx index eb53bc3f..b98c47af 100644 --- a/frontend/src/components/common/RepositorySelect.tsx +++ b/frontend/src/components/common/RepositorySelect.tsx @@ -30,9 +30,9 @@ export default function RepositorySelect({ return repository