Skip to content

Commit

Permalink
Merge pull request #11 from Nykseli/cpt-90
Browse files Browse the repository at this point in the history
Improvements and bugfixes
  • Loading branch information
SludgeGirl authored Feb 17, 2025
2 parents d87c6a6 + eda8f6f commit 45465ef
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Button } from "@patternfly/react-core";
import { useDialogs, WithDialogs } from "dialogs";
import { RepoDialog } from "./components/repo_dialog";
import { EmptyStatePanel } from "cockpit-components-empty-state";
import { RefreshAllButton } from "./components/repo_refresh";

const _ = cockpit.gettext;

Expand Down Expand Up @@ -67,15 +68,18 @@ const RepoCard = () => {
<CardHeader
actions={{
actions: (
<>
<RefreshAllButton backend={backend}/>
<Button
variant="secondary"
id="settings-button"
component="a"
onClick={() =>
Dialogs.show(<RepoDialog backend={backend} repo={null} />)}
Dialogs.show(<RepoDialog title={_("Add a repo")} backend={backend} repo={null} />)}
>
{_("Add Repo")}
</Button>
</>
),
}}
>
Expand Down
3 changes: 3 additions & 0 deletions src/backends/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// local_iso_image,
// }

import { Spawn } from "cockpit"

type Repo = {
index: number,
alias: string,
Expand All @@ -29,6 +31,7 @@ interface Backend {
addRepo(repo: Repo): Promise<any>
deleteRepo(repo: Repo): Promise<any>
modifyRepo(repo: Repo): Promise<any>
refreshRepo(repo: Repo | null, importKeys?: boolean): Spawn<string>
}

export { Repo, Backend };
16 changes: 15 additions & 1 deletion src/backends/zypp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import cockpit from "cockpit";
import cockpit, { Spawn } from "cockpit";

import { Backend, Repo } from "./backend";

Expand Down Expand Up @@ -72,4 +72,18 @@ export class Zypp implements Backend {
}
return cockpit.spawn(["zypper", "modifyrepo", ...args, repo.index.toString()], { superuser: "require" });
}

refreshRepo(repo: Repo | null, importKeys?: boolean): Spawn<string> {
let refArgs: string[] = [];
let zypArgs: string[] = []
// if there's no repos defined, all will be refreshed
if (repo) {
refArgs = ["-r", repo.index.toString()];
}
if (importKeys) {
zypArgs = ["--gpg-auto-import-keys"];
}

return cockpit.spawn(["zypper", ...zypArgs, "refresh", ...refArgs], { superuser: "require" });
}
}
4 changes: 2 additions & 2 deletions src/components/confirmation_dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const ConfirmationDialog = ({
}: {
title: string;
callback: () => void;
content: React.ReactNode | undefined;
content?: React.ReactNode;
}) => {
const Dialogs = useDialogs();

Expand All @@ -27,7 +27,7 @@ export const ConfirmationDialog = ({
<>
<Button
variant="danger"
onClick={callback}
onClick={() => { callback(); Dialogs.close()}}
aria-label={title}
>
{_("Delete")}
Expand Down
4 changes: 3 additions & 1 deletion src/components/repo_dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ const _ = cockpit.gettext;

export const RepoDialog = ({
backend,
title,
repo,
}: {
backend: Backend;
title: string,
repo: null | Repo;
}) => {
const Dialogs = useDialogs();

return (
<Modal
title={_("Add a repo")}
title={title}
variant="small"
onClose={Dialogs.close}
isOpen
Expand Down
4 changes: 4 additions & 0 deletions src/components/repo_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ const RepoForm = ({
onChange={(_, value) => onValueChange("alias", value)}
value={formData.alias}
placeholder=""
isDisabled={editing}
/>
{editing && <p>{_("Repo alias cannot be edited via Cockpit")}</p>}
</FormGroup>
<FormGroup label={_("Name")} fieldId="name">
<TextInput
Expand Down Expand Up @@ -136,7 +138,9 @@ const RepoForm = ({
onChange={(_, value) => onValueChange("uri", value)}
value={formData.uri}
placeholder=""
isDisabled={editing}
/>
{editing && <p>{_("Repo Uri cannot be edited via Cockpit")}</p>}
</FormGroup>
<ActionGroup>
<Button onClick={submit} variant="primary">
Expand Down
6 changes: 5 additions & 1 deletion src/components/repo_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Props = {

export const RepoList = ({ repos, backend }: Props) => {
const columns = [
{ title: "#" },
{ title: _("Name") },
{ title: _("Priority") },
{ title: _("GPG Check") },
Expand All @@ -35,6 +36,9 @@ export const RepoList = ({ repos, backend }: Props) => {
rows={repos.map((repo) => {
return {
columns: [
{
title: repo.index,
},
{
title: repo.name,
},
Expand Down Expand Up @@ -75,7 +79,7 @@ const RepoActions = ({ backend, repo }: { backend: Backend; repo: Repo }) => {
const actions = [
<DropdownItem
key="edit-repo"
onClick={() => Dialogs.show(<RepoDialog backend={backend} repo={repo} />)}
onClick={() => Dialogs.show(<RepoDialog title={_("Edit repo")} backend={backend} repo={repo} />)}
>
{_("Edit repo")}
</DropdownItem>,
Expand Down
158 changes: 158 additions & 0 deletions src/components/repo_refresh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from "react";
import { Button, Modal } from "@patternfly/react-core";
import { useDialogs } from "dialogs";
import { Backend } from "../backends/backend";

import cockpit from "cockpit.js";
import { EmptyStatePanel } from "cockpit-components-empty-state";

const _ = cockpit.gettext;

interface UknownRefresh {
err: "unknown";
}

interface UntrustedRefresh {
err: "untrusted";
repos: string[];
}

type RefreshError = UknownRefresh | UntrustedRefresh;

const OkFooter = () => {
const Dialogs = useDialogs();
return (
<Button
variant="primary"
onClick={Dialogs.close}
>
{_("Ok")}
</Button>
);
}

const ErrorFooter = ({
backend,
error,
setLoading,
onLoaded
}: {
backend: Backend,
error: RefreshError,
setLoading: () => void,
onLoaded: (err: RefreshError | null) => void
}) => {
const Dialogs = useDialogs();
return (
<>
<Button
variant="danger"
onClick={() => {
setLoading();
backend.refreshRepo(null, true)
.then(() => onLoaded(null))
.catch(reason => {
console.warn(reason);
onLoaded({ err: "unknown" });
})
}}
>
{error.err == "untrusted" ? _("Trust") : _("Ok")}
</Button>
<Button
variant="link"
className="btn-cancel"
onClick={Dialogs.close}
>
{_("Cancel")}
</Button>
</>
);
}

const ErrorMsg = ({ error }: { error: RefreshError }) => {
if (error.err == "untrusted") {
return (
<>
<p>{_("Couldn't trust following repos:")}</p>
{error.repos.map((err, idx) => <p key={idx}>{err}</p>)}
<br />
<p>{_("You can trust them, or run \"zypper ref\" as root in console to see more information about the issue")}</p>
</>
);
} else {
return (
<>
<p>{_("Unknown error occured.")}</p>
<p>{_("See \"zypper ref\" for more information.")}</p>
</>
);
}
}


const RefreshDialog = ({ backend }: { backend: Backend }) => {
const Dialogs = useDialogs();
const [refreshing, setRefreshing] = React.useState(true);
const [error, setError] = React.useState<RefreshError | null>(null);

const parseError = (error: string): UntrustedRefresh => {
const regex = /metadata for '([\S ]*)'/gm;
const lines = error.replace("\\n", "\n");
const repoNames = [];
let match;

while ((match = regex.exec(lines)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (match.index === regex.lastIndex) {
regex.lastIndex++;
}

repoNames.push(match[1]);
}

return { err: "untrusted", repos: repoNames };
}

React.useEffect(() => {
backend.refreshRepo(null)
.catch(reason => { setError(parseError(reason.message)) })
.finally(() => setRefreshing(false));
}, []);


return (
<Modal
title={_("Refreshing repositories")}
variant="small"
onClose={Dialogs.close}
isOpen
footer={
refreshing ? null : error
? <ErrorFooter backend={backend} error={error} setLoading={() => setRefreshing(true)} onLoaded={(err) => { setRefreshing(false); setError(err) }} />
: <OkFooter />
}
>
{refreshing ? <EmptyStatePanel loading /> :
error ? <ErrorMsg error={error} /> : <p>{_("Refreshing repos was successful")}</p>}
</Modal>
);
}

export const RefreshAllButton = ({ backend }: { backend: Backend }) => {
const Dialogs = useDialogs();

const refreshAll = () => {
Dialogs.show(<RefreshDialog backend={backend} />);
}

return (
<Button
variant="secondary"
id="settings-button"
onClick={refreshAll}
>
{_("Refresh repositories")}
</Button>
);
}

0 comments on commit 45465ef

Please sign in to comment.