Skip to content

Commit

Permalink
Merge pull request #5075 from rvykydal/webui-bz-report-offline
Browse files Browse the repository at this point in the history
Webui bz report offline
  • Loading branch information
rvykydal authored Aug 29, 2023
2 parents 383431b + c916fe9 commit 0bb1bfd
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 12 deletions.
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>}
</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;
}
};

0 comments on commit 0bb1bfd

Please sign in to comment.