Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions rust/agama-server/src/manager/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ pub struct ManagerState<'a> {
pub struct InstallerStatus {
/// Current installation phase.
phase: InstallationPhase,
/// List of busy services.
busy: Vec<String>,
/// Whether the service is busy.
is_busy: bool,
/// Whether Agama is running on Iguana.
iguana: bool,
use_iguana: bool,
/// Whether it is possible to start the installation.
can_install: bool,
}
Expand Down Expand Up @@ -183,8 +183,8 @@ async fn installer_status(
let status = InstallerStatus {
phase,
can_install,
busy: state.manager.busy_services().await?,
iguana: state.manager.use_iguana().await?,
is_busy: state.manager.is_busy().await,
use_iguana: state.manager.use_iguana().await?,
};
Ok(Json(status))
}
Expand Down
3 changes: 0 additions & 3 deletions rust/agama-server/src/web/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ pub enum Event {
InstallationPhaseChanged {
phase: InstallationPhase,
},
BusyServicesChanged {
services: Vec<String>,
},
ServiceStatusChanged {
service: String,
status: u32,
Expand Down
20 changes: 13 additions & 7 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import { ServerError, Installation } from "~/components/core";
import { useInstallerL10n } from "./context/installerL10n";
import { useInstallerClientStatus } from "~/context/installer";
import { useProduct, useProductChanges } from "./queries/software";
import { CONFIG, INSTALL, STARTUP } from "~/client/phase";
import { BUSY } from "~/client/status";
Comment thread
imobachgs marked this conversation as resolved.
import { useL10nConfigChanges } from "~/queries/l10n";
import { useIssuesChanges } from "./queries/issues";
import { useInstallerStatus, useInstallerStatusChanges } from "./queries/status";
import { PATHS as PRODUCT_PATHS } from "./routes/products";
import SimpleLayout from "./SimpleLayout";
import { InstallationPhase } from "./types/status";

/**
* Main application component.
Expand All @@ -43,18 +43,20 @@ import SimpleLayout from "./SimpleLayout";
*/
function App() {
const location = useLocation();
const { connected, error, phase, status } = useInstallerClientStatus();
const { isBusy, phase } = useInstallerStatus({ suspense: true });
const { connected, error } = useInstallerClientStatus();
const { selectedProduct, products } = useProduct();
const { language } = useInstallerL10n();
useL10nConfigChanges();
useProductChanges();
useIssuesChanges();
useInstallerStatusChanges();

const Content = () => {
if (error) return <ServerError />;

if (phase === INSTALL) {
return <Installation status={status} />;
if (phase === InstallationPhase.Install) {
return <Installation isBusy={isBusy} />;
}

if (!products || !connected)
Expand All @@ -64,15 +66,19 @@ function App() {
</SimpleLayout>
);

if ((phase === STARTUP && status === BUSY) || phase === undefined || status === undefined) {
Comment thread
dgdavid marked this conversation as resolved.
if (phase === InstallationPhase.Startup && isBusy) {
return <Loading />;
}

if (selectedProduct === undefined && location.pathname !== PRODUCT_PATHS.root) {
return <Navigate to="/products" />;
}

if (phase === CONFIG && status === BUSY && location.pathname !== PRODUCT_PATHS.progress) {
if (
phase === InstallationPhase.Config &&
isBusy &&
location.pathname !== PRODUCT_PATHS.progress
) {
return <Navigate to={PRODUCT_PATHS.progress} />;
}

Expand Down
36 changes: 18 additions & 18 deletions web/src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ import { installerRender } from "~/test-utils";

import App from "./App";
import { createClient } from "~/client";
import { STARTUP, CONFIG, INSTALL } from "~/client/phase";
import { IDLE, BUSY } from "~/client/status";
import { useL10nConfigChanges } from "./queries/l10n";
import { useProductChanges } from "./queries/software";
import { useIssuesChanges } from "./queries/issues";
import { InstallationPhase } from "./types/status";

jest.mock("~/client");

Expand Down Expand Up @@ -59,15 +55,19 @@ jest.mock("~/queries/issues", () => ({
}));

const mockClientStatus = {
connected: true,
error: false,
phase: STARTUP,
status: BUSY,
phase: InstallationPhase.Startup,
isBusy: true,
};

jest.mock("~/queries/status", () => ({
...jest.requireActual("~/queries/status"),
useInstallerStatus: () => mockClientStatus,
useInstallerStatusChanges: () => jest.fn(),
}));

jest.mock("~/context/installer", () => ({
...jest.requireActual("~/context/installer"),
useInstallerClientStatus: () => mockClientStatus,
useInstallerClientStatus: () => ({ connected: true, error: false }),
}));

// Mock some components,
Expand Down Expand Up @@ -115,8 +115,8 @@ describe("App", () => {

describe("when the service is busy during startup", () => {
beforeEach(() => {
mockClientStatus.phase = STARTUP;
mockClientStatus.status = BUSY;
mockClientStatus.phase = InstallationPhase.Startup;
mockClientStatus.isBusy = true;
});

it("renders the Loading screen", async () => {
Expand All @@ -125,14 +125,14 @@ describe("App", () => {
});
});

describe("on the CONFIG phase", () => {
describe("on the configuration phase", () => {
beforeEach(() => {
mockClientStatus.phase = CONFIG;
mockClientStatus.phase = InstallationPhase.Config;
});

describe("if the service is busy", () => {
beforeEach(() => {
mockClientStatus.status = BUSY;
mockClientStatus.isBusy = true;
mockSelectedProduct = { id: "Tumbleweed" };
});

Expand All @@ -144,7 +144,7 @@ describe("App", () => {

describe("if the service is not busy", () => {
beforeEach(() => {
mockClientStatus.status = IDLE;
mockClientStatus.isBusy = false;
});

it("renders the application content", async () => {
Expand All @@ -154,9 +154,9 @@ describe("App", () => {
});
});

describe("on the INSTALL phase", () => {
describe("on the installaiton phase", () => {
beforeEach(() => {
mockClientStatus.phase = INSTALL;
mockClientStatus.phase = InstallationPhase.Install;
mockSelectedProduct = { id: "Fake product" };
});

Expand Down
14 changes: 1 addition & 13 deletions web/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@

import { L10nClient } from "./l10n";
import { ManagerClient } from "./manager";
import { Monitor } from "./monitor";
import { ProductClient, SoftwareClient } from "./software";
import { StorageClient } from "./storage";
import phase from "./phase";
import { QuestionsClient } from "./questions";
import { NetworkClient } from "./network";
import { HTTPClient, WSClient } from "./http";
Expand All @@ -35,10 +32,7 @@ import { HTTPClient, WSClient } from "./http";
* @typedef {object} InstallerClient
* @property {L10nClient} l10n - localization client.
* @property {ManagerClient} manager - manager client.
* property {Monitor} monitor - service monitor. (FIXME)
* @property {NetworkClient} network - network client.
* @property {ProductClient} product - product client.
* @property {SoftwareClient} software - software client.
* @property {StorageClient} storage - storage client.
* @property {QuestionsClient} questions - questions client.
* @property {() => WSClient} ws - Agama WebSocket client.
Expand All @@ -60,11 +54,8 @@ const createClient = (url) => {
const client = new HTTPClient(url);
const l10n = new L10nClient(client);
// TODO: unify with the manager client
const product = new ProductClient(client);
const manager = new ManagerClient(client);
// const monitor = new Monitor(address, MANAGER_SERVICE);
const network = new NetworkClient(client);
const software = new SoftwareClient(client);
const storage = new StorageClient(client);
const questions = new QuestionsClient(client);

Expand All @@ -73,11 +64,8 @@ const createClient = (url) => {

return {
l10n,
product,
manager,
// monitor,
network,
software,
storage,
questions,
isConnected,
Expand All @@ -94,4 +82,4 @@ const createDefaultClient = async () => {
return createClient(httpUrl);
};

export { createClient, createDefaultClient, phase };
export { createClient, createDefaultClient };
77 changes: 2 additions & 75 deletions web/src/client/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,10 @@

// @ts-check

import { WithStatus } from "./mixins";

const MANAGER_SERVICE = "org.opensuse.Agama.Manager1";

/**
* Manager base client
*
* @ignore
* Client to interact with the Agama manager service
*/
class ManagerBaseClient {
class ManagerClient {
/**
* @param {import("./http").HTTPClient} client - HTTP client.
*/
Expand Down Expand Up @@ -60,24 +54,6 @@ class ManagerBaseClient {
return this.client.post("/manager/install", {});
}

/**
* Checks whether it is possible to start the installation
*
* It might happen that there are some validation errors. In that case,
* it is not possible to proceed with the installation.
*
* @return {Promise<boolean>}
*/
async canInstall() {
const response = await this.client.get("/manager/installer");
if (!response.ok) {
console.error("Failed to get installer config: ", response);
return false;
}
const installer = await response.json();
return installer.canInstall;
}

/**
* Returns the binary content of the YaST logs file
*
Expand All @@ -93,61 +69,12 @@ class ManagerBaseClient {
return response;
}

/**
* Return the installer status
*
* @return {Promise<number>}
*/
async getPhase() {
const response = await this.client.get("/manager/installer");
if (!response.ok) {
console.error("Failed to get installer config: ", response);
return 0;
}
const installer = await response.json();
return installer.phase;
}

/**
* Register a callback to run when the "CurrentInstallationPhase" changes
*
* @param {function} handler - callback function
* @return {import ("./dbus").RemoveFn} function to disable the callback
*/
onPhaseChange(handler) {
return this.client.onEvent("InstallationPhaseChanged", ({ phase }) => {
if (phase) {
handler(phase);
}
});
}

/**
* Runs cleanup when installation is done
*/
finishInstallation() {
return this.client.post("/manager/finish", {});
}

/**
* Returns whether Iguana is used on the system
*
* @return {Promise<boolean>}
*/
async useIguana() {
const response = await this.client.get("/manager/installer");
if (!response.ok) {
console.error("Failed to get installer config: ", response);
return false;
}
const installer = await response.json();
return installer.iguana;
}
}

/**
* Client to interact with the Agama manager service
*/
class ManagerClient extends WithStatus(ManagerBaseClient, "/manager/status", MANAGER_SERVICE) {}

export { ManagerClient };
Loading