in tools/awps-tunnel/client/src/components/api/Parameters.tsx [28:274]
export function Parameters({ path, parameters, example, setResponse, methodName, consumes }: {
path: string
parameters: Parameter[],
example: Example,
setResponse: React.Dispatch<React.SetStateAction<ApiResponse | undefined>>,
methodName: string,
consumes?: string[]
}): React.JSX.Element {
const { data, dataFetcher } = useDataContext();
const [urlCopied, setUrlCopied] = useState<boolean>(false);
const [tokenCopied, setTokenCopied] = useState<boolean>(false);
const [url, setUrl] = useState<string>("");
const [token, setToken] = useState("");
const [tokenVisible, setTokenVisible] = useState(false);
const [model, setModel] = useState<TryItModel>({ hasParameter: false });
const [invokeDisabled, setInvokeDisabled] = useState(true);
const [invoking, setInvoking] = useState(false);
// special logic for health check api
const needAuth = !path.endsWith("/api/health");
const [contentType, setContentType] = React.useState<string>("");
const contentRef = useRef<HTMLDivElement | null>(null);
const maskRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (consumes && consumes.length > 0) {
setContentType(consumes[0]);
} else {
setContentType("");
}
}, [consumes]);
useEffect(() => {
setModel(getTryItModel(parameters, example));
}, [parameters, example]);
useEffect(() => {
let newPath = path;
if (data.hub) {
newPath = newPath.replace('{hub}', data.hub);
}
let query: string = "";
if (model.path) {
Object.entries(model.path).forEach(([k, parameter]) => {
if (parameter.value !== undefined) {
newPath = newPath.replace(`{${parameter.name}}`, parameter.value);
}
});
}
if (model.query) {
Object.entries(model.query).forEach(([k, parameter]) => {
if (parameter.value) {
query += `${parameter.name}=${parameter.value}&`;
}
});
}
setUrl(`${data.endpoint.slice(0, -1)}${newPath}?${query}api-version=${data.apiSpec.info.version}`);
}, [path, data.hub, data.endpoint, model, data.apiSpec]);
useEffect(() => {
async function getToken() {
// if no server connected, typeof token if object
const token: any = await dataFetcher.invoke("getRestApiToken", url);
if (token) {
setToken(token);
} else {
setToken("please connect to server to get the token")
}
}
getToken();
}, [url, dataFetcher]);
useEffect(() => {
if (model.body && model.body.required && !model.body.value) {
console.log(model.body);
setInvokeDisabled(true);
return;
}
if (model.header) {
let i = Object.entries(model.header).find(([k, i]) => i.required && !i.value);
if (i && i.length > 0) {
console.log(i);
setInvokeDisabled(true);
return;
}
}
if (model.path) {
let i = Object.entries(model.path).find(([k, i]) => i.required && !i.value);
if (i && i.length > 0) {
console.log(i);
setInvokeDisabled(true);
return;
}
}
if (model.query) {
let i = Object.entries(model.query).find(([k, i]) => i.required && !i.value);
if (i && i.length > 0) {
console.log(i);
setInvokeDisabled(true);
return;
}
}
setInvokeDisabled(false);
}, [model]);
const updateMaskSize = () => {
if (contentRef.current && maskRef.current) {
const { width, height, top, left } = contentRef.current.getBoundingClientRect();
maskRef.current.style.width = `${width}px`;
maskRef.current.style.height = `${height}px`;
maskRef.current.style.top = `${top}px`;
maskRef.current.style.left = `${left}px`;
}
};
useEffect(() => {
if (invoking) {
updateMaskSize();
}
}, [invoking]);
function copyUrl(): void {
navigator.clipboard.writeText(url || "")
.then(() => {
setUrlCopied(true);
setTimeout(() => {
setUrlCopied(false);
}, 3000)
}).catch(err => console.error("Failed to copy url: ", err));
}
function copyHeaderToken(): void {
navigator.clipboard.writeText(token || "")
.then(() => {
setTokenCopied(true);
setTimeout(() => {
setTokenCopied(false);
}, 3000)
}).catch(err => console.error("Failed to copy token: ", err));
}
function onSetValue(param: TryItParameterModel, value: any, type: "path" | "query" | "body"): void {
if (type === "body") {
setModel(prev => ({
...prev, body: { ...param, value: value }
}));
} else {
setModel(prev => ({
...prev,
[type]: {
...prev[type],
[param.name]: { ...param, value: value },
}
}));
}
}
async function sendRequest(methodName: string, model: TryItModel, needAuth: boolean, contentType?: string): Promise<void> {
setInvoking(true);
let headers: HeadersInit = {};
if (contentType) {
headers["Content-Type"] = contentType;
}
if (needAuth) {
const token: string = await dataFetcher.invoke("getRestApiToken", url);
headers["Authorization"] = `Bearer ${token}`;
}
fetch(url, {
method: methodName,
headers: headers,
body: model.body?.value
}).then(async response => {
let res: ApiResponse = {
ok: response.ok,
status: response.status,
isJson: hasJsonBody(response.headers, methodName)
}
if (response.body !== null) {
// always use text to read
const body = await response.text();
if (body.length > 0) {
res.body = body;
}
}
setResponse(res);
}).finally(() => {
setInvoking(false);
});
}
return <div className="d-flex align-items-stretch">
<div hidden={!invoking} className="loading-mask" id="loadingMask" ref={maskRef}></div>
<Card className="m-2" style={{ flex: 1 }} ref={contentRef}>
<CardHeader header={<b className="fs-6">Try It</b>} />
<CardPreview className="d-flex flex-column align-items-start p-3">
<div className="d-flex justify-content-between w-100">
<Label>HTTP URL</Label>
</div>
<Input className="d-inline-flex" readOnly={true}
contentAfter={<Icon iconName={urlCopied ? "checkmark" : "copy"} style={{ cursor: "pointer" }}
onClick={copyUrl} />}
value={url} />
{(needAuth || consumes) && <><b>Headers</b>
{needAuth && <><div className="d-flex justify-content-between w-100">
<Label>Authorization</Label>
</div>
<Input className="d-inline-flex" readOnly={true}
contentAfter={<Icon iconName={tokenCopied ? "checkmark" : "copy"} style={{ cursor: "pointer" }}
onClick={copyHeaderToken} />}
value={token} type={tokenVisible ? "text" : "password"}
onClick={() => setTokenVisible(!tokenVisible)} /></>}
{consumes && consumes.length > 0 && <div className="form-group">
<div className="d-flex justify-content-between w-100">
<Label>Content-Type</Label>
</div>
<select className="form-select"
style={{ boxShadow: "none", outline: "none" }}
aria-labelledby="ct1"
value={contentType}
onChange={(ev) => {
setContentType(ev.target.value); // handle the selected value
}}>
{consumes.map((option) => (
<option key={option} >
{option}
</option>
))}
</select>
</div>}
</>}
{model.hasParameter && <><b>Parameters</b>
{model.path && Object.entries(model.path).map(([key, parameter], index) => (
<ParameterInput key={parameter.name} parameter={parameter} type="path" onChange={onSetValue}></ParameterInput>
))}
{model.query && Object.entries(model.query).map(([key, parameter], index) => (
<ParameterInput key={parameter.name} parameter={parameter} type="query" onChange={onSetValue}></ParameterInput>
))}
</>}
{model.body && <ParameterInput key={model.body.name} parameter={model.body} type="body" onChange={onSetValue} contentType={contentType}></ParameterInput>}
<div className="m-2 d-flex justify-content-end ">
<Button icon={<Send24Regular />} disabled={invokeDisabled} onClick={() => sendRequest(methodName, model, needAuth, contentType)}>Invoke</Button>
</div>
</CardPreview>
</Card>
</div>
}