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

F39 add offline version to error reporting #5103

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
51 changes: 30 additions & 21 deletions ui/webui/src/components/Error.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import {
HelperTextItem,
Modal,
ModalVariant,
Stack,
StackItem,
TextArea,
TextContent,
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 @@ -62,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 @@ -92,18 +95,27 @@ export const BZReportModal = ({
titleIconVariant={titleIconVariant}
variant={ModalVariant.large}
footer={
<>
<Button
variant="primary"
isLoading={preparingReport}
isDisabled={logContent === undefined || preparingReport}
icon={<ExternalLinkAltIcon />}
onClick={() => openBZIssue(reportLinkURL)}
component="a">
{preparingReport ? _("Preparing report") : _("Report issue")}
</Button>
{buttons}
</>
<Stack hasGutter>
<FormHelperText isHidden={false}>
<HelperText>
{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>}
</HelperText>
</FormHelperText>
<StackItem>
<Button
variant="primary"
isLoading={preparingReport}
isDisabled={logContent === undefined || preparingReport || !isConnected}
icon={<ExternalLinkAltIcon />}
onClick={() => openBZIssue(reportLinkURL)}
component="a">
{preparingReport ? _("Preparing report") : _("Report issue")}
</Button>
{buttons}
</StackItem>
</Stack>
}>
<Form>
{detailsLabel &&
Expand All @@ -125,11 +137,6 @@ export const BZReportModal = ({
isDisabled={logContent === undefined || preparingReport}
rows={25}
/>
<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>
</HelperText>
</FormHelperText>
</FormGroup>
</Form>
</Modal>
Expand Down Expand Up @@ -168,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 @@ -186,6 +193,7 @@ export const CriticalError = ({ exception, isBootIso, reportLinkURL }) => {
detailsLabel={_("Error details")}
detailsContent={exceptionInfo(exception, idPrefix)}
buttons={[quitButton(isBootIso)]}
isConnected={isConnected}
/>

);
Expand All @@ -208,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 @@ -218,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;
}
};