Skip to content
Open
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
123 changes: 5 additions & 118 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,138 +2,25 @@ import React from "react";
// PatternFly
import "@patternfly/react-core/dist/styles/base.css";
// Layouts
import { AppLayout } from "./AppLayout";
import DataSpinner from "./components/layouts/DataSpinner";
// Navigation
import { AppRoutes } from "./navigation/AppRoutes";
// RPC client
import { Command, useBatchCommandQuery } from "./services/rpc";
// Redux
import { useAppDispatch, useAppSelector } from "src/store/hooks";
import {
updateIpaServerConfiguration,
updateLoggedUserInfo,
updateEnvironment,
updateDnsIsEnabled,
updateTrustConfiguration,
updateDomainLevel,
updateCaIsEnabled,
updateVaultConfiguration,
} from "src/store/Global/global-slice";
import { setIsLogin, setIsLogout } from "./store/Global/auth-slice";
import { useUserMetadataQuery } from "./services/rpcAuth";
// Alerts
import ManagedAlerts from "./components/ManagedAlerts";

const App: React.FunctionComponent = () => {
const dispatch = useAppDispatch();
const { isFetching } = useUserMetadataQuery();

// Default: no user logged in & no loaded information about it
const [loggedInUser, setLoggedInUser] = React.useState<string | null>(null);
const [hasUser, setHasUser] = React.useState<boolean>(false);
const [isDataLoaded, setIsDataLoaded] = React.useState<boolean>(false);

const userLoggedIn = useAppSelector((state) => state.auth.isUserLoggedIn);

// [API Call] Get initial data
const payloadDataBatch: Command[] = [];
const methods = [
"config_show",
"whoami",
"env",
"dns_is_enabled",
"trustconfig_show",
"domainlevel_get",
"ca_is_enabled",
"vaultconfig_show",
];

methods.map((method) => {
const payloadItem = {
method: method,
params: [[], {}],
};
payloadDataBatch.push(payloadItem);
});

const {
data: initialBatchResponse,
isLoading: isInitialBatchLoading,
// TODO: Manage error handling correctly
} = useBatchCommandQuery(payloadDataBatch);

// Store data in global slice (Redux)
React.useEffect(() => {
if (!isInitialBatchLoading && initialBatchResponse === undefined) {
// Assume that the user is not loaded
setLoggedInUser(null);
setIsDataLoaded(true);
setHasUser(false);
dispatch(setIsLogout());
}

if (!isInitialBatchLoading && initialBatchResponse !== undefined) {
setIsDataLoaded(true);
// 0: IPA server configuration ("config_show")
const configShowResponse = initialBatchResponse.result.results[0].result;
dispatch(updateIpaServerConfiguration(configShowResponse));
// 1: Logged user information ("whoami")
const whoamiResponse = initialBatchResponse.result.results[1];
const user = whoamiResponse.arguments.toString();
dispatch(updateLoggedUserInfo(user));
// 2: Environment ("env")
const envResponse = initialBatchResponse.result.results[2].result;
dispatch(updateEnvironment(envResponse));
// 3: DNS is enabled ("dns_is_enabled")
const dnsEnabledResponse: boolean =
initialBatchResponse.result.results[3].result;
dispatch(updateDnsIsEnabled(dnsEnabledResponse));
// 4: Trust configuration ("trustconfig_show")
const trustConfigResponse = initialBatchResponse.result.results[4].result;
dispatch(updateTrustConfiguration(trustConfigResponse));
// 5: Domain level ("domainlevel_get")
const domainLevelResponse = initialBatchResponse.result.results[5].result;
dispatch(updateDomainLevel(domainLevelResponse));
// 6: CA is enabled ("ca_is_enabled")
const caEnabledResponse = initialBatchResponse.result.results[6].result;
dispatch(updateCaIsEnabled(caEnabledResponse));
// 7: Vault configuration ("vaultconfig_show")
const vaultConfig = initialBatchResponse.result.results[7].result;
dispatch(updateVaultConfiguration(vaultConfig));

// Set the login status if user found in the whoami response
if (user) {
setLoggedInUser(user);
setHasUser(true);
// [Redux] Update the login status
const loginPayload = {
loggedInUser: loggedInUser as string,
error: null,
};
dispatch(setIsLogin(loginPayload));
} else {
setLoggedInUser(null);
setHasUser(false);
dispatch(setIsLogout());
}
}
}, [isInitialBatchLoading]);

if (isInitialBatchLoading && !initialBatchResponse) {
if (isFetching) {
return <DataSpinner />;
}

return (
<>
<ManagedAlerts />
{hasUser && userLoggedIn && (
<AppLayout loggedInUser={loggedInUser}>
<AppRoutes isInitialDataLoaded={isDataLoaded} />
</AppLayout>
)}
{!hasUser && !userLoggedIn && (
<>
<AppRoutes isInitialDataLoaded={isDataLoaded} />
</>
)}
<AppRoutes />
</>
);
};
Expand Down
57 changes: 25 additions & 32 deletions src/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,51 +36,42 @@ import ContextualHelpPanel from "./components/ContextualHelpPanel/ContextualHelp
import headerLogo from "/assets/images/header-logo.png";
import avatarImg from "/assets/images/avatarImg.svg";
// Redux
import { useAppDispatch } from "./store/hooks";
import { setIsLogout } from "./store/Global/auth-slice";
import { useAppDispatch, useAppSelector } from "./store/hooks";
// RPC
import { useLogoutMutation } from "./services/rpcAuth";
import { useGetUserDetailsByUidMutation } from "./services/rpcUsers";
import { useGetUserByUidQuery } from "./services/rpcUsers";
import { Outlet } from "react-router";
import { logoutUser } from "./store/Global/global-slice";

interface PropsToAppLayout {
loggedInUser: string | null;
children: React.ReactNode;
}

const AppLayout = (props: PropsToAppLayout) => {
const AppLayout = () => {
const dispatch = useAppDispatch();
const loggedInUser = useAppSelector((state) => state.global.loggedInUser);

// RPC
const [logout] = useLogoutMutation();
const [getUserDetails] = useGetUserDetailsByUidMutation();
const { data: userDetails, isFetching } = useGetUserByUidQuery(
loggedInUser!,
{
skip: !loggedInUser,
}
);

// Retrieve and assign user full name
const [fullName, setFullName] = React.useState<string>("");

React.useEffect(() => {
if (props.loggedInUser) {
getUserDetails(props.loggedInUser).then((response) => {
if ("data" in response) {
const first = response.data?.result.result.givenname;
const last = response.data?.result.result.sn;
// Some users (e.g., admin) don't have first name
if (!first) {
setFullName(last as string);
} else {
setFullName(first + " " + last);
}
}
});
const fullName = React.useMemo(() => {
if (!userDetails) return "";
if (userDetails?.givenname !== "") {
return userDetails?.givenname + " " + userDetails?.sn;
}
}, [props.loggedInUser]);

return userDetails?.sn;
}, [userDetails]);

// On logout handler
const onLogout = () => {
logout().then((response) => {
if ("data" in response && !response.data?.error) {
dispatch(setIsLogout());
// Forcing full page to reload and redirect to login page
window.location.reload();
sessionStorage.setItem("isKerberosDisabled", "true");
dispatch(logoutUser());
}
});
};
Expand Down Expand Up @@ -147,7 +138,7 @@ const AppLayout = (props: PropsToAppLayout) => {
className="pf-v6-u-mr-md"
icon={<Avatar src={avatarImg} alt="avatar" size="sm" />}
>
{fullName}
{isFetching ? "" : fullName}
</MenuToggle>
)}
isOpen={isDropdownOpen}
Expand Down Expand Up @@ -230,7 +221,9 @@ const AppLayout = (props: PropsToAppLayout) => {
className="--pf-t--global--text--color--regular"
isContentFilled
>
<ContextualHelpPanel>{props.children}</ContextualHelpPanel>
<ContextualHelpPanel>
<Outlet />
</ContextualHelpPanel>
</Page>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { afterEach, describe, expect, it, Mock, vi } from "vitest";
// Component
import DualListLayout, { DualListProps } from "./DualListLayout";
import { renderWithRouter } from "src/utils/testUtils";
import { renderWithRouter } from "src/utils/testRouterUtils";

interface MockReturn {
data: { list: string[] } | { error: { message: string } };
Expand All @@ -28,10 +28,6 @@ const retrieveIDs: Mock<() => Promise<MockReturn>> = vi.fn(async () => {
});

vi.mock("src/services/rpc", () => ({
api: {
reducer: () => ({}),
middleware: () => (next) => next,
},
useGetIDListMutation: () => [retrieveIDs],
}));

Expand Down
2 changes: 1 addition & 1 deletion src/components/layouts/PaginationLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cleanup, fireEvent, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
// Component
import PaginationLayout from "./PaginationLayout";
import { renderWithRouter } from "src/utils/testUtils";
import { renderWithRouter } from "src/utils/testRouterUtils";

const list = Array.from({ length: 50 }, (_, i) => `item-${i + 1}`);

Expand Down
4 changes: 1 addition & 3 deletions src/components/modals/UserModals/ResetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ const ResetPassword = (props: PropsToResetPassword) => {
const dispatch = useAppDispatch();

// Get current logged-in user info
const loggedInUser = useAppSelector(
(state) => state.global.loggedUserInfo.arguments
);
const loggedInUser = useAppSelector((state) => state.global.loggedInUser);

// RPC hooks
const [resetPassword] = useChangePasswordMutation();
Expand Down
Loading
Loading