diff --git a/doc/dbus/org.opensuse.Agama.Language1.doc.xml b/doc/dbus/org.opensuse.Agama.Language1.doc.xml
new file mode 100644
index 0000000000..4fa4fbe82e
--- /dev/null
+++ b/doc/dbus/org.opensuse.Agama.Language1.doc.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/dbus/org.opensuse.Agama1.Manager.doc.xml b/doc/dbus/org.opensuse.Agama1.Manager.doc.xml
new file mode 100644
index 0000000000..8d136b54a7
--- /dev/null
+++ b/doc/dbus/org.opensuse.Agama1.Manager.doc.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/dbus_api.md b/doc/dbus_api.md
index ae35e462a9..40a6a2c04c 100644
--- a/doc/dbus_api.md
+++ b/doc/dbus_api.md
@@ -46,37 +46,10 @@ We use these resources to get more familiar with D-Bus API designing.
Iface: o.o.Agama.Language1
-#### methods:
-
-- ToInstall(array(string LangId)) -> void
- Set list of languages to install
- Example:
-
- ToInstall(["cs_CZ", "de_DE"]) -> () # only lang codes from AvailableLanguages is supported
-
-#### Properties (all read only):
-
-- AvailableLanguages -> array(struct(string LangId, string LangLabel, dict(string, variant) details))
- List of all available languages to install on target system.
- Example:
-
- AvailableLanguages -> [["cs_CZ", "Czech", {}]] # it is lang code, human readable lang name and dict for future extensions to provide more data
-
-- MarkedForInstall -> array(string LangId)
- List of languages to install. Same format as ToInstall
+See the new-style [reference][lang-ref] ([source][lang-src]).
-#### Signals:
-
-- PropertiesChanged ( only standard one from org.freedesktop.DBus.Properties interface )
-
-
-notes:
-
-identifiers: maybe LanguageTag https://www.rubydoc.info/github/yast/yast-packager/master/LanguageTag
-- move it to yast-yast2
-- link to the standard from yard
-- see https://tools.ietf.org/html/rfc4647 Matching of Language Tags
-- see https://lists.opensuse.org/archives/list/yast-devel@lists.opensuse.org/message/D52PSZ7TRID2RVM6CE6K2C2RUNNGOS6Z/
+[lang-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama.Language1.html
+[lang-src]: dbus/org.opensuse.Agama.Language1.doc.xml
## Base Product
@@ -803,62 +776,7 @@ any issue that might block the installation.
## Manager
-### Installation Phases
-
-The installation process follows a set of phases. Only the main service (`Agama::Manager`)
-knows the information about the current installation phase. The rest of services will act as utility
-services without any knowledge about the whole installation process.
-
-A client (e.g., a web UI) will ask to the main service for the current phase of the installation.
-
-In principle, the installation will follow 3 possible phases: *Startup*, *Config* and *Install*.
-
-* *Startup* Phase
-
-This is the initial phase. The manager service will start in this phase and it will not change to
-another phase until the client asks for performing the next phase.
-
-* *Config* Phase
-
-The installation is configured during this phase. Configuring the installation means that everything
-needed from the system is read and the required default proposal are calculated. In YaST terms, the
-*config* phase implies to probe some modules like storage, language, etc, and to perform their
-proposals. Note that not all modules have to be probed/proposed. Probing some modules could be
-delayed to the next *install* phase.
-
-* *Install* Phase
-
-This phase implies to perform everything to install the system according to the selected options and
-proposals. Note that this phase is not only a typical YaST commit. For example, some proposals
-(software?) could be done during this phase. In short, at the beginning of this phase we have all
-the required information to perform the installation, and at the end of the phase the system is
-installed.
-
-### Status of the Services
-
-Note that the services are blocked meanwhile they are performing a long task. For this
-reason, the *manager* service will store the status of each service and the clients will ask to
-*manager* to know that status.
-
-### org.opensuse.Agama1.Manager
-
-#### Properties
-
-- InstallationPhases -> array(array(dict(string, variant))) (r)
-
- All possible phases:
-~~~
- [
- {"id" => 0, "label" => "startup"},
- {"id" => 1, "label" => "config"},
- {"id" => 2, "label" => "install"}
- ]
-~~~
-
-- CurrentInstallationPhase -> unsigned 32-bit integer (r)
-
- Id of the current phase.
-
-- BusyServices -> a(s) (r)
+See the new-style [reference][mgr-ref] ([source][mgr-src]).
- List of names of the currently busy services.
+[mgr-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama1.Manager.html
+[mgr-src]: dbus/org.opensuse.Agama1.Manager.doc.xml
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000000..58402d52f9
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,10 @@
+
+Agama Documentation
+
+
+
+
+
+
diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes
index ecef283806..df21528888 100644
--- a/web/package/cockpit-agama.changes
+++ b/web/package/cockpit-agama.changes
@@ -11,6 +11,29 @@ Mon Apr 24 15:53:35 UTC 2023 - David Diaz
- Extract page options from the Sidebar to make them
more discoverable (gh#openSUSE/agama#545)
+-------------------------------------------------------------------
+Thu Apr 27 08:24:27 UTC 2023 - Ladislav Slezák
+
+- Display details for the "autocalculated" label in the storage
+ settings
+
+-------------------------------------------------------------------
+Tue Apr 25 13:42:22 UTC 2023 - David Diaz
+
+- UI: Fix dropdown content alignment at storage proposal page
+ (gh#openSUSE/agama#547).
+
+-------------------------------------------------------------------
+Tue Apr 25 12:23:27 UTC 2023 - David Diaz
+
+- UI: Do not indent sections without icons (gh#openSUSE/agama#549).
+
+-------------------------------------------------------------------
+Thu Apr 20 12:44:44 UTC 2023 - David Diaz
+
+- Make styles consistent when the user is hovering a button,
+ no matter if it is a focus state or not (gh#openSUSE/agama#544).
+
-------------------------------------------------------------------
Fri Apr 14 13:08:05 UTC 2023 - José Iván López González
diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss
index f3dbec39cd..abea6d5559 100644
--- a/web/src/assets/styles/blocks.scss
+++ b/web/src/assets/styles/blocks.scss
@@ -3,7 +3,7 @@
// section layouts.
section:not([class^="pf-c"]) {
display: grid;
- grid-template-columns: var(--icon-size-m) 1fr;
+ grid-template-columns: min-content 1fr;
grid-template-areas:
"icon title"
".... content";
@@ -262,3 +262,8 @@ span.notification-mark[data-variant="sidebar"] {
padding-left: calc(var(--icon-size) + var(--pf-c-helper-text__item-icon--MarginRight));
}
}
+
+// compact lists in popover
+.pf-c-popover li + li {
+ margin: 0;
+}
diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss
index 604afa49c2..e88a4e6e9d 100644
--- a/web/src/assets/styles/patternfly-overrides.scss
+++ b/web/src/assets/styles/patternfly-overrides.scss
@@ -44,6 +44,11 @@
--pf-c-button--m-primary--hover--BackgroundColor: var(--color-button-primary-hover);
}
+// Make :hover style visible when the button is in a :focus state too
+.pf-c-button.pf-m-primary:focus:hover {
+ --pf-c-button--m-primary--BackgroundColor: var(--color-button-primary-hover);
+}
+
.pf-c-button.pf-m-link {
// Colors for buttons modifiers
--pf-c-button--m-link--Color: var(--color-link);
@@ -61,6 +66,12 @@
--pf-c-button--m-secondary--hover--Color: var(--color-link-hover);
}
+// Make :hover style visible when the button is in a :focus state too
+.pf-c-button.pf-m-secondary:focus:hover {
+ --pf-c-button--after--BorderColor: var(--color-link-hover);
+ --pf-c-button--m-secondary--Color: var(--color-link-hover);
+}
+
// SVG icons does not obey font-size
.pf-c-empty-state__icon {
inline-size: 10rem;
diff --git a/web/src/components/core/Description.jsx b/web/src/components/core/Description.jsx
new file mode 100644
index 0000000000..849334be31
--- /dev/null
+++ b/web/src/components/core/Description.jsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { Popover, Button } from "@patternfly/react-core";
+
+/**
+ * Displays details popup after clicking the children elements
+ * @component
+ *
+ * @param {(JSX.Element|null)} description content displayed in a popup
+ * @param {JSX.Element} children the wrapped content
+ */
+export default function Description ({ description, children, ...otherProps }) {
+ if (description) {
+ return (
+
+
+
+ );
+ }
+
+ // none or empty description, just return the children
+ return children;
+}
diff --git a/web/src/components/core/Description.test.jsx b/web/src/components/core/Description.test.jsx
new file mode 100644
index 0000000000..500f50cd6e
--- /dev/null
+++ b/web/src/components/core/Description.test.jsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import { Description } from "~/components/core";
+
+describe("Description", () => {
+ const description = "Some great description";
+ const item = "Item with description";
+
+ it("displays the description after clicking the object", async () => {
+ const { user } = plainRender({item});
+
+ // the description is not displayed just after the render
+ expect(screen.queryByText(description)).not.toBeInTheDocument();
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+
+ // click it
+ const item_node = screen.getByText(item);
+ await user.click(item_node);
+
+ // then the description is visible in a dialog
+ screen.getByRole("dialog");
+ screen.getByText(description);
+ });
+
+ const expectNoPopup = async (content) => {
+ const { user } = plainRender(content);
+
+ const item_node = screen.getByText(item);
+ await user.click(item_node);
+
+ // do not display empty popup
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ };
+
+ it("displays the object without description when it is undefined", async () => {
+ expectNoPopup({item});
+ });
+
+ it("displays the object without description when it is null", async () => {
+ expectNoPopup({item});
+ });
+
+ it("displays the object without description when it is empty", async () => {
+ expectNoPopup({item});
+ });
+});
diff --git a/web/src/components/core/Tip.jsx b/web/src/components/core/Tip.jsx
new file mode 100644
index 0000000000..e71f8bd74b
--- /dev/null
+++ b/web/src/components/core/Tip.jsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { Label } from "@patternfly/react-core";
+
+import { Description } from "~/components/core";
+import { Icon } from "~/components/layout";
+
+/**
+ * Display a label with additional details. The details are displayed after
+ * clicking the label and the "i" icon indicates available details.
+ * If the label is not defined or is empty it behaves like a plain label.
+ * @component
+ *
+ * @param {JSX.Element} description details displayed after clicking the label
+ * @param {JSX.Element} children the content of the label
+ */
+export default function Tip ({ description, children }) {
+ if (description) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+}
diff --git a/web/src/components/core/Tip.test.jsx b/web/src/components/core/Tip.test.jsx
new file mode 100644
index 0000000000..967b41edb9
--- /dev/null
+++ b/web/src/components/core/Tip.test.jsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) [2023] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program 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 General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { screen } from "@testing-library/react";
+import { plainRender } from "~/test-utils";
+import { Tip } from "~/components/core";
+
+describe("Tip", () => {
+ const description = "Some great description";
+ const label = "Label";
+
+ describe("The description is not empty", () => {
+ it("displays the label with the 'info' icon and show the description after click", async () => {
+ const { user, container } = plainRender({label});
+
+ // an icon is displayed
+ expect(container.querySelector("svg")).toBeInTheDocument();
+
+ // the description is not displayed just after the render
+ expect(screen.queryByText(description)).not.toBeInTheDocument();
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+
+ // click it
+ const label_node = screen.getByText(label);
+ await user.click(label_node);
+
+ // then the description is visible in a dialog
+ screen.getByRole("dialog");
+ screen.getByText(description);
+ });
+ });
+
+ describe("The description is not defined", () => {
+ it("displays the label without the 'info' icon and clicking does not show any popup", async () => {
+ const { user, container } = plainRender({label});
+
+ // no icon
+ expect(container.querySelector("svg")).not.toBeInTheDocument();
+
+ // click it
+ const label_node = screen.getByText(label);
+ await user.click(label_node);
+
+ // no popup
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js
index f5f7efcf67..92f44ce589 100644
--- a/web/src/components/core/index.js
+++ b/web/src/components/core/index.js
@@ -21,6 +21,7 @@
export { default as About } from "./About";
export { default as PageOptions } from "./PageOptions";
+export { default as Description } from "./Description";
export { default as Disclosure } from "./Disclosure";
export { default as Sidebar } from "./Sidebar";
export { default as Section } from "./Section";
@@ -48,5 +49,6 @@ export { default as ProgressReport } from "./ProgressReport";
export { default as ProgressText } from "./ProgressText";
export { default as ValidationErrors } from "./ValidationErrors";
export { default as Terminal } from "./Terminal";
+export { default as Tip } from "./Tip";
export { default as ShowTerminalButton } from "./ShowTerminalButton";
export { default as NotificationMark } from "./NotificationMark";
diff --git a/web/src/components/storage/ProposalVolumes.jsx b/web/src/components/storage/ProposalVolumes.jsx
index c912c9fa2e..202f62a5b4 100644
--- a/web/src/components/storage/ProposalVolumes.jsx
+++ b/web/src/components/storage/ProposalVolumes.jsx
@@ -25,6 +25,7 @@ import React, { useState } from "react";
import {
Dropdown, DropdownToggle, DropdownItem,
Form, FormGroup, FormSelect, FormSelectOption,
+ List, ListItem,
Skeleton,
TextInput,
Toolbar, ToolbarContent, ToolbarItem
@@ -32,7 +33,7 @@ import {
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import { filesize } from "filesize";
-import { Em, If, Popup, RowActions } from '~/components/core';
+import { Em, If, Popup, RowActions, Tip } from '~/components/core';
import { Icon } from '~/components/layout';
import { noop } from "~/utils";
@@ -56,6 +57,33 @@ const sizeText = (size) => {
return filesize(size, { base: 2 });
};
+/**
+ * Generates an hint describing which attributes affect the auto-calculated limits.
+ * If the limits are not affected then it returns `null`.
+ * @function
+ *
+ * @param {object} volume - storage volume object
+ * @returns {(ReactComponent|null)} component to display (can be `null`)
+ */
+const AutoCalculatedHint = (volume) => {
+ // no hint, the size is not affected by snapshots or other volumes
+ if (!volume.snapshotsAffectSizes && volume.sizeRelevantVolumes && volume.sizeRelevantVolumes.length === 0) {
+ return null;
+ }
+
+ return (
+ <>
+ These limits are affected by:
+
+ {volume.snapshotsAffectSizes &&
+ The configuration of snapshots}
+ {volume.sizeRelevantVolumes && volume.sizeRelevantVolumes.length > 0 &&
+ Presence of other volumes ({volume.sizeRelevantVolumes.join(", ")})}
+
+ >
+ );
+};
+
/**
* Form used for adding a new file system from a list of templates
* @component
@@ -181,6 +209,7 @@ const GeneralActions = ({ templates, onAdd, onReset }) => {
return (
<>
{
const limits = `${sizeText(volume.minSize)} - ${sizeText(volume.maxSize)}`;
const isAuto = volume.adaptiveSizes && !volume.fixedSizeLimits;
- const autoModeIcon = ;
-
return (