Skip to content

Commit

Permalink
Merge pull request #7167 from JayaShakthi97/feat-remote-userstore
Browse files Browse the repository at this point in the history
[feat] introduce new remote user store impl - add file verification steps
  • Loading branch information
JayaShakthi97 authored Dec 3, 2024
2 parents a95b0f4 + 52725ae commit e2d0449
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-birds-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wso2is/admin.remote-userstores.v1": minor
---

[feat] introduce new remote user store impl - add file verification steps
70 changes: 70 additions & 0 deletions features/admin.remote-userstores.v1/api/use-get-sha-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import useRequest, {
RequestConfigInterface,
RequestErrorInterface,
RequestResultInterface
} from "@wso2is/admin.core.v1/hooks/use-request";
import { HttpMethods } from "@wso2is/core/models";
import isEmpty from "lodash-es/isEmpty";

/**
* Hook to get the checksum file content.
*
* @param filePath - File path to be fetched.
* @param shouldFetch - If true, will fetch the data.
*
* @returns content of the file.
*/
const useGetCheckSum = <Data = string, Error = RequestErrorInterface>(
filePath: string,
shouldFetch: boolean = true
): RequestResultInterface<Data, Error> => {

const requestConfig: RequestConfigInterface = {
headers: {
"Accept": "text/plain",
"Content-Type": "text/plain"
},
method: HttpMethods.GET,
responseType: "text",
url: filePath
};

const {
data,
error,
isLoading,
isValidating,
mutate
} = useRequest<Data, Error>(
(shouldFetch && !isEmpty(filePath)) ? requestConfig : null,
{ attachToken: false }
);

return {
data,
error,
isLoading,
isValidating,
mutate
};
};

export default useGetCheckSum;
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,46 @@
*/

import Button from "@oxygen-ui/react/Button";
import Code from "@oxygen-ui/react/Code";
import List from "@oxygen-ui/react/List";
import ListItem from "@oxygen-ui/react/ListItem";
import Skeleton from "@oxygen-ui/react/Skeleton";
import Stack from "@oxygen-ui/react/Stack";
import Typography from "@oxygen-ui/react/Typography";
import { DownloadIcon } from "@oxygen-ui/react-icons";
import { IdentifiableComponentInterface } from "@wso2is/core/models";
import React, { FunctionComponent, ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { AlertLevels, IdentifiableComponentInterface } from "@wso2is/core/models";
import { addAlert } from "@wso2is/core/store";
import { CodeEditor, Heading } from "@wso2is/react-components";
import isEmpty from "lodash-es/isEmpty";
import React, { FunctionComponent, ReactElement, useEffect, useMemo, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { Dispatch } from "redux";
import useGetCheckSum from "../../../../api/use-get-sha-file";

/**
* Interface for download URLs prop of the DownloadAgentStep component.
*/
export interface AgentDownloadInfoInterface {
/**
* The download URL of the user store agent.
*/
file: string;
/**
* The checksum file URL of the user store agent file.
*/
checkSum: string;
}

/**
* Interface for agent download options shown it the UI.
*/
interface AgentDownloadOptionInterface extends AgentDownloadInfoInterface {
/**
* The operating system of the user store agent.
*/
os: OperatingSystem;
}

/**
* Download agent step component props.
Expand All @@ -32,11 +66,21 @@ interface DownloadAgentStepPropsInterface extends IdentifiableComponentInterface
* Download URLs of the user store agent for different operating systems.
*/
downloadURLs: {
mac: string;
linux: string;
linuxArm: string;
windows: string;
};
linux: AgentDownloadInfoInterface;
linuxArm: AgentDownloadInfoInterface;
mac: AgentDownloadInfoInterface;
windows: AgentDownloadInfoInterface;
}
}

/**
* User store agent available operating systems.
*/
enum OperatingSystem {
Linux = "Linux",
LinuxArm = "Linux (ARM)",
Mac = "Mac OS",
Windows = "Windows"
}

/**
Expand All @@ -50,52 +94,167 @@ const DownloadAgentStep: FunctionComponent<DownloadAgentStepPropsInterface> = ({
["data-componentid"]: componentId = "download-agent-step"
}: DownloadAgentStepPropsInterface): ReactElement => {
const { t } = useTranslation();
const dispatch: Dispatch = useDispatch();

const [ downloadedOS, setDownloadedOS ] = useState<OperatingSystem>();

const availableOptions: { os: string; link: string }[] = [
const availableOptions: AgentDownloadOptionInterface[] = [
{
link: downloadURLs?.linux ?? "",
os: "Linux"
checkSum: downloadURLs?.linux?.checkSum ?? "",
file: downloadURLs?.linux?.file ?? "",
os: OperatingSystem.Linux
},
{
link: downloadURLs?.linuxArm ?? "",
os: "Linux (ARM)"
checkSum: downloadURLs?.linuxArm?.checkSum ?? "",
file: downloadURLs?.linuxArm?.file ?? "",
os: OperatingSystem.LinuxArm
},
{
link: downloadURLs?.mac ?? "",
os: "Mac OS"
checkSum: downloadURLs?.mac?.checkSum ?? "",
file: downloadURLs?.mac?.file ?? "",
os: OperatingSystem.Mac
},
{
link: downloadURLs?.windows ?? "",
os: "Windows"
checkSum: downloadURLs?.windows?.checkSum ?? "",
file: downloadURLs?.windows?.file ?? "",
os: OperatingSystem.Windows
}
];

const onDownloadClicked = (downloadURL: string) => {
/**
* Resolves the selected check sum path based on the downloaded agent's OS.
*/
const selectedCheckSumPath: string = useMemo(() => {
return availableOptions.find(
(option: (AgentDownloadInfoInterface & { os: OperatingSystem })) => option.os === downloadedOS)?.checkSum;
}, [ downloadedOS ]);

const {
data: fetchedCheckSumContent,
error: checkSumFetchRequestError,
isLoading: isCheckSumFetchRequestLoading,
isValidating: isCheckSumFetchRequestValidating
} = useGetCheckSum(selectedCheckSumPath, !!downloadedOS);

const checkSum: string = fetchedCheckSumContent?.split(" ")[0] ?? "";

/**
* Handles the check sum fetch error.
*/
useEffect(() => {
if (checkSumFetchRequestError) {
dispatch(addAlert({
description: t("remoteUserStores:notifications.checkSumError.description"),
level: AlertLevels.SUCCESS,
message: t("remoteUserStores:notifications.checkSumError.message")
}));
}
}, [ checkSumFetchRequestError ]);

const onDownloadClicked = (os: OperatingSystem, downloadURL: string) => {
window.open(downloadURL, "_blank", "noopener, noreferrer");
setDownloadedOS(os);
};

const renderLoadingPlaceholder = () => {
return (
<div data-componentid={ `${componentId}-validation-loading-placeholder` }>
<Skeleton width="50%" />
<Skeleton />
<Skeleton />
</div>
);
};

/**
* Resolves the verification command based on the downloaded agent's OS.
* @returns The verification command.
*/
const getVerificationCommand = () => {
switch(downloadedOS) {
case OperatingSystem.Linux:
case OperatingSystem.LinuxArm:
return "sha256sum <FILE_PATH>";
case OperatingSystem.Mac:
return "shasum -a 256 <FILE_PATH>";
case OperatingSystem.Windows:
return "certutil -hashFile <FILE_PATH> SHA256";
default:
return "";
}
};

return (
<>
<div className="download-agent-step">
<Typography component="p" marginBottom={ 2 }>
{ t("remoteUserStores:pages.edit.guide.steps.download.remote.description") }
</Typography>
<Stack direction="row" spacing={ 1 }>
{ availableOptions.map((option: { os: string; link: string }) => {
{ availableOptions.map((option: AgentDownloadOptionInterface) => {
return (
<Button
key={ option.os }
data-componentid={ `${componentId}-${option.os}-button` }
size="small"
variant="outlined"
startIcon={ <DownloadIcon /> }
onClick={ () => onDownloadClicked(option.link) }
onClick={ () => onDownloadClicked(option.os, option.file) }
>
{ option.os }
</Button>
);
}) }
</Stack>
</>
{ (isCheckSumFetchRequestLoading || isCheckSumFetchRequestValidating) && renderLoadingPlaceholder() }
{
!(isCheckSumFetchRequestLoading || isCheckSumFetchRequestValidating)
&& !isEmpty(checkSum)
&& (
<>
<Heading as="h4">
{ t("remoteUserStores:pages.edit.guide.steps.download.remote.verification.heading") }
</Heading>
<Typography component="p" marginBottom={ 1 }>
{ t("remoteUserStores:pages.edit.guide.steps"
+ ".download.remote.verification.description") }
</Typography>
<List className="verification-steps-list">
<ListItem>
<Typography component="p" marginBottom={ 1 }>
<Trans
i18nKey={ "remoteUserStores:pages.edit.guide.steps"
+ ".download.remote.verification.step1" }
>
Execute the following command in the command line. Replace the <Code>
FILE_PATH</Code> with the path of the downloaded agent zip file.
</Trans>
</Typography>
<CodeEditor
oneLiner
readOnly
withClipboardCopy
language="shell"
sourceCode={ getVerificationCommand() }
/>
</ListItem>
<ListItem>
<Typography component="p" marginBottom={ 1 }>
{ t("remoteUserStores:pages.edit.guide.steps"
+ ".download.remote.verification.step2") }
</Typography>
<CodeEditor
oneLiner
readOnly
withClipboardCopy
language="shell"
sourceCode={ checkSum }
/>
</ListItem>
</List>
</>
)
}
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,15 @@
margin-top: 4px;
}
}

.download-agent-step {
.verification-steps-list {
list-style: decimal;
margin-left: 20px;

li {
display: list-item;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import ConfigureStep from "./setup-guide/configure-step";
import GenerateTokenStep from "./setup-guide/generate-token-step";
import OnPremDownloadAgentStep from "./setup-guide/on-prem/download-step";
import OnPremRunAgentStep from "./setup-guide/on-prem/run-step";
import RemoteDownloadAgentStep from "./setup-guide/remote/download-step";
import RemoteDownloadAgentStep, { AgentDownloadInfoInterface } from "./setup-guide/remote/download-step";
import RemoteRunAgentStep from "./setup-guide/remote/run-step";
import "./userstore-setup-guide.scss";

Expand Down Expand Up @@ -80,10 +80,10 @@ export const SetupGuideTab: FunctionComponent<SetupGuideTabPropsInterface> = (
const agentDownloadURLs: {
onPrem: string;
remote: {
linux: string;
linuxArm: string;
mac: string;
windows: string;
linux: AgentDownloadInfoInterface;
linuxArm: AgentDownloadInfoInterface;
mac: AgentDownloadInfoInterface;
windows: AgentDownloadInfoInterface;
};
} = useSelector((state: AppState) => state.config?.deployment?.extensions?.userStoreAgentUrls);

Expand Down Expand Up @@ -142,7 +142,7 @@ export const SetupGuideTab: FunctionComponent<SetupGuideTabPropsInterface> = (
}

return (
<EmphasizedSegment padded="very">
<EmphasizedSegment padded="very" className="userstore-setup-guide">
<Heading as="h3">
{ t("remoteUserStores:pages.edit.guide.heading") }
<Heading subHeading as="h6">
Expand Down
Loading

0 comments on commit e2d0449

Please sign in to comment.