diff --git a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx index 2f20f0aae..6618fd40b 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx @@ -220,7 +220,8 @@ export const DeploymentDepositModal: React.FunctionComponent - Leases - {isActive && Logs} - {isActive && Shell} - {isActive && Events} - Update + + Leases + + {isActive && ( + + Logs + + )} + {isActive && ( + + Shell + + )} + {isActive && ( + + Events + + )} + + Update + {activeTab === "EDIT" && deployment && leases && ( @@ -200,6 +216,7 @@ export function DeploymentDetail({ dseq }: React.PropsWithChildren<{ dseq: strin leases.map((lease, i) => ( = ({ address
- @@ -119,7 +119,11 @@ export const DeploymentDetailTopBar: React.FunctionComponent = ({ address Redeploy )} - onCloseDeployment()} icon={}> + onCloseDeployment()} + icon={} + data-testid="deployment-detail-close-button" + > Close diff --git a/apps/deploy-web/src/components/deployments/LeaseRow.tsx b/apps/deploy-web/src/components/deployments/LeaseRow.tsx index 2b4b38a5c..a95b38d51 100644 --- a/apps/deploy-web/src/components/deployments/LeaseRow.tsx +++ b/apps/deploy-web/src/components/deployments/LeaseRow.tsx @@ -35,6 +35,7 @@ import { UrlService } from "@src/utils/urlUtils"; import { ManifestErrorSnackbar } from "../shared/ManifestErrorSnackbar"; type Props = { + index: number; lease: LeaseDto; setActiveTab: (value: SetStateAction) => void; deploymentManifest: string; @@ -47,415 +48,417 @@ export type AcceptRefType = { getLeaseStatus: () => void; }; -export const LeaseRow = React.forwardRef(({ lease, setActiveTab, deploymentManifest, dseq, providers, loadDeploymentDetail }, ref) => { - const provider = providers?.find(p => p.owner === lease?.provider); - const { localCert } = useCertificate(); - const isLeaseActive = lease.state === "active"; - const [isServicesAvailable, setIsServicesAvailable] = useState(false); - const { favoriteProviders, updateFavoriteProviders } = useLocalNotes(); - const isFavorite = favoriteProviders.some(x => lease?.provider === x); - const { - data: leaseStatus, - error, - refetch: getLeaseStatus, - isLoading: isLoadingLeaseStatus - } = useLeaseStatus(provider?.hostUri || "", lease, { - enabled: isLeaseActive && !isServicesAvailable && !!provider?.hostUri && !!localCert, - refetchInterval: 10_000, - onSuccess: leaseStatus => { - if (leaseStatus) { - checkIfServicesAreAvailable(leaseStatus); +export const LeaseRow = React.forwardRef( + ({ index, lease, setActiveTab, deploymentManifest, dseq, providers, loadDeploymentDetail }, ref) => { + const provider = providers?.find(p => p.owner === lease?.provider); + const { localCert } = useCertificate(); + const isLeaseActive = lease.state === "active"; + const [isServicesAvailable, setIsServicesAvailable] = useState(false); + const { favoriteProviders, updateFavoriteProviders } = useLocalNotes(); + const isFavorite = favoriteProviders.some(x => lease?.provider === x); + const { + data: leaseStatus, + error, + refetch: getLeaseStatus, + isLoading: isLoadingLeaseStatus + } = useLeaseStatus(provider?.hostUri || "", lease, { + enabled: isLeaseActive && !isServicesAvailable && !!provider?.hostUri && !!localCert, + refetchInterval: 10_000, + onSuccess: leaseStatus => { + if (leaseStatus) { + checkIfServicesAreAvailable(leaseStatus); + } } + }); + const { isLoading: isLoadingProviderStatus, refetch: getProviderStatus } = useProviderStatus(provider?.hostUri || "", { + enabled: false, + retry: false + }); + const isLeaseNotFound = error && (error as string).includes && (error as string).includes("lease not found") && isLeaseActive; + const servicesNames = useMemo(() => (leaseStatus ? Object.keys(leaseStatus.services) : []), [leaseStatus]); + const [isSendingManifest, setIsSendingManifest] = useState(false); + const { data: bid } = useBidInfo(lease.owner, lease.dseq, lease.gseq, lease.oseq, lease.provider); + const { enqueueSnackbar } = useSnackbar(); + + React.useImperativeHandle(ref, () => ({ + getLeaseStatus: loadLeaseStatus + })); + + const loadLeaseStatus = useCallback(() => { + if (isLeaseActive && provider && localCert) { + getLeaseStatus(); + getProviderStatus(); + } + }, [isLeaseActive, provider, localCert, getLeaseStatus, getProviderStatus]); + + const parsedManifest = useMemo(() => yaml.load(deploymentManifest), [deploymentManifest]); + + const checkIfServicesAreAvailable = leaseStatus => { + const servicesNames = leaseStatus ? Object.keys(leaseStatus.services) : []; + const isServicesAvailable = + servicesNames.length > 0 + ? servicesNames + .map(n => leaseStatus.services[n]) + .every(service => { + return service.available > 0; + }) + : false; + setIsServicesAvailable(isServicesAvailable); + }; + + useEffect(() => { + loadLeaseStatus(); + }, [lease, provider, localCert, loadLeaseStatus]); + + function handleEditManifestClick(ev) { + ev.preventDefault(); + setActiveTab("EDIT"); } - }); - const { isLoading: isLoadingProviderStatus, refetch: getProviderStatus } = useProviderStatus(provider?.hostUri || "", { - enabled: false, - retry: false - }); - const isLeaseNotFound = error && (error as string).includes && (error as string).includes("lease not found") && isLeaseActive; - const servicesNames = useMemo(() => (leaseStatus ? Object.keys(leaseStatus.services) : []), [leaseStatus]); - const [isSendingManifest, setIsSendingManifest] = useState(false); - const { data: bid } = useBidInfo(lease.owner, lease.dseq, lease.gseq, lease.oseq, lease.provider); - const { enqueueSnackbar } = useSnackbar(); - - React.useImperativeHandle(ref, () => ({ - getLeaseStatus: loadLeaseStatus - })); - - const loadLeaseStatus = useCallback(() => { - if (isLeaseActive && provider && localCert) { - getLeaseStatus(); - getProviderStatus(); - } - }, [isLeaseActive, provider, localCert, getLeaseStatus, getProviderStatus]); - - const parsedManifest = useMemo(() => yaml.load(deploymentManifest), [deploymentManifest]); - - const checkIfServicesAreAvailable = leaseStatus => { - const servicesNames = leaseStatus ? Object.keys(leaseStatus.services) : []; - const isServicesAvailable = - servicesNames.length > 0 - ? servicesNames - .map(n => leaseStatus.services[n]) - .every(service => { - return service.available > 0; - }) - : false; - setIsServicesAvailable(isServicesAvailable); - }; - - useEffect(() => { - loadLeaseStatus(); - }, [lease, provider, localCert, loadLeaseStatus]); - - function handleEditManifestClick(ev) { - ev.preventDefault(); - setActiveTab("EDIT"); - } - async function sendManifest() { - setIsSendingManifest(true); - try { - const manifest = deploymentData.getManifest(parsedManifest, true); + async function sendManifest() { + setIsSendingManifest(true); + try { + const manifest = deploymentData.getManifest(parsedManifest, true); - await sendManifestToProvider(provider as ApiProviderList, manifest, dseq, localCert as LocalCert); + await sendManifestToProvider(provider as ApiProviderList, manifest, dseq, localCert as LocalCert); - enqueueSnackbar(, { variant: "success", autoHideDuration: 10_000 }); + enqueueSnackbar(, { variant: "success", autoHideDuration: 10_000 }); - loadDeploymentDetail(); - } catch (err) { - enqueueSnackbar(, { variant: "error", autoHideDuration: null }); + loadDeploymentDetail(); + } catch (err) { + enqueueSnackbar(, { variant: "error", autoHideDuration: null }); + } + setIsSendingManifest(false); } - setIsSendingManifest(false); - } - const onStarClick = event => { - event.preventDefault(); - event.stopPropagation(); + const onStarClick = event => { + event.preventDefault(); + event.stopPropagation(); - const newFavorites = isFavorite ? favoriteProviders.filter(x => x !== lease.provider) : favoriteProviders.concat([lease.provider]); + const newFavorites = isFavorite ? favoriteProviders.filter(x => x !== lease.provider) : favoriteProviders.concat([lease.provider]); - updateFavoriteProviders(newFavorites); - }; + updateFavoriteProviders(newFavorites); + }; - const gpuModels = bid && bid.bid.resources_offer.flatMap(x => getGpusFromAttributes(x.resources.gpu.attributes)); + const gpuModels = bid && bid.bid.resources_offer.flatMap(x => getGpusFromAttributes(x.resources.gpu.attributes)); - const sshInstructions = useMemo(() => { - return servicesNames.reduce((acc, serviceName) => { - if (!sshVmImages.has(get(parsedManifest, ["services", serviceName, "image"]))) { - return acc; - } + const sshInstructions = useMemo(() => { + return servicesNames.reduce((acc, serviceName) => { + if (!sshVmImages.has(get(parsedManifest, ["services", serviceName, "image"]))) { + return acc; + } - const exposes = leaseStatus.forwarded_ports[serviceName]; + const exposes = leaseStatus.forwarded_ports[serviceName]; - return exposes.reduce((exposesAcc, expose) => { - if (expose.port !== 22) { - return exposesAcc; - } + return exposes.reduce((exposesAcc, expose) => { + if (expose.port !== 22) { + return exposesAcc; + } - if (exposesAcc) { - exposesAcc += "\n"; - } + if (exposesAcc) { + exposesAcc += "\n"; + } - return exposesAcc.concat(`ssh root@${expose.host} -p ${expose.externalPort} -i ~/.ssh/id_rsa`); - }, acc); - }, ""); - }, [parsedManifest, servicesNames, leaseStatus]); + return exposesAcc.concat(`ssh root@${expose.host} -p ${expose.externalPort} -i ~/.ssh/id_rsa`); + }, acc); + }, ""); + }, [parsedManifest, servicesNames, leaseStatus]); - return ( - - -
-
- {lease.state} - + return ( + + +
+
+ {lease.state} + - GSEQ: - {lease.gseq} + GSEQ: + {lease.gseq} - OSEQ: - {lease.oseq} -
+ OSEQ: + {lease.oseq} +
- {isLeaseActive && ( -
- setActiveTab("LOGS")}> - View logs - + {isLeaseActive && ( +
+ setActiveTab("LOGS")}> + View logs + +
+ )} +
+
+ +
+
+
- )} -
- - -
-
- + + +
+ } /> -
- - - -
- } - /> - - - {isLeaseActive && isLoadingProviderStatus && } - {provider && ( -
- - {provider.name?.length > 25 ? getSplitText(provider.name, 10, 10) : provider.name} - + + {isLeaseActive && isLoadingProviderStatus && } + {provider && (
- - - {provider?.isAudited && ( -
- -
- )} -
-
- )} - - } - /> -
- - {isLeaseNotFound && ( - - The lease was not found on this provider. This can happen if no manifest was sent to the provider. To send one you can update your deployment in the{" "} - VIEW / EDIT MANIFEST tab. - {deploymentManifest && ( - <> -
- OR -
- - - )} -
- )} - - {!leaseStatus && isLoadingLeaseStatus && } - - {isLeaseActive && - leaseStatus && - leaseStatus.services && - servicesNames - .map(n => leaseStatus.services[n]) - .map((service, i) => ( -
1 && i !== servicesNames.length - 1 - })} - key={`${service.name}_${i}`} - > -
- - {isLoadingLeaseStatus || !isServicesAvailable ? ( -
- -
- ) : ( -
- - Workloads can take some time to spin up. If you see an error when browsing the uri, it is recommended to refresh and wait a bit. - Check the{" "} - setActiveTab("LOGS")} className="text-white"> - logs - {" "} - for more information. - - } - > - - + + {provider.name?.length > 25 ? getSplitText(provider.name, 10, 10) : provider.name} + + +
+ + + {provider?.isAudited && ( +
+ +
+ )} +
)} + + } + /> +
- {isServicesAvailable && ( -
- -
- )} -
+ {isLeaseNotFound && ( + + The lease was not found on this provider. This can happen if no manifest was sent to the provider. To send one you can update your deployment in + the VIEW / EDIT MANIFEST tab. + {deploymentManifest && ( + <> +
+ OR +
+ + + )} +
+ )} + {!leaseStatus && isLoadingLeaseStatus && } + + {isLeaseActive && + leaseStatus && + leaseStatus.services && + servicesNames + .map(n => leaseStatus.services[n]) + .map((service, i) => (
0 || (leaseStatus.forwarded_ports && leaseStatus.forwarded_ports[service.name]?.length > 0) + className={cn("mt-2", { + ["border-b pb-2"]: servicesNames.length > 1 && i !== servicesNames.length - 1 })} + key={`${service.name}_${i}`} > -
- Available:  - 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> - {service.available} - -
-
- Ready Replicas:  - 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> - {service.ready_replicas} - +
+ + {isLoadingLeaseStatus || !isServicesAvailable ? ( +
+ +
+ ) : ( +
+ + Workloads can take some time to spin up. If you see an error when browsing the uri, it is recommended to refresh and wait a bit. + Check the{" "} + setActiveTab("LOGS")} className="text-white"> + logs + {" "} + for more information. + + } + > + + +
+ )} + + {isServicesAvailable && ( +
+ +
+ )}
-
- Total:  - 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> - {service.total} - + +
0 || (leaseStatus.forwarded_ports && leaseStatus.forwarded_ports[service.name]?.length > 0) + })} + > +
+ Available:  + 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> + {service.available} + +
+
+ Ready Replicas:  + 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> + {service.ready_replicas} + +
+
+ Total:  + 0 ? "success" : "destructive"} className="h-3 px-1 text-xs leading-3"> + {service.total} + +
-
- {leaseStatus.forwarded_ports && leaseStatus.forwarded_ports[service.name]?.length > 0 && ( -
0 })}> - - {leaseStatus.forwarded_ports[service.name].map(p => ( -
- {p.host ? ( - - - {p.port}:{p.externalPort} - + {leaseStatus.forwarded_ports && leaseStatus.forwarded_ports[service.name]?.length > 0 && ( +
0 })}> + + {leaseStatus.forwarded_ports[service.name].map(p => ( +
+ {p.host ? ( + + + {p.port}:{p.externalPort} + + + + ) : ( + {`${p.port}:${p.externalPort}`} + )} +
+ ))} +
+ } + /> +
+ )} + + {service.uris?.length > 0 && ( + <> +
+ +
    + {service.uris.map(uri => { + return ( +
  • + + {uri} - ) : ( - {`${p.port}:${p.externalPort}`} - )} -
- ))} -
- } - /> -
- )} - - {service.uris?.length > 0 && ( - <> -
- -
    - {service.uris.map(uri => { - return ( -
  • - - {uri} - - -    - -
  • - ); - })} -
-
- - )} -
- ))} - - {isLeaseActive && leaseStatus && leaseStatus.ips && ( -
- -
    - {servicesNames - .flatMap(service => leaseStatus.ips[service]) - .filter(Boolean) - .map(ip => ( -
  • - - - {ip.IP}:{ip.ExternalPort} - - - -    - -
    IP: {ip.IP}
    -
    External Port: {ip.ExternalPort}
    -
    Port: {ip.Port}
    -
    Protocol: {ip.Protocol}
    - - } - > - -
    - -
  • - ))} -
-
- )} - - {sshInstructions && ( -
-
SSH Instructions:
-
    -
  • - Open a command terminal on your machine and copy this command into it: - -
  • -
  • - Replace ~/.ssh/id_rsa with the path to the private key (stored on your local machine) corresponding to the public key you provided earlier -
  • -
  • Run the command
  • -
-
- )} - -
- ); -}); +    + + + ); + })} + +
+ + )} + + ))} + + {isLeaseActive && leaseStatus && leaseStatus.ips && ( +
+ +
    + {servicesNames + .flatMap(service => leaseStatus.ips[service]) + .filter(Boolean) + .map(ip => ( +
  • + + + {ip.IP}:{ip.ExternalPort} + + + +    + +
    IP: {ip.IP}
    +
    External Port: {ip.ExternalPort}
    +
    Port: {ip.Port}
    +
    Protocol: {ip.Protocol}
    + + } + > + +
    + +
  • + ))} +
+
+ )} + + {sshInstructions && ( +
+
SSH Instructions:
+
    +
  • + Open a command terminal on your machine and copy this command into it: + +
  • +
  • + Replace ~/.ssh/id_rsa with the path to the private key (stored on your local machine) corresponding to the public key you provided earlier +
  • +
  • Run the command
  • +
+
+ )} + + + ); + } +); diff --git a/apps/deploy-web/src/components/new-deployment/BidGroup.tsx b/apps/deploy-web/src/components/new-deployment/BidGroup.tsx index 69fa67812..9bde1c003 100644 --- a/apps/deploy-web/src/components/new-deployment/BidGroup.tsx +++ b/apps/deploy-web/src/components/new-deployment/BidGroup.tsx @@ -120,11 +120,12 @@ export const BidGroup: React.FunctionComponent = ({ - {fBids.map(bid => { + {fBids.map((bid, i) => { const provider = providers && providers.find(x => x.owner === bid.provider); const showBid = provider?.isValidVersion && (!isSendingManifest || selectedBid?.id === bid.id); return (showBid || selectedNetworkId !== MAINNET_ID) && provider ? ( void; @@ -29,7 +30,7 @@ type Props = { isSendingManifest: boolean; }; -export const BidRow: React.FunctionComponent = ({ bid, selectedBid, handleBidSelected, disabled, provider, isSendingManifest }) => { +export const BidRow: React.FunctionComponent = ({ index, bid, selectedBid, handleBidSelected, disabled, provider, isSendingManifest }) => { const { favoriteProviders, updateFavoriteProviders } = useLocalNotes(); const isFavorite = favoriteProviders.some(x => provider.owner === x); const isCurrentBid = selectedBid?.id === bid.id; @@ -72,6 +73,7 @@ export const BidRow: React.FunctionComponent = ({ bid, selectedBid, handl [`border bg-green-100 dark:bg-green-900`]: isCurrentBid })} onClick={onRowClick} + data-testid={`bid-list-row-${index}`} >
@@ -188,6 +190,7 @@ export const BidRow: React.FunctionComponent = ({ bid, selectedBid, handl checked={isCurrentBid} onChange={() => handleBidSelected(bid)} disabled={bid.state !== "open" || disabled} + data-testid={`bid-list-row-radio-${index}`} /> )} diff --git a/apps/deploy-web/src/components/new-deployment/CreateLease.tsx b/apps/deploy-web/src/components/new-deployment/CreateLease.tsx index 9435e1b40..9ff0c9199 100644 --- a/apps/deploy-web/src/components/new-deployment/CreateLease.tsx +++ b/apps/deploy-web/src/components/new-deployment/CreateLease.tsx @@ -283,6 +283,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => { onClick={hasActiveBid ? sendManifest : createLease} className="w-full whitespace-nowrap md:w-auto" disabled={hasActiveBid ? false : dseqList.some(gseq => !selectedBids[gseq]) || isSendingManifest || isCreatingLeases} + data-testid="create-lease-button" > {isCreatingLeases || isSendingManifest ? ( @@ -343,7 +344,12 @@ export const CreateLease: React.FunctionComponent = ({ dseq }) => {
- setIsFilteringAudited(value as boolean)} id="provider-audited" /> + setIsFilteringAudited(value as boolean)} + id="provider-audited" + data-testid="create-lease-filter-audited" + />