forked from cerc-io/snowballtools-base
Check deployment status while creating project with single deployer (#15)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) - Use deployer API to get status of the deployments Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Co-authored-by: IshaVenikar <ishavenikar7@gmail.com> Co-authored-by: Adw8 <adwaitgharpure@gmail.com> Reviewed-on: cerc-io/snowballtools-base#15 Co-authored-by: Nabarun Gogoi <nabarun@deepstacksoft.com> Co-committed-by: Nabarun Gogoi <nabarun@deepstacksoft.com>
This commit is contained in:
parent
be90fc76c1
commit
2a35ec1cd5
@ -20,16 +20,6 @@
|
|||||||
clientId = ""
|
clientId = ""
|
||||||
clientSecret = ""
|
clientSecret = ""
|
||||||
|
|
||||||
[google]
|
|
||||||
clientId = ""
|
|
||||||
clientSecret = ""
|
|
||||||
|
|
||||||
[turnkey]
|
|
||||||
apiBaseUrl = "https://api.turnkey.com"
|
|
||||||
apiPrivateKey = ""
|
|
||||||
apiPublicKey = ""
|
|
||||||
defaultOrganizationId = ""
|
|
||||||
|
|
||||||
[registryConfig]
|
[registryConfig]
|
||||||
fetchDeploymentRecordDelay = 5000
|
fetchDeploymentRecordDelay = 5000
|
||||||
checkAuctionStatusDelay = 5000
|
checkAuctionStatusDelay = 5000
|
||||||
|
@ -1348,7 +1348,7 @@ export class Service {
|
|||||||
const deployers: Deployer[] = [];
|
const deployers: Deployer[] = [];
|
||||||
|
|
||||||
for (const record of deployerRecords) {
|
for (const record of deployerRecords) {
|
||||||
if (record.names.length > 0) {
|
if (record.names && record.names.length > 0) {
|
||||||
const deployerId = record.id;
|
const deployerId = record.id;
|
||||||
const deployerLrn = record.names[0];
|
const deployerLrn = record.names[0];
|
||||||
const deployerApiUrl = record.attributes.apiUrl;
|
const deployerApiUrl = record.attributes.apiUrl;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
services:
|
services:
|
||||||
registry:
|
registry:
|
||||||
rpcEndpoint: http://laconicd.laconic.com:26657
|
rpcEndpoint: https://laconicd-sapo.laconic.com
|
||||||
gqlEndpoint: http://laconicd.laconic.com:9473/api
|
gqlEndpoint: https://laconicd-sapo.laconic.com/api
|
||||||
userKey: 08c0d30ed23706330468e6936316a3bc3e69e451e394f05027ad56119bb485b9
|
userKey:
|
||||||
bondId: 820587f916d9a6a056f1e6a5a250151d9fa0c1e771347a6b8bb3d6f2090fd11b
|
bondId:
|
||||||
chainId: laconic_9000-2
|
chainId: laconic_9000-2
|
||||||
gas:
|
|
||||||
fees:
|
|
||||||
gasPrice: 1alnt
|
gasPrice: 1alnt
|
||||||
|
@ -98,8 +98,7 @@ fi
|
|||||||
|
|
||||||
# Get payment address for deployer
|
# Get payment address for deployer
|
||||||
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
|
paymentAddress=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.paymentAddress')
|
||||||
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment')
|
paymentAmount=$(yarn --silent laconic -c config.yml registry name resolve "$DEPLOYER_LRN" | jq -r '.[0].attributes.minimumPayment' | sed 's/alnt//g')
|
||||||
|
|
||||||
# Pay deployer if paymentAmount is not null
|
# Pay deployer if paymentAmount is not null
|
||||||
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
|
if [[ -n "$paymentAmount" && "$paymentAmount" != "null" ]]; then
|
||||||
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
|
payment=$(yarn --silent laconic -c config.yml registry tokens send --address "$paymentAddress" --type alnt --quantity "$paymentAmount")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 3000",
|
"dev": "vite --port 3000",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import { useStopwatch } from 'react-timer-hook';
|
import { useStopwatch } from 'react-timer-hook';
|
||||||
|
|
||||||
import FormatMillisecond, { FormatMilliSecondProps } from './FormatMilliSecond';
|
import FormatMillisecond, { FormatMilliSecondProps } from './FormatMilliSecond';
|
||||||
@ -12,14 +13,21 @@ const setStopWatchOffset = (time: string) => {
|
|||||||
|
|
||||||
interface StopwatchProps extends Omit<FormatMilliSecondProps, 'time'> {
|
interface StopwatchProps extends Omit<FormatMilliSecondProps, 'time'> {
|
||||||
offsetTimestamp: Date;
|
offsetTimestamp: Date;
|
||||||
|
isPaused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Stopwatch = ({ offsetTimestamp, ...props }: StopwatchProps) => {
|
const Stopwatch = ({ offsetTimestamp, isPaused, ...props }: StopwatchProps) => {
|
||||||
const { totalSeconds } = useStopwatch({
|
const { totalSeconds, pause } = useStopwatch({
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
offsetTimestamp: offsetTimestamp,
|
offsetTimestamp: offsetTimestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPaused) {
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
}, [isPaused]);
|
||||||
|
|
||||||
return <FormatMillisecond time={totalSeconds * 1000} {...props} />;
|
return <FormatMillisecond time={totalSeconds * 1000} {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Deployment } from 'gql-client';
|
||||||
|
|
||||||
import { DeployStep, DeployStatus } from './DeployStep';
|
import { DeployStep, DeployStatus } from './DeployStep';
|
||||||
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
|
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
|
||||||
@ -7,13 +9,37 @@ import { Heading } from '../../shared/Heading';
|
|||||||
import { Button } from '../../shared/Button';
|
import { Button } from '../../shared/Button';
|
||||||
import { ClockOutlineIcon, WarningIcon } from '../../shared/CustomIcon';
|
import { ClockOutlineIcon, WarningIcon } from '../../shared/CustomIcon';
|
||||||
import { CancelDeploymentDialog } from '../../projects/Dialog/CancelDeploymentDialog';
|
import { CancelDeploymentDialog } from '../../projects/Dialog/CancelDeploymentDialog';
|
||||||
|
import { useGQLClient } from 'context/GQLClientContext';
|
||||||
|
|
||||||
|
const FETCH_DEPLOYMENTS_INTERVAL = 5000;
|
||||||
|
|
||||||
|
type RequestState =
|
||||||
|
| 'SUBMITTED'
|
||||||
|
| 'DEPLOYING'
|
||||||
|
| 'DEPLOYED'
|
||||||
|
| 'REMOVED'
|
||||||
|
| 'CANCELLED'
|
||||||
|
| 'ERROR';
|
||||||
|
|
||||||
|
type Record = {
|
||||||
|
id: string;
|
||||||
|
createTime: string;
|
||||||
|
app: string;
|
||||||
|
lastState: RequestState;
|
||||||
|
lastUpdate: string;
|
||||||
|
logAvailable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const TIMEOUT_DURATION = 5000;
|
|
||||||
const Deploy = () => {
|
const Deploy = () => {
|
||||||
|
const client = useGQLClient();
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const projectId = searchParams.get('projectId');
|
const projectId = searchParams.get('projectId');
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [deployment, setDeployment] = useState<Deployment>();
|
||||||
|
const [record, setRecord] = useState<Record>();
|
||||||
|
|
||||||
const handleOpen = () => setOpen(!open);
|
const handleOpen = () => setOpen(!open);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -23,13 +49,67 @@ const Deploy = () => {
|
|||||||
navigate(`/${orgSlug}/projects/create`);
|
navigate(`/${orgSlug}/projects/create`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const isDeploymentFailed = useMemo(() => {
|
||||||
const timerID = setTimeout(() => {
|
if (!record) {
|
||||||
navigate(`/${orgSlug}/projects/create/success/${projectId}`);
|
return false;
|
||||||
}, TIMEOUT_DURATION);
|
}
|
||||||
|
|
||||||
return () => clearInterval(timerID);
|
// Not checking for `REMOVED` status as this status is received for a brief period before receiving `DEPLOYED` status
|
||||||
}, []);
|
if (record.lastState === 'CANCELLED' || record.lastState === 'ERROR') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [record]);
|
||||||
|
|
||||||
|
const fetchDeploymentRecords = useCallback(async () => {
|
||||||
|
if (!deployment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const record: Record = response.data;
|
||||||
|
setRecord(record);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log('Error fetching data from deployer', err);
|
||||||
|
}
|
||||||
|
}, [deployment]);
|
||||||
|
|
||||||
|
const fetchDeployment = useCallback(async () => {
|
||||||
|
if (!projectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { deployments } = await client.getDeployments(projectId);
|
||||||
|
setDeployment(deployments[0]);
|
||||||
|
}, [client, projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDeployment();
|
||||||
|
fetchDeploymentRecords();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchDeploymentRecords();
|
||||||
|
}, FETCH_DEPLOYMENTS_INTERVAL);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [fetchDeployment, fetchDeploymentRecords]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!record) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.lastState === 'DEPLOYED') {
|
||||||
|
navigate(`/${orgSlug}/projects/create/success/${projectId}`);
|
||||||
|
}
|
||||||
|
}, [record]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-7">
|
<div className="space-y-7">
|
||||||
@ -42,6 +122,7 @@ const Deploy = () => {
|
|||||||
<ClockOutlineIcon size={16} className="text-elements-mid-em" />
|
<ClockOutlineIcon size={16} className="text-elements-mid-em" />
|
||||||
<Stopwatch
|
<Stopwatch
|
||||||
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
|
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
|
||||||
|
isPaused={isDeploymentFailed}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,30 +141,36 @@ const Deploy = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{!isDeploymentFailed ? (
|
||||||
<DeployStep
|
<div>
|
||||||
title="Building"
|
<DeployStep
|
||||||
status={DeployStatus.COMPLETE}
|
title={record ? 'Submitted' : 'Submitting'}
|
||||||
step="1"
|
status={record ? DeployStatus.COMPLETE : DeployStatus.PROCESSING}
|
||||||
processTime="72000"
|
step="1"
|
||||||
/>
|
/>
|
||||||
<DeployStep
|
|
||||||
title="Deployment summary"
|
<DeployStep
|
||||||
status={DeployStatus.PROCESSING}
|
title={
|
||||||
step="2"
|
record && record.lastState === 'DEPLOYED'
|
||||||
startTime={Date.now().toString()}
|
? 'Deployed'
|
||||||
/>
|
: 'Deploying'
|
||||||
<DeployStep
|
}
|
||||||
title="Running checks"
|
status={
|
||||||
status={DeployStatus.NOT_STARTED}
|
!record
|
||||||
step="3"
|
? DeployStatus.NOT_STARTED
|
||||||
/>
|
: record.lastState === 'DEPLOYED'
|
||||||
<DeployStep
|
? DeployStatus.COMPLETE
|
||||||
title="Assigning domains"
|
: DeployStatus.PROCESSING
|
||||||
status={DeployStatus.NOT_STARTED}
|
}
|
||||||
step="4"
|
step="2"
|
||||||
/>
|
startTime={Date.now().toString()}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<DeployStep title={record!.lastState} status={DeployStatus.ERROR} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,27 +1,16 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { Collapse } from '@snowballtools/material-tailwind-react-fork';
|
|
||||||
|
|
||||||
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
|
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
|
||||||
import FormatMillisecond from '../../FormatMilliSecond';
|
|
||||||
import processLogs from '../../../assets/process-logs.json';
|
|
||||||
import { cn } from 'utils/classnames';
|
import { cn } from 'utils/classnames';
|
||||||
import {
|
import {
|
||||||
CheckRoundFilledIcon,
|
CheckRoundFilledIcon,
|
||||||
ClockOutlineIcon,
|
ClockOutlineIcon,
|
||||||
CopyIcon,
|
|
||||||
LoaderIcon,
|
LoaderIcon,
|
||||||
MinusCircleIcon,
|
|
||||||
PlusIcon,
|
|
||||||
} from 'components/shared/CustomIcon';
|
} from 'components/shared/CustomIcon';
|
||||||
import { Button } from 'components/shared/Button';
|
|
||||||
import { useToast } from 'components/shared/Toast';
|
|
||||||
import { useIntersectionObserver } from 'usehooks-ts';
|
|
||||||
|
|
||||||
enum DeployStatus {
|
enum DeployStatus {
|
||||||
PROCESSING = 'progress',
|
PROCESSING = 'progress',
|
||||||
COMPLETE = 'complete',
|
COMPLETE = 'complete',
|
||||||
NOT_STARTED = 'notStarted',
|
NOT_STARTED = 'notStarted',
|
||||||
|
ERROR = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeployStepsProps {
|
interface DeployStepsProps {
|
||||||
@ -32,35 +21,11 @@ interface DeployStepsProps {
|
|||||||
processTime?: string;
|
processTime?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeployStep = ({
|
const DeployStep = ({ step, status, title, startTime }: DeployStepsProps) => {
|
||||||
step,
|
|
||||||
status,
|
|
||||||
title,
|
|
||||||
startTime,
|
|
||||||
processTime,
|
|
||||||
}: DeployStepsProps) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const { toast, dismiss } = useToast();
|
|
||||||
const { isIntersecting: hideGradientOverlay, ref } = useIntersectionObserver({
|
|
||||||
threshold: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const disableCollapse = status !== DeployStatus.COMPLETE;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-border-separator">
|
<div className="border-b border-border-separator">
|
||||||
{/* Collapisble trigger */}
|
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn('flex justify-between w-full py-5 gap-2', 'cursor-auto')}
|
||||||
'flex justify-between w-full py-5 gap-2',
|
|
||||||
disableCollapse && 'cursor-auto',
|
|
||||||
)}
|
|
||||||
tabIndex={disableCollapse ? -1 : undefined}
|
|
||||||
onClick={() => {
|
|
||||||
if (!disableCollapse) {
|
|
||||||
setIsOpen((val) => !val);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className={cn('grow flex items-center gap-3')}>
|
<div className={cn('grow flex items-center gap-3')}>
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
@ -73,12 +38,6 @@ const DeployStep = ({
|
|||||||
{status === DeployStatus.PROCESSING && (
|
{status === DeployStatus.PROCESSING && (
|
||||||
<LoaderIcon className="animate-spin text-elements-link" />
|
<LoaderIcon className="animate-spin text-elements-link" />
|
||||||
)}
|
)}
|
||||||
{status === DeployStatus.COMPLETE && (
|
|
||||||
<div className="text-controls-primary">
|
|
||||||
{!isOpen && <PlusIcon size={24} />}
|
|
||||||
{isOpen && <MinusCircleIcon size={24} />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
@ -96,7 +55,10 @@ const DeployStep = ({
|
|||||||
{status === DeployStatus.PROCESSING && (
|
{status === DeployStatus.PROCESSING && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<ClockOutlineIcon size={16} className="text-elements-low-em" />
|
<ClockOutlineIcon size={16} className="text-elements-low-em" />
|
||||||
<Stopwatch offsetTimestamp={setStopWatchOffset(startTime!)} />
|
<Stopwatch
|
||||||
|
offsetTimestamp={setStopWatchOffset(startTime!)}
|
||||||
|
isPaused={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{status === DeployStatus.COMPLETE && (
|
{status === DeployStatus.COMPLETE && (
|
||||||
@ -107,51 +69,9 @@ const DeployStep = ({
|
|||||||
size={15}
|
size={15}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormatMillisecond time={Number(processTime)} />{' '}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Collapsible */}
|
|
||||||
<Collapse open={isOpen}>
|
|
||||||
<div className="relative text-xs text-elements-low-em h-36 overflow-y-auto">
|
|
||||||
{/* Logs */}
|
|
||||||
{processLogs.map((log, key) => {
|
|
||||||
return (
|
|
||||||
<p className="font-mono" key={key}>
|
|
||||||
{log}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* End of logs ref used for hiding gradient overlay */}
|
|
||||||
<div ref={ref} />
|
|
||||||
|
|
||||||
{/* Overflow gradient overlay */}
|
|
||||||
{!hideGradientOverlay && (
|
|
||||||
<div className="h-14 w-full sticky bottom-0 inset-x-0 bg-gradient-to-t from-white to-transparent" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Copy log button */}
|
|
||||||
<div className={cn('sticky bottom-4 left-1/2 flex justify-center')}>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(processLogs.join('\n'));
|
|
||||||
toast({
|
|
||||||
title: 'Logs copied',
|
|
||||||
variant: 'success',
|
|
||||||
id: 'logs',
|
|
||||||
onDismiss: dismiss,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
leftIcon={<CopyIcon size={16} />}
|
|
||||||
>
|
|
||||||
Copy log
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -93,12 +93,20 @@ const DeploymentDetailsCard = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchDeploymentLogs = async () => {
|
const fetchDeploymentLogs = async () => {
|
||||||
let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`;
|
const statusUrl = `${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`;
|
||||||
const res = await fetch(url, { cache: 'no-store' });
|
const statusRes = await fetch(statusUrl, { cache: 'no-store' }).then(
|
||||||
handleOpenDialog();
|
(res) => res.json(),
|
||||||
if (res.ok) {
|
);
|
||||||
const logs = await res.text();
|
if (!statusRes.logAvailable) {
|
||||||
setDeploymentLogs(logs);
|
setDeploymentLogs(statusRes.lastState);
|
||||||
|
handleOpenDialog();
|
||||||
|
} else {
|
||||||
|
const logsUrl = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`;
|
||||||
|
const logsRes = await fetch(logsUrl, { cache: 'no-store' }).then((res) =>
|
||||||
|
res.text(),
|
||||||
|
);
|
||||||
|
setDeploymentLogs(logsRes);
|
||||||
|
handleOpenDialog();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user