Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webui bz report offline #5075

Merged
merged 2 commits into from
Aug 29, 2023
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
31 changes: 31 additions & 0 deletions ui/webui/src/actions/network-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Red Hat, Inc.
*
* This program 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.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with This program; If not, see <http://www.gnu.org/licenses/>.
*/

import {
getConnected,
} from "../apis/network.js";

export const getConnectedAction = () => {
return async (dispatch) => {
const connected = await getConnected();

return dispatch({
type: "GET_NETWORK_CONNECTED",
payload: { connected }
});
};
};
83 changes: 83 additions & 0 deletions ui/webui/src/apis/network.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 Red Hat, Inc.
*
* This program 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.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with This program; If not, see <http://www.gnu.org/licenses/>.
*/

import cockpit from "cockpit";

import { getConnectedAction } from "../actions/network-actions.js";
import { debug } from "../helpers/log.js";

export class NetworkClient {
constructor (address) {
if (NetworkClient.instance && (!address || NetworkClient.instance.address === address)) {
return NetworkClient.instance;
}

NetworkClient.instance?.client.close();

NetworkClient.instance = this;

this.client = cockpit.dbus(
"org.fedoraproject.Anaconda.Modules.Network",
{ superuser: "try", bus: "none", address }
);
this.address = address;
}

init () {
this.client.addEventListener("close", () => console.error("Network client closed"));
}
}

/**
* @returns {Promise} The bool state of the network connection
*/
export const getConnected = () => {
return (
new NetworkClient().client.call(
"/org/fedoraproject/Anaconda/Modules/Network",
"org.freedesktop.DBus.Properties",
"Get",
["org.fedoraproject.Anaconda.Modules.Network", "Connected"]
)
.then(res => res[0].v)
);
};

export const startEventMonitorNetwork = ({ dispatch }) => {
return new NetworkClient().client.subscribe(
{ },
(path, iface, signal, args) => {
switch (signal) {
case "PropertiesChanged":
if (args[0] === "org.fedoraproject.Anaconda.Modules.Network" && Object.hasOwn(args[1], "Connected")) {
dispatch(getConnectedAction());
} else {
debug(`Unhandled signal on ${path}: ${iface}.${signal}`, JSON.stringify(args));
}
break;
default:
debug(`Unhandled signal on ${path}: ${iface}.${signal}`, JSON.stringify(args));
}
}
);
};

export const initDataNetwork = ({ dispatch }) => {
return Promise.all([
dispatch(getConnectedAction())
]);
};
4 changes: 2 additions & 2 deletions ui/webui/src/components/AnacondaHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { HeaderKebab } from "./HeaderKebab.jsx";

const _ = cockpit.gettext;

export const AnacondaHeader = ({ beta, title, reportLinkURL }) => {
export const AnacondaHeader = ({ beta, title, reportLinkURL, isConnected }) => {
const prerelease = _("Pre-release");
const betanag = beta
? (
Expand Down Expand Up @@ -68,7 +68,7 @@ export const AnacondaHeader = ({ beta, title, reportLinkURL }) => {
<Text component="h1">{title}</Text>
</TextContent>
{betanag}
<HeaderKebab reportLinkURL={reportLinkURL} />
<HeaderKebab reportLinkURL={reportLinkURL} isConnected={isConnected} />
</Flex>
</PageSection>
);
Expand Down
17 changes: 11 additions & 6 deletions ui/webui/src/components/Error.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
TextVariants,
Text,
} from "@patternfly/react-core";
import { ExternalLinkAltIcon } from "@patternfly/react-icons";
import { ExternalLinkAltIcon, DisconnectedIcon } from "@patternfly/react-icons";

import { exitGui } from "../helpers/exit.js";

Expand Down Expand Up @@ -64,7 +64,8 @@ export const BZReportModal = ({
logFile,
detailsLabel,
detailsContent,
buttons
buttons,
isConnected
}) => {
const [logContent, setLogContent] = useState();
const [preparingReport, setPreparingReport] = useState(false);
Expand Down Expand Up @@ -97,14 +98,16 @@ export const BZReportModal = ({
<Stack hasGutter>
<FormHelperText isHidden={false}>
<HelperText>
<HelperTextItem>{_("Reporting an issue will send information over the network. Plese review and edit the attached log to remove any sensitive information.")}</HelperTextItem>
{isConnected
? <HelperTextItem> {_("Reporting an issue will send information over the network. Please review and edit the attached log to remove any sensitive information.")} </HelperTextItem>
: <HelperTextItem icon={<DisconnectedIcon />}> {_("Network not available. Configure the network in the top bar menu to report the issue.")} </HelperTextItem>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good for now in the Fedora Workstation environment, but we should take a note to handle this somehow better for other environments. Eq. boot.iso currently does not have a top bar & other Live spins could have different mechanisms for this.

</HelperText>
</FormHelperText>
<StackItem>
<Button
variant="primary"
isLoading={preparingReport}
isDisabled={logContent === undefined || preparingReport}
isDisabled={logContent === undefined || preparingReport || !isConnected}
icon={<ExternalLinkAltIcon />}
onClick={() => openBZIssue(reportLinkURL)}
component="a">
Expand Down Expand Up @@ -172,7 +175,7 @@ const quitButton = (isBootIso) => {
);
};

export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
export const CriticalError = ({ exception, isBootIso, isConnected, reportLinkURL }) => {
const context = exception.contextData?.context;
const description = context
? cockpit.format(_("The installer cannot continue due to a critical error: $0"), _(context))
Expand All @@ -190,6 +193,7 @@ export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
detailsLabel={_("Error details")}
detailsContent={exceptionInfo(exception, idPrefix)}
buttons={[quitButton(isBootIso)]}
isConnected={isConnected}
/>

);
Expand All @@ -212,7 +216,7 @@ const cancelButton = (onClose) => {
);
};

export const UserIssue = ({ reportLinkURL, setIsReportIssueOpen }) => {
export const UserIssue = ({ reportLinkURL, setIsReportIssueOpen, isConnected }) => {
return (
<BZReportModal
description={_("The following log will be sent to the issue tracking system where you may provide additional details.")}
Expand All @@ -222,6 +226,7 @@ export const UserIssue = ({ reportLinkURL, setIsReportIssueOpen }) => {
titleIconVariant={null}
logFile="/tmp/webui.log"
buttons={[cancelButton(() => setIsReportIssueOpen(false))]}
isConnected={isConnected}
/>
);
};
Expand Down
3 changes: 2 additions & 1 deletion ui/webui/src/components/HeaderKebab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const AnacondaAboutModal = ({ isModalOpen, setIsAboutModalOpen }) => {
);
};

export const HeaderKebab = ({ reportLinkURL }) => {
export const HeaderKebab = ({ reportLinkURL, isConnected }) => {
const [isOpen, setIsOpen] = useState(false);
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false);
const [isReportIssueOpen, setIsReportIssueOpen] = useState(false);
Expand Down Expand Up @@ -167,6 +167,7 @@ export const HeaderKebab = ({ reportLinkURL }) => {
<UserIssue
reportLinkURL={reportLinkURL}
setIsReportIssueOpen={setIsReportIssueOpen}
isConnected={isConnected}
/>}
</>
);
Expand Down
15 changes: 12 additions & 3 deletions ui/webui/src/components/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { LocalizationClient, initDataLocalization, startEventMonitorLocalization
import { StorageClient, initDataStorage, startEventMonitorStorage } from "../apis/storage.js";
import { PayloadsClient } from "../apis/payloads";
import { RuntimeClient, getIsFinal } from "../apis/runtime";
import { NetworkClient, initDataNetwork, startEventMonitorNetwork } from "../apis/network.js";

import { readConf } from "../helpers/conf.js";
import { debug } from "../helpers/log.js";
Expand Down Expand Up @@ -69,7 +70,8 @@ export const Application = () => {
new StorageClient(address),
new PayloadsClient(address),
new RuntimeClient(address),
new BossClient(address)
new BossClient(address),
new NetworkClient(address),
];
clients.forEach(c => c.init());

Expand All @@ -78,11 +80,13 @@ export const Application = () => {
Promise.all([
initDataStorage({ dispatch }),
initDataLocalization({ dispatch }),
initDataNetwork({ dispatch }),
])
.then(() => {
setStoreInitialized(true);
startEventMonitorStorage({ dispatch });
startEventMonitorLocalization({ dispatch });
startEventMonitorNetwork({ dispatch });
}, onCritFail({ context: N_("Reading information about the computer failed.") }));

getIsFinal().then(
Expand Down Expand Up @@ -128,7 +132,7 @@ export const Application = () => {
const page = (
<>
{criticalError &&
<CriticalError exception={criticalError} isBootIso={isBootIso} reportLinkURL={bzReportURL} />}
<CriticalError exception={criticalError} isBootIso={isBootIso} isConnected={state.network.connected} reportLinkURL={bzReportURL} />}
<Page
data-debug={conf.Anaconda.debug}
>
Expand Down Expand Up @@ -156,7 +160,12 @@ export const Application = () => {
})}
</AlertGroup>}
<PageGroup stickyOnBreakpoint={{ default: "top" }}>
<AnacondaHeader beta={beta} title={title} reportLinkURL={bzReportURL} />
<AnacondaHeader
beta={beta}
title={title}
reportLinkURL={bzReportURL}
isConnected={state.network.connected}
/>
</PageGroup>
<AddressContext.Provider value={address}>
<WithDialogs>
Expand Down
15 changes: 15 additions & 0 deletions ui/webui/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ export const localizationInitialState = {
commonLocales: []
};

/* Intial state for the network store substate */
export const networkInitialState = {
connected: null
};

/* Initial state for the global store */
export const initialState = {
localization: localizationInitialState,
storage: storageInitialState,
network: networkInitialState,
};

/* Custom hook to use the reducer with async actions */
Expand All @@ -64,6 +70,7 @@ export const reducer = (state, action) => {
return ({
localization: localizationReducer(state.localization, action),
storage: storageReducer(state.storage, action),
network: networkReducer(state.network, action),
});
};

Expand All @@ -90,3 +97,11 @@ export const localizationReducer = (state = localizationInitialState, action) =>
return state;
}
};

export const networkReducer = (state = networkInitialState, action) => {
if (action.type === "GET_NETWORK_CONNECTED") {
return { ...state, connected: action.payload.connected };
} else {
return state;
}
};