From ec09e52c7ee9ff61b9a94292f171025bc62cb189 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 15 May 2024 16:36:39 +0100 Subject: [PATCH 1/8] Added manager route for downloading logs --- rust/agama-server/src/manager/web.rs | 43 ++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/rust/agama-server/src/manager/web.rs b/rust/agama-server/src/manager/web.rs index 475f97dd50..0baaec1812 100644 --- a/rust/agama-server/src/manager/web.rs +++ b/rust/agama-server/src/manager/web.rs @@ -5,20 +5,23 @@ //! * `manager_service` which returns the Axum service. //! * `manager_stream` which offers an stream that emits the manager events coming from D-Bus. -use std::pin::Pin; - use agama_lib::{ error::ServiceError, manager::{InstallationPhase, ManagerClient}, proxies::Manager1Proxy, }; use axum::{ - extract::State, + extract::{Request, State}, + http::StatusCode, + response::IntoResponse, routing::{get, post}, Json, Router, }; +use rand::distributions::{Alphanumeric, DistString}; use serde::Serialize; +use std::{pin::Pin, process::Command}; use tokio_stream::{Stream, StreamExt}; +use tower_http::services::ServeFile; use crate::{ error::Error, @@ -90,6 +93,7 @@ pub async fn manager_service(dbus: zbus::Connection) -> Result impl IntoResponse { + let path = generate_logs().await; + let Ok(path) = path else { + return (StatusCode::NOT_FOUND).into_response(); + }; + + match ServeFile::new(path) + .try_call(Request::new(axum::body::Body::empty())) + .await + { + Ok(res) => res.into_response(), + Err(_) => (StatusCode::NOT_FOUND).into_response(), + } +} + +async fn generate_logs() -> Result { + let random_name: String = Alphanumeric.sample_string(&mut rand::thread_rng(), 8); + let path = format!("/run/agama/logs_{random_name}"); + + Command::new("agama") + .args(["logs", "store", "-d", path.as_str()]) + .status() + .expect("Cannot generate logs"); + + let full_path = format!("{path}.tar.bz2"); + Ok(full_path) +} From 0c110faa3e7eaa222615b584a6e6a07923a36e19 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 09:43:25 +0100 Subject: [PATCH 2/8] Modify returned status code --- rust/agama-server/src/manager/web.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/agama-server/src/manager/web.rs b/rust/agama-server/src/manager/web.rs index 0baaec1812..7e5660e400 100644 --- a/rust/agama-server/src/manager/web.rs +++ b/rust/agama-server/src/manager/web.rs @@ -176,7 +176,7 @@ async fn installer_status( pub async fn download_logs() -> impl IntoResponse { let path = generate_logs().await; let Ok(path) = path else { - return (StatusCode::NOT_FOUND).into_response(); + return (StatusCode::INTERNAL_SERVER_ERROR).into_response(); }; match ServeFile::new(path) @@ -184,7 +184,7 @@ pub async fn download_logs() -> impl IntoResponse { .await { Ok(res) => res.into_response(), - Err(_) => (StatusCode::NOT_FOUND).into_response(), + Err(_) => (StatusCode::INTERNAL_SERVER_ERROR).into_response(), } } From 2093f6a25ca0282a749eefdc0fbd6cb5e760dd65 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 10:04:58 +0100 Subject: [PATCH 3/8] Fix or bring back the option for fetching logs --- web/src/client/http.js | 9 +++++++++ web/src/client/manager.js | 6 +++--- web/src/components/core/LogsButton.jsx | 21 +++++++++------------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/web/src/client/http.js b/web/src/client/http.js index 37a4ebdbef..b92413d29d 100644 --- a/web/src/client/http.js +++ b/web/src/client/http.js @@ -106,6 +106,15 @@ class HTTPClient { return response; } + /** + * @param {string} url - Endpoint URL (e.g., "/l10n/config"). + * @return {Promise} Server response. + */ + async getRaw(url) { + const response = await fetch(`${this.baseUrl}${url}`); + return response; + } + /** * @param {string} url - Endpoint URL (e.g., "/l10n/config"). * @param {object} data - Data to submit diff --git a/web/src/client/manager.js b/web/src/client/manager.js index 7b0951a9ff..a3bcb4d64d 100644 --- a/web/src/client/manager.js +++ b/web/src/client/manager.js @@ -82,10 +82,10 @@ class ManagerBaseClient { * Returns the binary content of the YaST logs file * * @todo Implement a mechanism to get the logs. - * @return {Promise} + * @return {Promise} */ async fetchLogs() { - // TODO + return this.client.getRaw("/manager/logs"); } /** @@ -147,6 +147,6 @@ class ManagerClient extends WithProgress( WithStatus(ManagerBaseClient, "/manager/status", MANAGER_SERVICE), "/manager/progress", MANAGER_SERVICE, -) {} +) { } export { ManagerClient }; diff --git a/web/src/components/core/LogsButton.jsx b/web/src/components/core/LogsButton.jsx index aeb3284a72..b5dc674e0a 100644 --- a/web/src/components/core/LogsButton.jsx +++ b/web/src/components/core/LogsButton.jsx @@ -28,7 +28,6 @@ import { Icon } from "~/components/layout"; import { _ } from "~/i18n"; const FILENAME = "agama-installation-logs.tar.bzip2"; -const FILETYPE = "application/x-xz"; /** * Button for collecting and downloading YaST logs @@ -43,16 +42,14 @@ const LogsButton = ({ ...props }) => { const [error, setError] = useState(null); /** - * Helper function for creating the blob and triggering the download automatically + * Helper function for triggering the download automatically * * @note Based on the article "Programmatic file downloads in the browser" found at * https://blog.logrocket.com/programmatic-file-downloads-in-the-browser-9a5186298d5c * - * @param {Uint8Array} data - binary data for creating a {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob Blob} + * @param {string} url - the file location to download from */ - const download = (data) => { - const blob = new Blob([data], { type: FILETYPE }); - const url = URL.createObjectURL(blob); + const autoDownload = (url) => { const a = document.createElement('a'); a.href = url; a.download = FILENAME; @@ -79,8 +76,8 @@ const LogsButton = ({ ...props }) => { const collectAndDownload = () => { setError(null); setIsCollecting(true); - cancellablePromise(client.manager.fetchLogs()) - .then(download) + cancellablePromise(client.manager.fetchLogs().then(response => URL.createObjectURL(response.blob()))) + .then(autoDownload) .catch(setError) .finally(() => setIsCollecting(false)); }; @@ -98,21 +95,21 @@ const LogsButton = ({ ...props }) => { {isCollecting ? _("Collecting logs...") : _("Download logs")} - { isCollecting && + {isCollecting && } + />} - { error && + {error && } + />} ); }; From 471e52583724f67ee53ce84b3e0feaf021c378c5 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 10:05:23 +0100 Subject: [PATCH 4/8] Removed the options for showing logs and terminal --- web/src/components/core/ShowLogButton.jsx | 61 ------------------- .../components/core/ShowLogButton.test.jsx | 44 ------------- .../components/core/ShowTerminalButton.jsx | 54 ---------------- .../core/ShowTerminalButton.test.jsx | 39 ------------ web/src/components/core/Sidebar.jsx | 11 +--- web/src/components/core/Sidebar.test.jsx | 4 -- web/src/components/core/index.js | 2 - 7 files changed, 2 insertions(+), 213 deletions(-) delete mode 100644 web/src/components/core/ShowLogButton.jsx delete mode 100644 web/src/components/core/ShowLogButton.test.jsx delete mode 100644 web/src/components/core/ShowTerminalButton.jsx delete mode 100644 web/src/components/core/ShowTerminalButton.test.jsx diff --git a/web/src/components/core/ShowLogButton.jsx b/web/src/components/core/ShowLogButton.jsx deleted file mode 100644 index 90aa9b10f4..0000000000 --- a/web/src/components/core/ShowLogButton.jsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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, { useState } from "react"; -import { FileViewer } from "~/components/core"; -import { Icon } from "~/components/layout"; -import { Button } from "@patternfly/react-core"; -import { _ } from "~/i18n"; - -/** - * Button for displaying the YaST logs - * @component - */ -const ShowLogButton = () => { - const [isLogDisplayed, setIsLogDisplayed] = useState(false); - - const onClick = () => setIsLogDisplayed(true); - const onClose = () => setIsLogDisplayed(false); - - return ( - <> - - - { isLogDisplayed && - } - - ); -}; - -export default ShowLogButton; diff --git a/web/src/components/core/ShowLogButton.test.jsx b/web/src/components/core/ShowLogButton.test.jsx deleted file mode 100644 index 3958021f45..0000000000 --- a/web/src/components/core/ShowLogButton.test.jsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { ShowLogButton } from "~/components/core"; - -jest.mock("~/components/core/FileViewer", () => () =>
FileViewer Mock
); - -describe("ShowLogButton", () => { - it("renders a button for displaying logs", () => { - plainRender(); - const button = screen.getByRole("button", "Show Logs"); - expect(button).not.toHaveAttribute("disabled"); - }); - - describe("when user clicks on it", () => { - it("displays the FileView component", async () => { - const { user } = plainRender(); - const button = screen.getByRole("button", "Show Logs"); - await user.click(button); - screen.getByText(/FileViewer Mock/); - }); - }); -}); diff --git a/web/src/components/core/ShowTerminalButton.jsx b/web/src/components/core/ShowTerminalButton.jsx deleted file mode 100644 index 3334af849e..0000000000 --- a/web/src/components/core/ShowTerminalButton.jsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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, { useState } from "react"; -import { Button } from "@patternfly/react-core"; - -import { Icon } from "~/components/layout"; -import { _ } from "~/i18n"; - -/** - * Button for displaying the terminal application - * @component - */ -const ShowTerminalButton = () => { - const [isTermDisplayed, setIsTermDisplayed] = useState(false); - - const onClick = () => setIsTermDisplayed(true); - - return ( - <> - - - { isTermDisplayed && "TODO" } - - ); -}; - -export default ShowTerminalButton; diff --git a/web/src/components/core/ShowTerminalButton.test.jsx b/web/src/components/core/ShowTerminalButton.test.jsx deleted file mode 100644 index 90cb617638..0000000000 --- a/web/src/components/core/ShowTerminalButton.test.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { ShowTerminalButton } from "~/components/core"; - -describe("ShowTerminalButton", () => { - it("renders a button that displays after clicking", async () => { - const { user } = plainRender(); - const button = screen.getByRole("button", "Terminal"); - - // no terminal displayed just after the render - expect(screen.queryByText(/TODO/)).not.toBeInTheDocument(); - - await user.click(button); - // it is displayed after clicking the button - screen.getByText(/TODO/); - }); -}); diff --git a/web/src/components/core/Sidebar.jsx b/web/src/components/core/Sidebar.jsx index c7ed29c3a2..97f8b10c65 100644 --- a/web/src/components/core/Sidebar.jsx +++ b/web/src/components/core/Sidebar.jsx @@ -25,10 +25,7 @@ import { InstallerKeymapSwitcher, InstallerLocaleSwitcher } from "~/components/l import { About, Disclosure, - // FIXME: unify names here by renaming LogsButton -> LogButton or ShowLogButton -> ShowLogsButton LogsButton, - ShowLogButton, - ShowTerminalButton, } from "~/components/core"; import { noop } from "~/utils"; import { _ } from "~/i18n"; @@ -47,7 +44,7 @@ import useNodeSiblings from "~/hooks/useNodeSiblings"; * * @param {SidebarProps} */ -export default function Sidebar ({ isOpen, onClose = noop, children }) { +export default function Sidebar({ isOpen, onClose = noop, children }) { const asideRef = useRef(null); const closeButtonRef = useRef(null); const [addAttribute, removeAttribute] = useNodeSiblings(asideRef.current); @@ -136,11 +133,7 @@ export default function Sidebar ({ isOpen, onClose = noop, children }) {
- - - - - + {children}
diff --git a/web/src/components/core/Sidebar.test.jsx b/web/src/components/core/Sidebar.test.jsx index f2a9c26c2a..72cac32ba2 100644 --- a/web/src/components/core/Sidebar.test.jsx +++ b/web/src/components/core/Sidebar.test.jsx @@ -27,8 +27,6 @@ import { If, Sidebar } from "~/components/core"; // Mock some components jest.mock("~/components/core/About", () => () =>
About link mock
); jest.mock("~/components/core/LogsButton", () => () =>
LogsButton mock
); -jest.mock("~/components/core/ShowLogButton", () => () =>
ShowLogButton mock
); -jest.mock("~/components/core/ShowTerminalButton", () => () =>
ShowTerminalButton mock
); jest.mock("~/components/l10n/InstallerKeymapSwitcher", () => () =>
Installer keymap switcher mock
); jest.mock("~/components/l10n/InstallerLocaleSwitcher", () => () =>
Installer locale switcher mock
); @@ -51,8 +49,6 @@ it("renders expected options", () => { screen.getByText("Installer keymap switcher mock"); screen.getByText("Installer locale switcher mock"); screen.getByText("LogsButton mock"); - screen.getByText("ShowLogButton mock"); - screen.getByText("ShowTerminalButton mock"); screen.getByText("About link mock"); }); diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js index 4b12c532a6..72f0740cbc 100644 --- a/web/src/components/core/index.js +++ b/web/src/components/core/index.js @@ -44,7 +44,6 @@ export { default as LoginPage } from "./LoginPage"; export { default as LogsButton } from "./LogsButton"; export { default as FileViewer } from "./FileViewer"; export { default as RowActions } from "./RowActions"; -export { default as ShowLogButton } from "./ShowLogButton"; export { default as Page } from "./Page"; export { default as PasswordAndConfirmationInput } from "./PasswordAndConfirmationInput"; export { default as Popup } from "./Popup"; @@ -52,7 +51,6 @@ export { default as ProgressReport } from "./ProgressReport"; export { default as ProgressText } from "./ProgressText"; export { default as ValidationErrors } from "./ValidationErrors"; export { default as Tip } from "./Tip"; -export { default as ShowTerminalButton } from "./ShowTerminalButton"; export { default as NumericTextInput } from "./NumericTextInput"; export { default as PasswordInput } from "./PasswordInput"; export { default as Selector } from "./Selector"; From 8295c7a00ddafa16d48d570437edf4f8caa7263e Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 11:06:06 +0100 Subject: [PATCH 5/8] Fixed logs button test --- web/src/components/core/LogsButton.test.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/core/LogsButton.test.jsx b/web/src/components/core/LogsButton.test.jsx index cfb9c3ccb3..6eef301fcd 100644 --- a/web/src/components/core/LogsButton.test.jsx +++ b/web/src/components/core/LogsButton.test.jsx @@ -88,9 +88,9 @@ describe("LogsButton", () => { describe("and logs are collected successfully", () => { beforeEach(() => { - // new TextEncoder().encode("Hello logs!") - const data = new Uint8Array([72, 101, 108, 108, 111, 32, 108, 111, 103, 115, 33]); - fetchLogsFn.mockResolvedValue(data); + fetchLogsFn.mockResolvedValue({ + blob: jest.fn().mockResolvedValue(new Blob(["testing"])) + }); }); it("triggers the download", async () => { From 464dcf9021dbad7f220c66b0dffd35939b703df8 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 11:27:30 +0100 Subject: [PATCH 6/8] Added changelog --- rust/package/agama.changes | 6 ++++++ web/package/agama-web-ui.changes | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 971b38b9e2..378479e380 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue May 16 12:48:42 UTC 2024 - Knut Anderssen + +- Allow to download Agama los throgh the manager HTTP API + (gh#openSUSE/1216). + ------------------------------------------------------------------- Thu May 16 12:34:43 UTC 2024 - Imobach Gonzalez Sosa diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 138b1b0123..717c14fab1 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu May 16 12:48:27 UTC 2024 - Knut Anderssen + +- Fix the download logs action in the web UI and drop the broken + actions show logs and show terminal (gh#openSUSE/agama#1216). + ------------------------------------------------------------------- Tue May 14 12:24:23 UTC 2024 - José Iván López González From 7e8d85017e11a65ba2e24d38b92dac9da3d14aaf Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 13:58:58 +0100 Subject: [PATCH 7/8] Revert "Removed the options for showing logs and terminal" This reverts commit 0bd0fa13d34f438126ea5d89556eecd3d6f25387. --- web/src/components/core/ShowLogButton.jsx | 61 +++++++++++++++++++ .../components/core/ShowLogButton.test.jsx | 44 +++++++++++++ .../components/core/ShowTerminalButton.jsx | 54 ++++++++++++++++ .../core/ShowTerminalButton.test.jsx | 39 ++++++++++++ web/src/components/core/Sidebar.jsx | 11 +++- web/src/components/core/Sidebar.test.jsx | 4 ++ web/src/components/core/index.js | 2 + 7 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 web/src/components/core/ShowLogButton.jsx create mode 100644 web/src/components/core/ShowLogButton.test.jsx create mode 100644 web/src/components/core/ShowTerminalButton.jsx create mode 100644 web/src/components/core/ShowTerminalButton.test.jsx diff --git a/web/src/components/core/ShowLogButton.jsx b/web/src/components/core/ShowLogButton.jsx new file mode 100644 index 0000000000..90aa9b10f4 --- /dev/null +++ b/web/src/components/core/ShowLogButton.jsx @@ -0,0 +1,61 @@ +/* + * 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, { useState } from "react"; +import { FileViewer } from "~/components/core"; +import { Icon } from "~/components/layout"; +import { Button } from "@patternfly/react-core"; +import { _ } from "~/i18n"; + +/** + * Button for displaying the YaST logs + * @component + */ +const ShowLogButton = () => { + const [isLogDisplayed, setIsLogDisplayed] = useState(false); + + const onClick = () => setIsLogDisplayed(true); + const onClose = () => setIsLogDisplayed(false); + + return ( + <> + + + { isLogDisplayed && + } + + ); +}; + +export default ShowLogButton; diff --git a/web/src/components/core/ShowLogButton.test.jsx b/web/src/components/core/ShowLogButton.test.jsx new file mode 100644 index 0000000000..3958021f45 --- /dev/null +++ b/web/src/components/core/ShowLogButton.test.jsx @@ -0,0 +1,44 @@ +/* + * 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 { ShowLogButton } from "~/components/core"; + +jest.mock("~/components/core/FileViewer", () => () =>
FileViewer Mock
); + +describe("ShowLogButton", () => { + it("renders a button for displaying logs", () => { + plainRender(); + const button = screen.getByRole("button", "Show Logs"); + expect(button).not.toHaveAttribute("disabled"); + }); + + describe("when user clicks on it", () => { + it("displays the FileView component", async () => { + const { user } = plainRender(); + const button = screen.getByRole("button", "Show Logs"); + await user.click(button); + screen.getByText(/FileViewer Mock/); + }); + }); +}); diff --git a/web/src/components/core/ShowTerminalButton.jsx b/web/src/components/core/ShowTerminalButton.jsx new file mode 100644 index 0000000000..3334af849e --- /dev/null +++ b/web/src/components/core/ShowTerminalButton.jsx @@ -0,0 +1,54 @@ +/* + * 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, { useState } from "react"; +import { Button } from "@patternfly/react-core"; + +import { Icon } from "~/components/layout"; +import { _ } from "~/i18n"; + +/** + * Button for displaying the terminal application + * @component + */ +const ShowTerminalButton = () => { + const [isTermDisplayed, setIsTermDisplayed] = useState(false); + + const onClick = () => setIsTermDisplayed(true); + + return ( + <> + + + { isTermDisplayed && "TODO" } + + ); +}; + +export default ShowTerminalButton; diff --git a/web/src/components/core/ShowTerminalButton.test.jsx b/web/src/components/core/ShowTerminalButton.test.jsx new file mode 100644 index 0000000000..90cb617638 --- /dev/null +++ b/web/src/components/core/ShowTerminalButton.test.jsx @@ -0,0 +1,39 @@ +/* + * 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 { ShowTerminalButton } from "~/components/core"; + +describe("ShowTerminalButton", () => { + it("renders a button that displays after clicking", async () => { + const { user } = plainRender(); + const button = screen.getByRole("button", "Terminal"); + + // no terminal displayed just after the render + expect(screen.queryByText(/TODO/)).not.toBeInTheDocument(); + + await user.click(button); + // it is displayed after clicking the button + screen.getByText(/TODO/); + }); +}); diff --git a/web/src/components/core/Sidebar.jsx b/web/src/components/core/Sidebar.jsx index 97f8b10c65..c7ed29c3a2 100644 --- a/web/src/components/core/Sidebar.jsx +++ b/web/src/components/core/Sidebar.jsx @@ -25,7 +25,10 @@ import { InstallerKeymapSwitcher, InstallerLocaleSwitcher } from "~/components/l import { About, Disclosure, + // FIXME: unify names here by renaming LogsButton -> LogButton or ShowLogButton -> ShowLogsButton LogsButton, + ShowLogButton, + ShowTerminalButton, } from "~/components/core"; import { noop } from "~/utils"; import { _ } from "~/i18n"; @@ -44,7 +47,7 @@ import useNodeSiblings from "~/hooks/useNodeSiblings"; * * @param {SidebarProps} */ -export default function Sidebar({ isOpen, onClose = noop, children }) { +export default function Sidebar ({ isOpen, onClose = noop, children }) { const asideRef = useRef(null); const closeButtonRef = useRef(null); const [addAttribute, removeAttribute] = useNodeSiblings(asideRef.current); @@ -133,7 +136,11 @@ export default function Sidebar({ isOpen, onClose = noop, children }) {
- + + + + + {children}
diff --git a/web/src/components/core/Sidebar.test.jsx b/web/src/components/core/Sidebar.test.jsx index 72cac32ba2..f2a9c26c2a 100644 --- a/web/src/components/core/Sidebar.test.jsx +++ b/web/src/components/core/Sidebar.test.jsx @@ -27,6 +27,8 @@ import { If, Sidebar } from "~/components/core"; // Mock some components jest.mock("~/components/core/About", () => () =>
About link mock
); jest.mock("~/components/core/LogsButton", () => () =>
LogsButton mock
); +jest.mock("~/components/core/ShowLogButton", () => () =>
ShowLogButton mock
); +jest.mock("~/components/core/ShowTerminalButton", () => () =>
ShowTerminalButton mock
); jest.mock("~/components/l10n/InstallerKeymapSwitcher", () => () =>
Installer keymap switcher mock
); jest.mock("~/components/l10n/InstallerLocaleSwitcher", () => () =>
Installer locale switcher mock
); @@ -49,6 +51,8 @@ it("renders expected options", () => { screen.getByText("Installer keymap switcher mock"); screen.getByText("Installer locale switcher mock"); screen.getByText("LogsButton mock"); + screen.getByText("ShowLogButton mock"); + screen.getByText("ShowTerminalButton mock"); screen.getByText("About link mock"); }); diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js index 72f0740cbc..4b12c532a6 100644 --- a/web/src/components/core/index.js +++ b/web/src/components/core/index.js @@ -44,6 +44,7 @@ export { default as LoginPage } from "./LoginPage"; export { default as LogsButton } from "./LogsButton"; export { default as FileViewer } from "./FileViewer"; export { default as RowActions } from "./RowActions"; +export { default as ShowLogButton } from "./ShowLogButton"; export { default as Page } from "./Page"; export { default as PasswordAndConfirmationInput } from "./PasswordAndConfirmationInput"; export { default as Popup } from "./Popup"; @@ -51,6 +52,7 @@ export { default as ProgressReport } from "./ProgressReport"; export { default as ProgressText } from "./ProgressText"; export { default as ValidationErrors } from "./ValidationErrors"; export { default as Tip } from "./Tip"; +export { default as ShowTerminalButton } from "./ShowTerminalButton"; export { default as NumericTextInput } from "./NumericTextInput"; export { default as PasswordInput } from "./PasswordInput"; export { default as Selector } from "./Selector"; From 0c493efe67270c6590c92b1478d09b56b489fe8c Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 16 May 2024 15:20:51 +0100 Subject: [PATCH 8/8] Changes based on Code Review --- rust/agama-lib/src/error.rs | 2 ++ rust/agama-server/src/manager/web.rs | 2 +- rust/data.json | 36 ++++++++++++++++++++++++ web/src/client/http.js | 9 ------ web/src/client/manager.js | 3 +- web/src/components/core/Sidebar.jsx | 12 ++------ web/src/components/core/Sidebar.test.jsx | 2 -- 7 files changed, 43 insertions(+), 23 deletions(-) create mode 100644 rust/data.json diff --git a/rust/agama-lib/src/error.rs b/rust/agama-lib/src/error.rs index b09e7ede3e..513381803a 100644 --- a/rust/agama-lib/src/error.rs +++ b/rust/agama-lib/src/error.rs @@ -6,6 +6,8 @@ use zbus::{self, zvariant}; #[derive(Error, Debug)] pub enum ServiceError { + #[error("Cannot generate Agama logs: {0}")] + CannotGenerateLogs(String), #[error("D-Bus service error: {0}")] DBus(#[from] zbus::Error), #[error("Could not connect to Agama bus at '{0}': {1}")] diff --git a/rust/agama-server/src/manager/web.rs b/rust/agama-server/src/manager/web.rs index 7e5660e400..dd238f4b4e 100644 --- a/rust/agama-server/src/manager/web.rs +++ b/rust/agama-server/src/manager/web.rs @@ -195,7 +195,7 @@ async fn generate_logs() -> Result { Command::new("agama") .args(["logs", "store", "-d", path.as_str()]) .status() - .expect("Cannot generate logs"); + .map_err(|e| ServiceError::CannotGenerateLogs(e.to_string()))?; let full_path = format!("{path}.tar.bz2"); Ok(full_path) diff --git a/rust/data.json b/rust/data.json new file mode 100644 index 0000000000..a6b2691850 --- /dev/null +++ b/rust/data.json @@ -0,0 +1,36 @@ +{ + "network": { + "connections": [ + { + "id": "eth0", + "method4": "auto", + "method6": "auto", + "addresses": [ + "192.168.0.101/24" + ], + "interface": "eth0", + "status": "up" + }, + { + "id": "Wired connection 1", + "method4": "auto", + "method6": "auto", + "interface": "enp5s0f4u2c2", + "status": "up" + }, + { + "id": "Sarambeque", + "method4": "auto", + "method6": "auto", + "wireless": { + "password": "vikingo.pass", + "security": "wpa-psk", + "ssid": "Sarambeque", + "mode": "infrastructure" + }, + "status": "up" + } + ] + } +} + diff --git a/web/src/client/http.js b/web/src/client/http.js index b92413d29d..37a4ebdbef 100644 --- a/web/src/client/http.js +++ b/web/src/client/http.js @@ -106,15 +106,6 @@ class HTTPClient { return response; } - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @return {Promise} Server response. - */ - async getRaw(url) { - const response = await fetch(`${this.baseUrl}${url}`); - return response; - } - /** * @param {string} url - Endpoint URL (e.g., "/l10n/config"). * @param {object} data - Data to submit diff --git a/web/src/client/manager.js b/web/src/client/manager.js index a3bcb4d64d..5b4b7647f0 100644 --- a/web/src/client/manager.js +++ b/web/src/client/manager.js @@ -85,7 +85,8 @@ class ManagerBaseClient { * @return {Promise} */ async fetchLogs() { - return this.client.getRaw("/manager/logs"); + const response = await fetch(`${this.client.baseUrl}/manager/logs`); + return response; } /** diff --git a/web/src/components/core/Sidebar.jsx b/web/src/components/core/Sidebar.jsx index c7ed29c3a2..3dd8e14a5c 100644 --- a/web/src/components/core/Sidebar.jsx +++ b/web/src/components/core/Sidebar.jsx @@ -24,11 +24,7 @@ import { Icon } from "~/components/layout"; import { InstallerKeymapSwitcher, InstallerLocaleSwitcher } from "~/components/l10n"; import { About, - Disclosure, - // FIXME: unify names here by renaming LogsButton -> LogButton or ShowLogButton -> ShowLogsButton LogsButton, - ShowLogButton, - ShowTerminalButton, } from "~/components/core"; import { noop } from "~/utils"; import { _ } from "~/i18n"; @@ -47,7 +43,7 @@ import useNodeSiblings from "~/hooks/useNodeSiblings"; * * @param {SidebarProps} */ -export default function Sidebar ({ isOpen, onClose = noop, children }) { +export default function Sidebar({ isOpen, onClose = noop, children }) { const asideRef = useRef(null); const closeButtonRef = useRef(null); const [addAttribute, removeAttribute] = useNodeSiblings(asideRef.current); @@ -136,11 +132,7 @@ export default function Sidebar ({ isOpen, onClose = noop, children }) {
- - - - - + {children}
diff --git a/web/src/components/core/Sidebar.test.jsx b/web/src/components/core/Sidebar.test.jsx index f2a9c26c2a..97aadfb671 100644 --- a/web/src/components/core/Sidebar.test.jsx +++ b/web/src/components/core/Sidebar.test.jsx @@ -51,8 +51,6 @@ it("renders expected options", () => { screen.getByText("Installer keymap switcher mock"); screen.getByText("Installer locale switcher mock"); screen.getByText("LogsButton mock"); - screen.getByText("ShowLogButton mock"); - screen.getByText("ShowTerminalButton mock"); screen.getByText("About link mock"); });