Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
SludgeGirl committed Jan 27, 2025
1 parent e80e547 commit 6828832
Show file tree
Hide file tree
Showing 16 changed files with 520 additions and 73 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# extract name from package.json
PACKAGE_NAME := $(shell awk '/"name":/ {gsub(/[",]/, "", $$2); print $$2}' package.json)
RPM_NAME := cockpit-$(PACKAGE_NAME)
RPM_NAME := $(PACKAGE_NAME)
VERSION := $(shell T=$$(git describe 2>/dev/null) || T=1; echo $$T | tr '-' '.')
ifeq ($(TEST_OS),)
TEST_OS = centos-9-stream
Expand All @@ -10,7 +10,7 @@ TARFILE=$(RPM_NAME)-$(VERSION).tar.xz
NODE_CACHE=$(RPM_NAME)-node-$(VERSION).tar.xz
SPEC=$(RPM_NAME).spec
PREFIX ?= /usr/local
APPSTREAMFILE=org.cockpit_project.$(subst -,_,$(PACKAGE_NAME)).metainfo.xml
APPSTREAMFILE=org.opensuse.$(subst -,_,$(PACKAGE_NAME)).metainfo.xml
VM_IMAGE=$(CURDIR)/test/images/$(TEST_OS)
# stamp file to check for node_modules/
NODE_MODULES_TEST=package-lock.json
Expand Down
23 changes: 0 additions & 23 deletions org.cockpit_project.starter_kit.metainfo.xml

This file was deleted.

19 changes: 19 additions & 0 deletions org.opensuse.cockpit_repos.metainfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="addon">
<id>org.opensuse.cockpit-repos.metainfo.xml</id>
<metadata_license>CC0-1.0</metadata_license>
<name>Repositories</name>
<summary>A cockpit module for managing repositories.</summary>
<description>
<p>
A cockpit module for managing repositories.
</p>
</description>
<extends>org.cockpit_project.cockpit</extends>
<launchable type="cockpit-manifest">repositories</launchable>
<url type="homepage">https://github.com/openSUSE/cockpit-repos</url>
<url type="bugtracker">https://github.com/openSUSE/cockpit-repos/issues</url>
<developer id="org.opensuse">
<name>openSUSE</name>
</developer>
</component>
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "starter-kit",
"description": "Scaffolding for a cockpit module",
"name": "cockpit-repos",
"description": "A cockpit module for managing system repositories",
"type": "module",
"main": "index.js",
"repository": "[email protected]:cockpit/starter-kit.git",
Expand Down Expand Up @@ -54,6 +54,7 @@
"@patternfly/react-core": "5.4.11",
"@patternfly/react-icons": "5.4.2",
"@patternfly/react-styles": "5.4.1",
"@patternfly/react-table": "^6.1.0",
"react": "18.3.1",
"react-dom": "18.3.1"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Name: cockpit-starter-kit
Name: cockpit-repos
Version: %{VERSION}
Release: 1%{?dist}
Summary: Cockpit Starter Kit Example Module
Expand Down
2 changes: 1 addition & 1 deletion packit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# To use this, enable Packit-as-a-service in GitHub: https://packit.dev/docs/packit-as-a-service/
# See https://packit.dev/docs/configuration/ for the format of this file

specfile_path: cockpit-starter-kit.spec
specfile_path: cockpit-repos.spec
# use the nicely formatted release description from our upstream release, instead of git shortlog
copy_upstream_release_description: true

Expand Down
12 changes: 12 additions & 0 deletions src/app.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
@use "@patternfly/patternfly/patternfly-addons";
@use "page.scss";

p {
font-weight: bold;
}

.pf-v6-c-table tr {
> td, th {
--pf-v5-c-table--cell--PaddingTop: var(--pf-v5-global--spacer--xs);
--pf-v5-c-table--cell--PaddingBottom: var(--pf-v5-global--spacer--xs);

padding-block: var(--pf-v5-c-table--cell--PaddingTop) var(--pf-v5-c-table--cell--PaddingBottom);

padding-inline: var(--pf-v5-c-table--cell--PaddingLeft) var(--pf-v5-c-table--cell--PaddingRight);
}
}
115 changes: 81 additions & 34 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,94 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useEffect, useState } from 'react';
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Card, CardBody, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";

import cockpit from 'cockpit';
import React, {
createContext,
Dispatch,
SetStateAction,
useContext,
useEffect,
useState,
} from "react";
import {
Card,
CardBody,
CardHeader,
CardTitle,
} from "@patternfly/react-core/dist/esm/components/Card/index.js";

import cockpit from "cockpit";
import { Zypp } from "./backends/zypp";
import { Backend, Repo } from "./backends/backend";
import { RepoList } from "./components/repo_list";
import { Button } from "@patternfly/react-core";
import { useDialogs, WithDialogs } from "dialogs";
import { RepoDialog } from "./components/repo_dialog";
import { EmptyStatePanel } from "cockpit-components-empty-state";

const _ = cockpit.gettext;

export const RepoChangesContext = createContext<{
reposChanged: number | null;
setReposChanged: Dispatch<SetStateAction<number>> | null;
}>({
reposChanged: null,
setReposChanged: null,
});

export const Application = () => {
const [hostname, setHostname] = useState(_("Unknown"));
const [reposChanged, setReposChanged] = useState<number>(0);

return (
<RepoChangesContext.Provider
value={{
reposChanged,
setReposChanged,
}}
>
<WithDialogs>
<RepoCard />
</WithDialogs>
</RepoChangesContext.Provider>
);
};

const RepoCard = () => {
const [backend, _setBackend] = useState<Backend>(new Zypp());
const [repos, setRepos] = useState<Repo[]>([]);
const { reposChanged, setReposChanged } = useContext(RepoChangesContext);

const Dialogs = useDialogs();

useEffect(() => {
const hostname = cockpit.file('/etc/hostname');
hostname.watch(content => setHostname(content?.trim() ?? ""));
return hostname.close;
}, []);
backend.getRepos().then((repos) => {
setRepos(repos);
});
}, [backend, reposChanged]);

return (
<Card>
<CardTitle>Starter Kit</CardTitle>
<CardHeader
actions={{
actions: (
<Button
variant="secondary"
id="settings-button"
component="a"
onClick={() =>
Dialogs.show(<RepoDialog backend={backend} repo={null} />)}
>
{_("Add Repo")}
</Button>
),
}}
>
<CardTitle>{_("Software Repositories")}</CardTitle>
</CardHeader>
<CardBody>
<Alert
variant="info"
title={ cockpit.format(_("Running on $0"), hostname) }
/>
{repos
? (
<RepoList repos={repos} backend={backend} />
)
: (
<EmptyStatePanel loading />
)}
</CardBody>
</Card>
);
Expand Down
34 changes: 34 additions & 0 deletions src/backends/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// enum RepoType {
// ftp,
// http,
// https,
// smb_cifs,
// nfs,
// cd,
// dvd,
// hard_disk,
// usb,
// local_directory,
// local_iso_image,
// }

type Repo = {
index: number,
alias: string,
name: string,
// type: RepoType,
priority: number,
enabled: boolean,
autorefresh: boolean,
gpgcheck: boolean,
uri: string,
}

interface Backend {
getRepos(): Promise<Repo[]>,
addRepo(repo: Repo): Promise<any>
deleteRepo(repo: Repo): Promise<any>
modifyRepo(repo: Repo): Promise<any>
}

export { Repo, Backend };
75 changes: 75 additions & 0 deletions src/backends/zypp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import cockpit from "cockpit";

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

export class Zypp implements Backend {
deleteRepo(repo: Repo): Promise<any> {
return cockpit.spawn(["zypper", "removerepo", repo.index.toString()], { superuser: "require" });
}

async getRepos(): Promise<Repo[]> {
return cockpit.spawn(["zypper", "--xmlout", "repos"]).then((response) => {
const parser = new DOMParser();
const doc = parser.parseFromString(response, "text/xml");
let index = 1;
const repos = Array.from(doc.documentElement.querySelectorAll("repo")).map(
(repo): Repo => {
const definedRepo = {
index,
alias: repo.getAttribute("alias") || "",
name: repo.getAttribute("name") || "",
priority: parseInt(repo.getAttribute("priority") || ""),
enabled: repo.getAttribute("enabled") === "1",
autorefresh: repo.getAttribute("autorefresh") === "1",
gpgcheck: repo.getAttribute("gpgcheck") === "1",
uri: repo.querySelector("url")?.textContent || "",
};

index++;
return definedRepo;
},
);
return repos;
});
}

addRepo(repo: Repo): Promise<any> {
const args = ["-n", repo.name, "-p", repo.priority.toString()];
if (repo.enabled) {
args.push("--enable");
} else {
args.push("--disable");
}
if (repo.autorefresh) {
args.push("--refresh");
} else {
args.push("--no-refresh");
}
if (repo.gpgcheck) {
args.push("--gpgcheck");
} else {
args.push("--no-gpgcheck");
}
return cockpit.spawn(["zypper", "addrepo", ...args, repo.uri, repo.alias], { superuser: "require" });
}

modifyRepo(repo: Repo): Promise<any> {
const args = ["-n", repo.name, "-p", repo.priority.toString()];
if (repo.enabled) {
args.push("--enable");
} else {
args.push("--disable");
}
if (repo.autorefresh) {
args.push("--refresh");
} else {
args.push("--no-refresh");
}
if (repo.gpgcheck) {
args.push("--gpgcheck");
} else {
args.push("--no-gpgcheck");
}
return cockpit.spawn(["zypper", "modifyrepo", ...args, repo.index.toString()], { superuser: "require" });
}
}
30 changes: 30 additions & 0 deletions src/components/repo_dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { Modal } from "@patternfly/react-core";
import cockpit from "cockpit";

import { useDialogs } from "dialogs.jsx";
import RepoForm from "./repo_form";
import { Backend, Repo } from "../backends/backend";

const _ = cockpit.gettext;

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

return (
<Modal
title={_("Add a repo")}
variant="small"
onClose={Dialogs.close}
isOpen
>
<RepoForm backend={backend} repo={repo} close={Dialogs.close} />
</Modal>
);
};
Loading

0 comments on commit 6828832

Please sign in to comment.