From e2ec16e29506b0a754f2584878443c6588394f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Thu, 10 Oct 2024 16:03:09 +0200 Subject: [PATCH] PoC for online demo - Compile with AGAMA_DEMO= to build a static site with the recorded data --- web/src/App.tsx | 31 ++++++++++++++++++++++- web/src/api/http.ts | 42 +++++++++++++++++++++++++++---- web/src/client/ws.js | 3 +++ web/src/context/auth.jsx | 28 +++++++++++---------- web/src/context/installer.jsx | 2 +- web/src/context/installerL10n.tsx | 2 +- web/webpack.config.js | 2 +- 7 files changed, 88 insertions(+), 22 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index ca9c11b9bc..0e0c478e89 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -20,7 +20,8 @@ * find current contact information at www.suse.com. */ -import React from "react"; +import React, { useState } from "react"; +import { Alert, AlertActionCloseButton } from "@patternfly/react-core"; import { Navigate, Outlet, useLocation } from "react-router-dom"; import { ServerError } from "~/components/core"; import { Loading, PlainLayout } from "~/components/layout"; @@ -35,6 +36,8 @@ import { useDeprecatedChanges } from "~/queries/storage"; import { ROOT, PRODUCT } from "~/routes/paths"; import { InstallationPhase } from "~/types/status"; +import { _ } from "~/i18n"; + /** * Main application component. */ @@ -44,6 +47,7 @@ function App() { const { connected, error } = useInstallerClientStatus(); const { selectedProduct, products } = useProduct(); const { language } = useInstallerL10n(); + const [isOpen, setIsOpen] = useState(true); useL10nConfigChanges(); useProductChanges(); useIssuesChanges(); @@ -89,8 +93,33 @@ function App() { if (!language) return null; + // TRANSLATORS: the text in square brackets [] is a clickable link + const [msgStart, msgLink, msgEnd] = _( + "The demo runs in read-only mode, you cannot change any values and the installation cannot \ +be started. To find more details about Agama check the [home page].", + ).split(/[[\]]/); + + const alert = + process.env.AGAMA_DEMO && isOpen ? ( + setIsOpen(false)} />} + > +

+ {msgStart} + + {msgLink} + + {msgEnd} +

+
+ ) : null; + return ( <> + {alert} diff --git a/web/src/api/http.ts b/web/src/api/http.ts index aff969cc7c..01e06b481d 100644 --- a/web/src/api/http.ts +++ b/web/src/api/http.ts @@ -22,17 +22,45 @@ import axios from "axios"; +let demo_data; +if (process.env.AGAMA_DEMO) { + demo_data = await import(process.env.AGAMA_DEMO); +} + const http = axios.create({ responseType: "json", }); +function mock_response(method: string, url: string) { + console.info("Demo mode, ignoring request", method, url); + + return Promise.resolve({ + data: {}, + status: 200, + statusText: "OK", + headers: {}, + config: { + headers: {}, + }, + }); +} + /** * Retrieves the object from given URL * * @param url - HTTP URL * @return data from the response body */ -const get = (url: string) => http.get(url).then(({ data }) => data); +const get = (url: string) => { + if (process.env.AGAMA_DEMO) { + if (!(url in demo_data)) { + console.error("Missing demo data for REST API path", url); + } + return Promise.resolve(demo_data[url]); + } else { + return http.get(url).then(({ data }) => data); + } +}; /** * Performs a PATCH request with the given URL and data @@ -40,7 +68,8 @@ const get = (url: string) => http.get(url).then(({ data }) => data); * @param url - endpoint URL * @param data - Request payload */ -const patch = (url: string, data?: object) => http.patch(url, data); +const patch = (url: string, data?: object) => + process.env.AGAMA_DEMO ? mock_response("PATCH", url) : http.patch(url, data); /** * Performs a PUT request with the given URL and data @@ -48,7 +77,8 @@ const patch = (url: string, data?: object) => http.patch(url, data); * @param url - endpoint URL * @param data - request payload */ -const put = (url: string, data: object) => http.put(url, data); +const put = (url: string, data: object) => + process.env.AGAMA_DEMO ? mock_response("PUT", url) : http.put(url, data); /** * Performs a POST request with the given URL and data @@ -56,7 +86,8 @@ const put = (url: string, data: object) => http.put(url, data); * @param url - endpoint URL * @param data - request payload */ -const post = (url: string, data?: object) => http.post(url, data); +const post = (url: string, data?: object) => + process.env.AGAMA_DEMO ? mock_response("POST", url) : http.post(url, data); /** * Performs a DELETE request on the given URL @@ -64,6 +95,7 @@ const post = (url: string, data?: object) => http.post(url, data); * @param url - endpoint URL * @param data - request payload */ -const del = (url: string) => http.delete(url); +const del = (url: string) => + process.env.AGAMA_DEMO ? mock_response("DELETE", url) : http.delete(url); export { get, patch, post, put, del }; diff --git a/web/src/client/ws.js b/web/src/client/ws.js index b63c8e2a51..f45f0dd94a 100644 --- a/web/src/client/ws.js +++ b/web/src/client/ws.js @@ -82,10 +82,13 @@ class WSClient { } isConnected() { + if (process.env.AGAMA_DEMO) return true; return this.wsState() === SocketStates.CONNECTED; } buildClient() { + if (process.env.AGAMA_DEMO) return null; + const client = new WebSocket(this.url); client.onopen = () => { console.log("Websocket connected"); diff --git a/web/src/context/auth.jsx b/web/src/context/auth.jsx index 536e8c054b..cf25df7da5 100644 --- a/web/src/context/auth.jsx +++ b/web/src/context/auth.jsx @@ -49,7 +49,7 @@ const AuthErrors = Object.freeze({ * @param {React.ReactNode} [props.children] - content to display within the provider */ function AuthProvider({ children }) { - const [isLoggedIn, setIsLoggedIn] = useState(undefined); + const [isLoggedIn, setIsLoggedIn] = useState(process.env.AGAMA_DEMO ? true : undefined); const [error, setError] = useState(null); const login = useCallback(async (password) => { @@ -79,19 +79,21 @@ function AuthProvider({ children }) { }, []); useEffect(() => { - fetch("/api/auth", { - headers: { "Content-Type": "application/json" }, - }) - .then((response) => { - setIsLoggedIn(response.status === 200); - if (response.status >= 500 && response.status < 600) { - setError(AuthErrors.SERVER); - } - if (response.status >= 400 && response.status < 500) { - setError(AuthErrors.AUTH); - } + if (!process.env.AGAMA_DEMO) { + fetch("/api/auth", { + headers: { "Content-Type": "application/json" }, }) - .catch(() => setIsLoggedIn(false)); + .then((response) => { + setIsLoggedIn(response.status === 200); + if (response.status >= 500 && response.status < 600) { + setError(AuthErrors.SERVER); + } + if (response.status >= 400 && response.status < 500) { + setError(AuthErrors.AUTH); + } + }) + .catch(() => setIsLoggedIn(false)); + } }, []); return ( diff --git a/web/src/context/installer.jsx b/web/src/context/installer.jsx index b2a96aefc2..d65ceb6cb6 100644 --- a/web/src/context/installer.jsx +++ b/web/src/context/installer.jsx @@ -77,7 +77,7 @@ function useInstallerClientStatus() { */ function InstallerClientProvider({ children, client = null }) { const [value, setValue] = useState(client); - const [connected, setConnected] = useState(false); + const [connected, setConnected] = useState(process.env.AGAMA_DEMO !== undefined); const [error, setError] = useState(false); useEffect(() => { diff --git a/web/src/context/installerL10n.tsx b/web/src/context/installerL10n.tsx index 3e3dfed660..2967012617 100644 --- a/web/src/context/installerL10n.tsx +++ b/web/src/context/installerL10n.tsx @@ -194,7 +194,7 @@ function reload(newLanguage: string) { */ function InstallerL10nProvider({ children }: { children?: React.ReactNode }) { const { connected } = useInstallerClientStatus(); - const [language, setLanguage] = useState(undefined); + const [language, setLanguage] = useState(process.env.AGAMA_DEMO ? "en-us" : undefined); const [keymap, setKeymap] = useState(undefined); const syncBackendLanguage = useCallback(async () => { diff --git a/web/webpack.config.js b/web/webpack.config.js index ac73bbbbe2..d36b9c7da1 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -54,7 +54,7 @@ const plugins = [ // the current value of the environment variable, that variable is set to // "true" when running the development server ("npm run server") // https://webpack.js.org/plugins/environment-plugin/ - new webpack.EnvironmentPlugin({ WEBPACK_SERVE: null, LOCAL_CONNECTION: null }), + new webpack.EnvironmentPlugin({ WEBPACK_SERVE: null, LOCAL_CONNECTION: null, AGAMA_DEMO: null }), ].filter(Boolean); if (eslint) {