diff --git a/dashboard/src/components/Boilerplate.tsx b/dashboard/src/components/Boilerplate.tsx deleted file mode 100644 index 21c63d4df6..0000000000 --- a/dashboard/src/components/Boilerplate.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { useState } from "react"; - -import styled from "styled-components"; - -type Props = {}; - -const Boilerplate: React.FC = (props) => { - const [someState, setSomeState] = useState(""); - - return ; -}; - -export default Boilerplate; - -const StyledBoilerplate = styled.div``; diff --git a/dashboard/src/components/SOC2Checks.tsx b/dashboard/src/components/SOC2Checks.tsx deleted file mode 100644 index 995258b5e7..0000000000 --- a/dashboard/src/components/SOC2Checks.tsx +++ /dev/null @@ -1,299 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { withRouter, type RouteComponentProps } from "react-router"; -import styled from "styled-components"; - -import Container from "components/porter/Container"; - -import external_link from "assets/external-link.svg"; -import failure from "assets/failure.svg"; -import pending from "assets/pending.svg"; -import healthy from "assets/status-healthy.png"; - -import Loading from "./Loading"; -import Link from "./porter/Link"; -import Spacer from "./porter/Spacer"; -import Text from "./porter/Text"; -import ToggleRow from "./porter/ToggleRow"; -import { type Soc2Data, type Soc2Check } from "shared/types"; - -type Props = RouteComponentProps & { - soc2Data: Soc2Check; - error?: string; - enableAll: boolean; - setSoc2Data: (x: Soc2Check) => void; - readOnly: boolean; -}; -type ItemProps = RouteComponentProps & { - checkKey: string; - checkLabel?: string; -}; - -const SOC2Checks: React.FC = ({ - soc2Data, - enableAll, - setSoc2Data, - readOnly, -}) => { - // const { soc2Data, setSoc2Data } = useContext(Context); - const soc2Checks = soc2Data?.soc2_checks || {}; - - const combinedKeys = new Set([...Object.keys(soc2Checks)]); - - useEffect(() => { - if (enableAll) { - const newSOC2Checks = Object.keys(soc2Checks).reduce((acc, key) => { - acc[key] = { - ...soc2Checks[key], - status: soc2Checks[key].enabled - ? soc2Checks[key].status === "PENDING_ENABLED" - ? "PENDING_ENABLED" - : "ENABLED" - : "PENDING_ENABLED", - }; - return acc; - }, {}); - setSoc2Data((prev: Soc2Data) => ({ - ...prev, - soc2_checks: newSOC2Checks, - })); - } else { - const newSOC2Checks = Object.keys(soc2Checks).reduce((acc, key) => { - acc[key] = { - ...soc2Checks[key], - status: !soc2Checks[key].enabled - ? "" - : soc2Checks[key].status === "PENDING_ENABLED" - ? "PENDING_ENABLED" - : "ENABLED", - }; - return acc; - }, {}); - setSoc2Data((prev: Soc2Data) => ({ - ...prev, - soc2_checks: newSOC2Checks, - })); - } - }, [enableAll]); - - const Soc2Item: React.FC = ({ checkKey, checkLabel }) => { - const checkData = soc2Data?.soc2_checks?.[checkKey]; - const hasMessage = checkData?.message; - const enabled = checkData?.enabled; - const status = checkData?.status; - - const [isExpanded, setIsExpanded] = useState(true); - - const handleToggle = (): void => { - if (hasMessage && enabled) { - setIsExpanded(!isExpanded); - } - }; - - const determineStatus = (currentStatus: string): string => { - if (currentStatus === "ENABLED") { - return "PENDING_DISABLED"; - } - if (currentStatus === "PENDING_DISABLED") { - return "ENABLED"; - } - if (currentStatus === "PENDING_ENABLED") { - return ""; - } - if (currentStatus === "") { - return "PENDING_ENABLED"; - } - }; - - const handleEnable = (): void => { - setSoc2Data((prev) => ({ - ...prev, - soc2_checks: { - ...prev.soc2_checks, - [checkKey]: { - ...prev.soc2_checks[checkKey], - enabled: !prev.soc2_checks[checkKey].enabled, - status: determineStatus(prev.soc2_checks[checkKey].status), - }, - }, - })); - }; - - return ( - - {" "} - {/* Pass isExpanded as a prop */} - - {status === "LOADING" && ( - - )} - {status === "PENDING_ENABLED" && } - {status === "ENABLED" && } - {(status === "" || status === "PENDING_DISABLED") && ( - - )} - - {checkLabel} - {enabled && ( - - arrow_drop_down - - )} - - {isExpanded && hasMessage && ( -
- - {checkData?.message} - - {checkData?.metadata && - Object.entries(checkData.metadata).map(([key, value]) => ( - <> -
- {key}: - {value} -
- - ))} - - - {!checkData?.hideToggle && ( - <> - - { - handleEnable(); - }} - disabled={ - readOnly || - enableAll || - (enabled && - checkData?.locked && - status !== "PENDING_ENABLED") - } - disabledTooltip={ - readOnly - ? "Wait for provisioning to complete before editing this field." - : enableAll - ? "Global SOC 2 setting must be disabled to toggle this" - : checkData?.disabledTooltip - } - > - - {checkData.enabledField} - - {checkData.info} - - - - {checkData.link && ( - { - window.open(checkData.link, "_blank"); - }} - > - - More Info - - )} - - - - )} -
- )} -
- ); - }; - return ( - <> - <> - {/*
- -
*/} - - - {Array.from(combinedKeys).map((checkKey) => ( - - ))} - - - - ); -}; - -export default withRouter(SOC2Checks); - -const AppearingDiv = styled.div<{ color?: string }>` - animation: floatIn 0.5s; - animation-fill-mode: forwards; - display: flex; - flex-direction: column; - color: ${(props) => props.color || "#ffffff44"}; - - @keyframes floatIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0px); - } - } -`; -const StatusIcon = styled.img` - height: ${(props) => (props.height ? props.height : "14px")}; -`; - -const CheckItemContainer = styled.div` - display: flex; - flex-direction: column; - border: ${(props) => - props.isExpanded - ? "2px solid #3a48ca" - : "1px solid " + - props.theme.border}; // Thicker and blue border if expanded - border-radius: 5px; - font-size: 13px; - width: 100%; - margin-bottom: 10px; - padding-left: 10px; - cursor: ${(props) => (props.hasMessage ? "pointer" : "default")}; - background: ${(props) => props.theme.clickable.bg}; -`; - -const CheckItemTop = styled.div` - display: flex; - align-items: center; - padding: 10px; - background: ${(props) => props.theme.clickable.bg}; -`; - -const ExpandIcon = styled.i<{ isExpanded: boolean }>` - margin-left: 8px; - color: #ffffff66; - font-size: 20px; - cursor: pointer; - border-radius: 20px; - transform: ${(props) => (props.isExpanded ? "" : "rotate(-90deg)")}; -`; -const ErrorMessageLabel = styled.span` - font-weight: bold; - margin-left: 10px; -`; -const ErrorMessageContent = styled.div` - font-family: "Courier New", Courier, monospace; - padding: 5px 10px; - border-radius: 4px; - margin-left: 10px; - user-select: text; - cursor: text; -`; -const TagIcon = styled.img` - height: 12px; - margin-right: 3px; -`; diff --git a/dashboard/src/components/expanded-object/Header.tsx b/dashboard/src/components/expanded-object/Header.tsx deleted file mode 100644 index 977ab46b79..0000000000 --- a/dashboard/src/components/expanded-object/Header.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import DynamicLink from "components/DynamicLink"; -import React from "react"; -import styled from "styled-components"; -import TitleSection from "components/TitleSection"; - -import leftArrow from "assets/left-arrow.svg"; - -type Props = { - last_updated: string; - back_link: string; - name: string; - icon: string; - inline_title_items?: React.ReactNodeArray; - sub_title_items?: React.ReactNodeArray; - materialIconClass?: string; -}; - -const Header: React.FunctionComponent = (props) => { - const { - last_updated, - back_link, - icon, - name, - inline_title_items, - sub_title_items, - materialIconClass, - } = props; - - return ( - <> - - - - Back - - - - - {name} - <Flex>{inline_title_items}</Flex> - - - {sub_title_items || ( - - Last updated {last_updated} - - )} - - - ); -}; - -export default Header; - -const Wrap = styled.div` - z-index: 999; -`; - -const ArrowIcon = styled.img` - width: 15px; - margin-right: 8px; - opacity: 50%; -`; - -const BreadcrumbRow = styled.div` - width: 100%; - display: flex; - justify-content: flex-start; -`; - -const Breadcrumb = styled(DynamicLink)` - color: #aaaabb88; - font-size: 13px; - margin-bottom: 15px; - display: flex; - align-items: center; - margin-top: -10px; - z-index: 999; - padding: 5px; - padding-right: 7px; - border-radius: 5px; - cursor: pointer; - :hover { - background: #ffffff11; - } -`; - -const HeaderWrapper = styled.div` - position: relative; - margin-bottom: 10px; -`; - -const InfoWrapper = styled.div` - display: flex; - align-items: center; - width: auto; - justify-content: space-between; -`; - -const InfoText = styled.span` - font-size: 13px; - color: #aaaabb66; -`; - -const Title = styled(TitleSection)` - font-size: 16px; - margin-top: 4px; -`; - -const Flex = styled.div` - display: flex; - align-items: center; - margin: 10px 0; -`; diff --git a/dashboard/src/components/porter/TemplateComponent.tsx b/dashboard/src/components/porter/TemplateComponent.tsx deleted file mode 100644 index db3220ce29..0000000000 --- a/dashboard/src/components/porter/TemplateComponent.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useEffect, useState } from "react"; -import styled from "styled-components"; - -type Props = { -}; - -const TemplateComponent: React.FC = ({ -}) => { - const [isExpanded, setIsExpanded] = useState(false); - - useEffect(() => { - // Do something - }, []); - - return ( - - - ); -}; - -export default TemplateComponent; - -const StyledTemplateComponent = styled.div` -width: 100%; -animation: fadeIn 0.3s 0s; -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -`; \ No newline at end of file diff --git a/dashboard/src/components/repo-selector/NewGHAction.tsx b/dashboard/src/components/repo-selector/NewGHAction.tsx deleted file mode 100644 index 5385a43f71..0000000000 --- a/dashboard/src/components/repo-selector/NewGHAction.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import { ChartType } from "shared/types"; -import { Context } from "shared/Context"; -import InputRow from "components/form-components/InputRow"; - -import Loading from "../Loading"; - -type PropsType = { - repoName: string; - dockerPath: string; - grid: number; - chart: ChartType; - imgURL: string; - setURL: (x: string) => void; -}; - -type StateType = { - trueDockerPath: string; - loading: boolean; - error: boolean; -}; - -export default class NewGHAction extends Component { - state = { - dockerRepo: "", - trueDockerPath: this.props.dockerPath, - loading: false, - error: false, - }; - - componentDidMount() { - if (this.props.dockerPath[0] === "/") { - this.setState({ - trueDockerPath: this.props.dockerPath.substring( - 1, - this.props.dockerPath.length - ), - }); - } - } - - renderConfirmation = () => { - let { loading } = this.state; - if (loading) { - return ( - - - - ); - } - - return ( - - console.log(x)} - /> - console.log(x)} - /> - this.props.setURL(x)} - /> - - ); - }; - - render() { - return
{this.renderConfirmation()}
; - } -} - -NewGHAction.contextType = Context; - -const Holder = styled.div` - padding: 0px 12px; -`; - -const LoadingWrapper = styled.div` - padding: 30px 0px; - background: #ffffff11; - display: flex; - align-items: center; - justify-content: center; - font-size: 13px; - color: #ffffff44; -`; diff --git a/dashboard/src/lib/hooks/useClusterResourceLimits.ts b/dashboard/src/lib/hooks/useClusterResourceLimits.ts deleted file mode 100644 index aa4a313253..0000000000 --- a/dashboard/src/lib/hooks/useClusterResourceLimits.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { useEffect, useState } from "react"; -import { - Contract, - GKENodePoolType, - LoadBalancerType, - NodeGroupType, - NodePoolType, -} from "@porter-dev/api-contracts"; -import { useQuery } from "@tanstack/react-query"; -import convert from "convert"; -import { match } from "ts-pattern"; -import { z } from "zod"; - -import { azureMachineTypeDetails } from "components/azureUtils"; -import { AWS_INSTANCE_LIMITS } from "main/home/app-dashboard/validate-apply/services-settings/tabs/utils"; - -import api from "shared/api"; - -const DEFAULT_INSTANCE_CLASS = "t3"; -const DEFAULT_INSTANCE_SIZE = "medium"; - -export type ClientLoadBalancerType = "ALB" | "NLB" | "UNSPECIFIED"; - -const encodedContractValidator = z.object({ - ID: z.number(), - CreatedAt: z.string(), - UpdatedAt: z.string(), - DeletedAt: z.string().nullable(), - id: z.string(), - base64_contract: z.string(), - cluster_id: z.number(), - project_id: z.number(), - condition: z.string(), - condition_metadata: z.record(z.any()).nullable(), -}); - -export type NodeGroup = { - instanceType: string; - minInstances: number; - maxInstances: number; - nodeGroupType: string; - isStateful?: boolean; -}; - -export type EksKind = { - clusterName: string; - clusterVersion: string; - cidrRange: string; - region: string; - nodeGroups: NodeGroup[]; - loadBalancer: { - loadBalancerType: string; - }; - logging: Record; - network: { - vpcCidr: string; - serviceCidr: string; - }; -}; - -export type GKEKind = { - clusterName: string; - clusterVersion: string; - region: string; - nodePools: NodePools[]; - user: { - id: number; - }; - network: { - cidrRange: string; - controlPlaneCidr: string; - podCidr: string; - serviceCidr: string; - }; -}; - -export type NodePools = { - instanceType: string; - minInstances: number; - maxInstances: number; - nodePoolType: string; - isStateful?: boolean; - additionalTaints?: string[]; -}; - -const clusterNodesValidator = z - .object({ - labels: z - .object({ - "beta.kubernetes.io/instance-type": z.string().nullish(), - "porter.run/workload-kind": z.string().nullish(), - }) - .optional(), - }) - .transform((data) => { - const defaultResources = { - maxCPU: - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].vCPU, - maxRAM: - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].RAM, - maxGPU: 1, - }; - if (!data.labels) { - return defaultResources; - } - const workloadKind = data.labels["porter.run/workload-kind"]; - if ( - !workloadKind || - (workloadKind !== "application" && workloadKind !== "custom") - ) { - return defaultResources; - } - const instanceType = data.labels["beta.kubernetes.io/instance-type"]; - - if (!instanceType) { - return defaultResources; - } - - // Azure instance types are all prefixed with "Standard_" - if (instanceType.startsWith("Standard_")) { - const azureMachineType = azureMachineTypeDetails(instanceType); - if (azureMachineType) { - const { vCPU, RAM, GPU } = azureMachineType.resources; - return { - maxCPU: vCPU, - maxRAM: RAM, - maxGPU: GPU || 1, - }; - } else { - return defaultResources; - } - } - - let parsedType; - if (instanceType && instanceType.includes(".")) { - parsedType = z - .tuple([z.string(), z.string()]) - .safeParse(instanceType.split(".")); - } else if (instanceType && instanceType.includes("-")) { - const [instanceClass, ...instanceSizeParts] = instanceType.split("-"); - const instanceSize = instanceSizeParts.join("-"); - parsedType = z - .tuple([z.string(), z.string()]) - .safeParse([instanceClass, instanceSize]); - } else { - return defaultResources; // Return defaults if instanceType format is not recognized - } - - if (!parsedType.success) { - return defaultResources; - } - - const [instanceClass, instanceSize] = parsedType.data; - if (AWS_INSTANCE_LIMITS[instanceClass]?.[instanceSize]) { - const { vCPU, RAM, GPU } = - AWS_INSTANCE_LIMITS[instanceClass][instanceSize]; - return { - maxCPU: vCPU, - maxRAM: RAM, - maxGPU: GPU || 1, - }; - } - return defaultResources; - }); - -export const useClusterResourceLimits = ({ - projectId, - clusterId, - clusterStatus, -}: { - projectId: number | undefined; - clusterId: number | undefined; - clusterStatus: string | undefined; -}): { - maxCPU: number; - maxRAM: number; - // defaults indicate the resources assigned to new services - defaultCPU: number; - defaultRAM: number; - clusterContainsGPUNodes: boolean; - maxGPU: number; - clusterIngressIp: string; - loadBalancerType: ClientLoadBalancerType; -} => { - const SMALL_INSTANCE_UPPER_BOUND = 0.75; - const LARGE_INSTANCE_UPPER_BOUND = 0.9; - const DEFAULT_MULTIPLIER = 0.125; - const [clusterContainsGPUNodes, setClusterContainsGPUNodes] = useState(false); - const [maxGPU, setMaxGPU] = useState(1); - const [maxCPU, setMaxCPU] = useState( - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].vCPU * - SMALL_INSTANCE_UPPER_BOUND - ); - const [maxRAM, setMaxRAM] = useState( - // round to nearest 100 - Math.round( - (convert( - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].RAM, - "GiB" - ).to("MB") * - SMALL_INSTANCE_UPPER_BOUND) / - 100 - ) * 100 - ); - const [defaultCPU, setDefaultCPU] = useState( - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].vCPU * - DEFAULT_MULTIPLIER - ); - const [defaultRAM, setDefaultRAM] = useState( - // round to nearest 100 - Math.round( - (convert( - AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE].RAM, - "GiB" - ).to("MB") * - DEFAULT_MULTIPLIER) / - 100 - ) * 100 - ); - const [clusterIngressIp, setClusterIngressIp] = useState(""); - const [loadBalancerType, setLoadBalancerType] = - useState("UNSPECIFIED"); - - const getClusterNodes = useQuery( - ["getClusterNodes", projectId, clusterId], - async () => { - if (!projectId || !clusterId || clusterId === -1) { - return await Promise.resolve([]); - } - - const res = await api.getClusterNodes( - "", - {}, - { - project_id: projectId, - cluster_id: clusterId, - } - ); - return await z.array(clusterNodesValidator).parseAsync(res.data); - }, - { - enabled: !!projectId && !!clusterId, - refetchOnWindowFocus: false, - retry: false, - } - ); - - const { data: contract } = useQuery( - ["getContracts", projectId, clusterId, clusterStatus], - async () => { - if (!projectId || !clusterId || clusterId === -1) { - return; - } - - const res = await api.getContracts( - "", - {}, - { project_id: projectId } - ); - const contracts = await z - .array(encodedContractValidator) - .parseAsync(res.data); - if (contracts.length) { - const latestContract = contracts - .filter((contract) => contract.cluster_id === clusterId) - .sort( - (a, b) => - new Date(b.CreatedAt).getTime() - new Date(a.CreatedAt).getTime() - )[0]; - const decodedContract = Contract.fromJsonString( - atob(latestContract.base64_contract) - ); - return decodedContract.cluster; - } - }, - { - enabled: !!projectId, - refetchOnWindowFocus: false, - retry: false, - } - ); - - useEffect(() => { - if (getClusterNodes.isSuccess) { - const data = getClusterNodes.data; - if (data.length) { - // this logic handles CPU and RAM independently - we might want to change this later - const maxCPU = data.reduce((acc, curr) => { - return Math.max(acc, curr.maxCPU); - }, 0); - const maxRAM = data.reduce((acc, curr) => { - return Math.max(acc, curr.maxRAM); - }, 0); - const maxGPU = data.reduce((acc, curr) => { - return Math.max(acc, curr.maxGPU); - }, 0); - let maxMultiplier = SMALL_INSTANCE_UPPER_BOUND; - // if the instance type has more than 16 GB ram, we use 90% of the ram/cpu - // otherwise, we use 75% - if (maxRAM > 16) { - maxMultiplier = LARGE_INSTANCE_UPPER_BOUND; - } - // round down to nearest 0.5 cores - const newMaxCPU = Math.floor(maxCPU * maxMultiplier * 2) / 2; - // round down to nearest 100 MB - const newMaxRAM = - Math.round((convert(maxRAM, "GiB").to("MB") * maxMultiplier) / 100) * - 100; - setMaxCPU(newMaxCPU); - setMaxRAM(newMaxRAM); - setMaxGPU(maxGPU); - setDefaultCPU(Number((newMaxCPU * DEFAULT_MULTIPLIER).toFixed(2))); - setDefaultRAM(Number((newMaxRAM * DEFAULT_MULTIPLIER).toFixed(0))); - } - } - }, [getClusterNodes]); - - const getCluster = useQuery( - ["getClusterIngressIp", projectId, clusterId], - async () => { - if (!projectId || !clusterId || clusterId === -1) { - return await Promise.resolve({ ingress_ip: "" }); - } - - const res = await api.getCluster( - "", - {}, - { - project_id: projectId, - cluster_id: clusterId, - } - ); - - return await z.object({ ingress_ip: z.string() }).parseAsync(res.data); - }, - { - enabled: !!projectId && !!clusterId, - refetchOnWindowFocus: false, - retry: false, - } - ); - - useEffect(() => { - if (getCluster.isSuccess) { - setClusterIngressIp(getCluster.data.ingress_ip); - } - }, [getCluster]); - - useEffect(() => { - if (contract) { - const containsCustomNodeGroup = match(contract) - .with({ kindValues: { case: "eksKind" } }, (c) => { - return c.kindValues.value.nodeGroups.some( - (ng) => - (ng.nodeGroupType === NodeGroupType.CUSTOM && - (ng.instanceType.includes("g4dn") || - ng.instanceType.includes("p4d"))) || - (ng.nodeGroupType === NodeGroupType.APPLICATION && - (ng.instanceType.includes("g4dn") || - ng.instanceType.includes("p4d"))) - ); - }) - .with({ kindValues: { case: "gkeKind" } }, (c) => { - return c.kindValues.value.nodePools.some( - (ng) => - (ng.nodePoolType === GKENodePoolType.GKE_NODE_POOL_TYPE_CUSTOM && - ng.instanceType.includes("n1")) || - (ng.nodePoolType === - GKENodePoolType.GKE_NODE_POOL_TYPE_APPLICATION && - ng.instanceType.includes("n1")) - ); - }) - .with({ kindValues: { case: "aksKind" } }, (c) => { - return c.kindValues.value.nodePools.some( - (ng) => ng.nodePoolType === NodePoolType.CUSTOM - ); - }) - .otherwise(() => false); - - const loadBalancerType: ClientLoadBalancerType = match(contract) - .with({ kindValues: { case: "eksKind" } }, (c) => { - const loadBalancer = c.kindValues.value.loadBalancer; - if (!loadBalancer) { - return "UNSPECIFIED"; - } - return match(loadBalancer.loadBalancerType) - .with(LoadBalancerType.ALB, (): ClientLoadBalancerType => "ALB") - .with(LoadBalancerType.NLB, (): ClientLoadBalancerType => "NLB") - .otherwise((): ClientLoadBalancerType => "UNSPECIFIED"); - }) - .otherwise(() => "UNSPECIFIED"); - - // console.log(gpu); - // setMaxGPU(gpu); - setClusterContainsGPUNodes(containsCustomNodeGroup); - setLoadBalancerType(loadBalancerType); - } - }, [contract]); - - return { - maxCPU, - maxRAM, - defaultCPU, - defaultRAM, - clusterContainsGPUNodes, - maxGPU, - clusterIngressIp, - loadBalancerType, - }; -}; - -// this function returns the fraction which the resource sliders 'snap' to when the user turns on smart optimization -export const lowestClosestResourceMultipler = ( - min: number, - max: number, - value: number -): number => { - const fractions = [0.5, 0.25, 0.125]; - - for (const fraction of fractions) { - const newValue = fraction * (max - min) + min; - if (newValue <= value) { - return fraction; - } - } - - return 0.125; // Return 0 if no fraction rounds down -}; - -// this function is used to snap both resource sliders in unison when one is changed -export const closestMultiplier = ( - min: number, - max: number, - value: number -): number => { - const fractions = [0.5, 0.25, 0.125]; - let closestFraction = 0.125; - for (const fraction of fractions) { - const newValue = fraction * (max - min) + min; - if ( - Math.abs(newValue - value) < - Math.abs(closestFraction * (max - min) + min - value) - ) { - closestFraction = fraction; - } - } - - return closestFraction; -}; diff --git a/dashboard/src/lib/hooks/useResizeObserver.ts b/dashboard/src/lib/hooks/useResizeObserver.ts deleted file mode 100644 index 6541057b43..0000000000 --- a/dashboard/src/lib/hooks/useResizeObserver.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useLayoutEffect, useRef } from "react"; - -/* - * - * useResizeObserver takes in a callback function and returns a ref - * that can be attached to a DOM element. The callback function will - * be called whenever the DOM element is resized. - * - */ -function useResizeObserver( - callback: (target: T) => void -) { - const ref = useRef(null); - - useLayoutEffect(() => { - const element = ref?.current; - - if (!element) { - return; - } - - const observer = new ResizeObserver(() => { - callback(element); - }); - - observer.observe(element); - return () => { - observer.disconnect(); - }; - }, [callback, ref]); - - return ref; -} - -export default useResizeObserver; diff --git a/dashboard/src/main/home/WelcomeForm.tsx b/dashboard/src/main/home/WelcomeForm.tsx deleted file mode 100644 index 7dba4d6f7d..0000000000 --- a/dashboard/src/main/home/WelcomeForm.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import React, { useContext, useState } from "react"; -import styled from "styled-components"; -import { CSSTransition } from "react-transition-group"; -import api from "shared/api"; - -import { Context } from "shared/Context"; - -type Props = { - closeForm: () => void; -}; - -type StateType = { - active: boolean; -}; - -const WelcomeForm: React.FunctionComponent = ({}) => { - const context = useContext(Context); - const [active, setActive] = useState(true); - const [isCompany, setIsCompany] = useState(true); - const [name, setName] = useState(""); - const [company, setCompany] = useState(""); - const [role, setRole] = useState("unspecified"); - - const submitForm = () => { - api - .postWelcome( - "", - { - email: context.user && context.user.email, - name, - isCompany, - company, - role, - }, - {} - ) - .then(() => { - localStorage.setItem("welcomed", "true"); - setActive(false); - }) - .catch((err) => console.log(err)); - }; - - const renderContents = () => { - return ( - - Welcome to Porter - Just a few things before getting started. - - 1 What is your name? * - - setName(e.target.value)} - /> - - 2 What is your company website? * - - setCompany(e.target.value)} - /> - - 3 What is your role? * - - setRole("founder")} - selected={role === "founder"} - > - - {role === "founder" ? "check_box" : "check_box_outline_blank"} - {" "} - Founder - - setRole("developer")} - selected={role === "developer"} - > - - {role === "developer" ? "check_box" : "check_box_outline_blank"} - {" "} - Developer - - setRole("devops")} - selected={role === "devops"} - > - - {role === "devops" ? "check_box" : "check_box_outline_blank"} - {" "} - DevOps - - - company && role !== "unspecified" && submitForm()} - > - check Done - - - ); - }; - - return ( - setActive(true)} - onExited={() => setActive(false)} - > - -
- {renderContents()} -
-
-
-
-
- ); -}; - -export default WelcomeForm; - -const Circle = styled.div` - width: 13px; - height: 13px; - border-radius: 20px; - background: #ffffff11; - margin-right: 12px; - border: 1px solid #aaaabb; -`; - -const FadeWrapper = styled.div` - background: #202227; - opacity: 0; - animation: fadeIn 0.7s 0s; - animation-fill-mode: forwards; -`; - -const Num = styled.div` - display: flex; - align-items: center; - margin-right: 15px; - justify-content: center; - width: 30px; - height: 30px; - border: 1px solid #ffffff; -`; - -const Option = styled.div` - width: 500px; - max-width: 80vw; - height: 50px; - background: #ffffff22; - display: flex; - align-items: center; - margin-top: 15px; - border: 1px solid #aaaabb; - border-radius: 5px; - padding-left: 15px; - cursor: pointer; - :hover { - background: #ffffff44; - } - - > i { - font-size: 20px; - margin-right: 12px; - color: #aaaabb; - } - - opacity: 0; - animation: slideIn 0.7s 1.3s; - animation-fill-mode: forwards; - - @keyframes slideIn { - from { - opacity: 0; - transform: translateX(-30px); - } - to { - opacity: 1; - transform: translateX(0); - } - } -`; - -const Submit = styled(Option)<{ isDisabled: boolean }>` - border: 0; - opacity: 0; - animation: fadeIn 0.7s 0.5s; - animation-fill-mode: forwards; - margin-top: 35px; - cursor: ${(props) => (props.isDisabled ? "not-allowed" : "pointer")}; - background: ${(props) => (props.isDisabled ? "#aaaabb" : "#616FEEcc")}; - :hover { - filter: ${(props) => (props.isDisabled ? "" : "brightness(130%)")}; - background: ${(props) => (props.isDisabled ? "#aaaabb" : "#616FEEcc")}; - } - - > i { - color: #ffffff; - } -`; - -const RadioButton = styled(Option)<{ selected: boolean }>` - opacity: 0; - background: ${(props) => (props.selected ? "#ffffff44" : "#ffffff22")}; - animation: fadeIn 0.5s 0.2s; - animation-fill-mode: forwards; - - > div { - background: ${(props) => (props.selected ? "#ffffff44" : "")}; - } -`; - -const Input = styled.input` - width: 500px; - max-width: 80vw; - height: 50px; - background: #ffffff22; - font-size: 18px; - display: flex; - align-items: center; - margin-top: 0px; - color: #ffffff; - border: 1px solid #aaaabb; - border-radius: 5px; - padding-left: 15px; - margin-bottom: 40px; - - opacity: 0; - animation: fadeIn 0.5s 0.2s; - animation-fill-mode: forwards; -`; - -const Subtitle = styled.div<{ delay?: string }>` - margin: 20px 0 30px; - color: #aaaabb; - - opacity: 0; - animation: fadeIn 0.5s ${(props) => props.delay || "0.2s"}; - animation-fill-mode: forwards; -`; - -const SubtitleAlt = styled(Subtitle)` - margin: -5px 0 30px; - color: white; - display: flex; - align-items: center; - animation: fadeIn 0.5s 0.2s; - animation-fill-mode: forwards; -`; - -const Title = styled.div` - color: white; - - font-size: 26px; - margin-bottom: 5px; - display: flex; - align-items: center; - - opacity: 0; - animation: fadeIn 0.5s 0.2s; - animation-fill-mode: forwards; - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const StyledWelcomeForm = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - z-index: 1; - background: #202227; - - &.alert-exit { - opacity: 1; - } - &.alert-exit-active { - opacity: 0; - transform: translateY(-100px); - transition: opacity 500ms, transform 1000ms; - } -`; diff --git a/dashboard/src/main/home/app-dashboard/app-view/tabs/preview-environments/PreviewEnvironmentSettings.tsx b/dashboard/src/main/home/app-dashboard/app-view/tabs/preview-environments/PreviewEnvironmentSettings.tsx deleted file mode 100644 index 9108c87aa6..0000000000 --- a/dashboard/src/main/home/app-dashboard/app-view/tabs/preview-environments/PreviewEnvironmentSettings.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import Button from "components/porter/Button"; -import Spacer from "components/porter/Spacer"; -import Text from "components/porter/Text"; -import React from "react"; -import { Link } from "react-router-dom"; -import { useLatestRevision } from "../../LatestRevisionContext"; -import { useQuery } from "@tanstack/react-query"; -import api from "shared/api"; -import { useGithubWorkflow } from "lib/hooks/useGithubWorkflow"; -import styled from "styled-components"; -import healthy from "assets/status-healthy.png"; -import Icon from "components/porter/Icon"; -import { z } from "zod"; -import { PorterApp } from "@porter-dev/api-contracts"; - -type Props = {}; - -const PreviewEnvironmentSettings: React.FC = ({}) => { - const { porterApp, clusterId, projectId } = useLatestRevision(); - - const { data: templateExists, status } = useQuery( - ["getAppTemplate", projectId, clusterId, porterApp.name], - async () => { - try { - const res = await api.getAppTemplate( - "", - {}, - { - project_id: projectId, - cluster_id: clusterId, - porter_app_name: porterApp.name, - } - ); - - const data = await z - .object({ - template_b64_app_proto: z.string(), - app_env: z.object({ - variables: z.record(z.string()).default({}), - secret_variables: z.record(z.string()).default({}), - }), - }) - .parseAsync(res.data); - - const template = PorterApp.fromJsonString( - atob(data.template_b64_app_proto), - { - ignoreUnknownFields: true, - } - ); - - return { - template, - env: data.app_env, - }; - } catch (err) { - return null; - } - } - ); - - const { githubWorkflowFilename, isLoading } = useGithubWorkflow({ - porterApp, - fileNames: [`porter_preview_${porterApp.name}.yml`], - }); - - if (status === "loading" || isLoading) { - return null; - } - - return ( - <> - {templateExists && githubWorkflowFilename ? ( - - Preview Environments Enabled - - - ) : ( - - Enable preview environments for "{porterApp.name}" - - )} - - - {templateExists && githubWorkflowFilename - ? "Preview environments are enabled for this app" - : "Setup your app to automatically create preview environments for each pull request."} - - - - - - - - ); -}; - -export default PreviewEnvironmentSettings; - -const EnabledContainer = styled.div` - display: flex; - align-items: center; - column-gap: 0.75rem; -`; diff --git a/dashboard/src/main/home/app-dashboard/build-settings/AdvancedBuildSettings.tsx b/dashboard/src/main/home/app-dashboard/build-settings/AdvancedBuildSettings.tsx deleted file mode 100644 index 5bf0702b96..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/AdvancedBuildSettings.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useState } from "react"; -import styled from "styled-components"; -import Text from "components/porter/Text"; -import Spacer from "components/porter/Spacer"; -import Input from "components/porter/Input"; -import AnimateHeight from "react-animate-height"; -import Select from "components/porter/Select"; -import { BuildMethod, PorterApp } from "../types/porterApp"; -import BuildpackSettings from "./buildpacks/BuildpackSettings"; -import _ from "lodash"; - -interface AdvancedBuildSettingsProps { - porterApp: PorterApp; - updatePorterApp: (attrs: Partial) => void; - autoDetectBuildpacks: boolean; - buildView: BuildMethod; - setBuildView: (buildView: BuildMethod) => void; -} - -const AdvancedBuildSettings: React.FC = ({ - porterApp, - updatePorterApp, - autoDetectBuildpacks, - buildView, - setBuildView, -}) => { - const [showSettings, setShowSettings] = useState(false); - - return ( - <> - { - setShowSettings(!showSettings); - }} - > - {buildView == "docker" ? ( - - arrow_drop_down - Configure Dockerfile settings - - ) : ( - - arrow_drop_down - Configure buildpack settings - - )} - - - - - updatePorterApp({ dockerfile: val })} - /> - - - : } - - - - ); -}; - -export default AdvancedBuildSettings; - -const StyledAdvancedBuildSettings = styled.div` - color: ${({ showSettings }) => (showSettings ? "white" : "#aaaabb")}; - background: ${({ theme }) => theme.fg}; - border: 1px solid #494b4f; - :hover { - border: 1px solid #7a7b80; - color: white; - } - display: flex; - justify-content: space-between; - align-items: center; - border-radius: 5px; - height: 40px; - font-size: 13px; - width: 100%; - padding-left: 10px; - cursor: pointer; - border-bottom-left-radius: ${({ showSettings }) => showSettings && "0px"}; - border-bottom-right-radius: ${({ showSettings }) => showSettings && "0px"}; - - .dropdown { - margin-right: 8px; - font-size: 20px; - cursor: pointer; - border-radius: 20px; - transform: ${(props: { showSettings: boolean; isCurrent: boolean }) => - props.showSettings ? "" : "rotate(-90deg)"}; - } -`; - -const AdvancedBuildTitle = styled.div` - display: flex; - align-items: center; -`; - -const StyledSourceBox = styled.div` - width: 100%; - color: #ffffff; - padding: 25px 35px 25px; - position: relative; - font-size: 13px; - border-radius: 5px; - background: ${(props) => props.theme.fg}; - border: 1px solid #494b4f; - border-top: 0px; - border-top-left-radius: 0px; - border-top-right-radius: 0px; -`; diff --git a/dashboard/src/main/home/app-dashboard/build-settings/BuildSettingsTab.tsx b/dashboard/src/main/home/app-dashboard/build-settings/BuildSettingsTab.tsx deleted file mode 100644 index eec395bf4e..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/BuildSettingsTab.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React, { - useContext, - useState, -} from "react"; -import Text from "components/porter/Text"; -import Spacer from "components/porter/Spacer"; -import { CreateUpdatePorterAppOptions } from "shared/types"; -import { Context } from "shared/Context"; - -import api from "shared/api"; -import { AxiosError } from "axios"; -import Button from "components/porter/Button"; -import Checkbox from "components/porter/Checkbox"; -import SharedBuildSettings from "./SharedBuildSettings"; -import { BuildMethod, PorterApp } from "../types/porterApp"; -import _ from "lodash"; - -type Props = { - porterApp: PorterApp; - setTempPorterApp: (app: PorterApp) => void; - updatePorterApp: (options: Partial) => Promise; - clearStatus: () => void; - buildView: BuildMethod; - setBuildView: (buildView: BuildMethod) => void; -}; - -const BuildSettingsTab: React.FC = ({ - porterApp, - setTempPorterApp, - clearStatus, - updatePorterApp, - buildView, - setBuildView, -}) => { - const { setCurrentError, currentCluster, currentProject } = useContext(Context); - const [redeployOnSave, setRedeployOnSave] = useState(true); - const [runningWorkflowURL, setRunningWorkflowURL] = useState(""); - - const [buttonStatus, setButtonStatus] = useState< - "loading" | "success" | string - >(""); - - const triggerWorkflow = async () => { - try { - if (currentProject == null || currentCluster == null) { - return; - } - - const res = await api.reRunGHWorkflow( - "", - {}, - { - project_id: currentProject.id, - cluster_id: currentCluster.id, - git_installation_id: porterApp.git_repo_id, - owner: porterApp.repo_name?.split("/")[0], - name: porterApp.repo_name?.split("/")[1], - branch: porterApp.git_branch, - filename: "porter_stack_" + porterApp.name + ".yml", - } - ); - if (res.data != null) { - window.open(res.data, "_blank", "noreferrer") - } - } catch (error) { - if (!error?.response) { - throw error; - } - - let tmpError: AxiosError = error; - - /** - * @smell - * Currently the expanded chart is clearing all the state when a chart update is triggered (saveEnvVariables). - * Temporary usage of setCurrentError until a context is applied to keep the state of the ReRunError during re renders. - */ - - if (tmpError.response.status === 400) { - // setReRunError({ - // title: "No previous run found", - // description: - // "There are no previous runs for this workflow, please trigger manually a run before changing the build settings.", - // }); - setCurrentError( - "There are no previous runs for this workflow. Please manually trigger a run before changing build settings." - ); - return; - } - - if (tmpError.response.status === 409) { - // setReRunError({ - // title: "The workflow is still running", - // description: - // 'If you want to make more changes, please choose the option "Save" until the workflow finishes.', - // }); - - if (typeof tmpError.response.data === "string") { - setRunningWorkflowURL(tmpError.response.data); - } - setCurrentError( - 'The workflow is still running. You can "Save" the current build settings for the next workflow run and view the current status of the workflow here: ' + - tmpError.response.data - ); - return; - } - - if (tmpError.response.status === 404) { - let description = "No action file matching this deployment was found."; - if (typeof tmpError.response.data === "string") { - const filename = tmpError.response.data; - description = description.concat( - `Please check that the file "${filename}" exists in your repository.` - ); - } - // setReRunError({ - // title: "The action doesn't seem to exist", - // description, - // }); - - setCurrentError(description); - return; - } - throw error; - } - }; - - const saveConfig = async () => { - try { - await updatePorterApp({}); - } catch (err) { - console.log(err); - } - }; - - const handleSave = async () => { - setButtonStatus("loading"); - - try { - await saveConfig(); - setButtonStatus("success"); - } catch (error) { - setButtonStatus("Something went wrong"); - console.log(error); - } - }; - - const handleSaveAndReDeploy = async () => { - setButtonStatus("loading"); - - try { - await saveConfig(); - await triggerWorkflow(); - setButtonStatus("success"); - clearStatus(); - } catch (error) { - setButtonStatus("Something went wrong"); - console.log(error); - } - }; - return ( - <> - ) => setTempPorterApp(PorterApp.setAttributes(porterApp, attrs))} - setPorterYaml={() => { }} - autoDetectionOn={false} - canChangeRepo={false} - buildView={buildView} - setBuildView={setBuildView} - /> - - setRedeployOnSave(!redeployOnSave)} - > - Re-run build and deploy on save - - - - - ); -}; - -export default BuildSettingsTab; diff --git a/dashboard/src/main/home/app-dashboard/build-settings/DetectDockerfileAndPorterYaml.tsx b/dashboard/src/main/home/app-dashboard/build-settings/DetectDockerfileAndPorterYaml.tsx deleted file mode 100644 index c883826340..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/DetectDockerfileAndPorterYaml.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import React, { useState, useEffect, useContext, useCallback } from "react"; -import styled from "styled-components"; -import Button from "components/porter/Button"; -import api from "shared/api"; -import Error from "components/porter/Error"; - -import { Context } from "shared/Context"; -import { FileType } from "shared/types"; - -import Spacer from "components/porter/Spacer"; -import Modal from "components/porter/Modal"; -import Input from "components/porter/Input"; -import Text from "components/porter/Text"; -import Link from "components/porter/Link"; -import { PorterApp } from "../types/porterApp"; - -type PropsType = { - setPorterYaml: (yaml: string, filename: string) => void; - porterApp: PorterApp; - updatePorterApp: (attrs: Partial) => void; - updateDockerfileFound: () => void; - setBuildpackView: () => void; -}; - -const DetectDockerfileAndPorterYaml: React.FC = ({ - setPorterYaml, - porterApp, - updatePorterApp, - updateDockerfileFound, - setBuildpackView, -}) => { - const [showModal, setShowModal] = useState(false); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(false); - const [contents, setContents] = useState([]); - const [buttonStatus, setButtonStatus] = useState(""); - const [possiblePorterYamlPath, setPossiblePorterYamlPath] = useState(""); - - const { currentProject } = useContext(Context); - const fetchAndSetPorterYaml = async (fileName: string) => { - setButtonStatus("loading"); - const response = await fetchPorterYamlContent(fileName); - if (response == null) { - setButtonStatus(); - } else { - setPorterYaml(atob(response.data), fileName); - setButtonStatus("success"); - } - setShowModal(false); - }; - - useEffect(() => { - const fetchOnRender = async () => { - try { - const response = await fetchPorterYamlContent("./porter.yaml"); - setPorterYaml(atob(response.data), "./porter.yaml"); - } catch (error) { - setShowModal(true); - } - }; - fetchOnRender(); - }, []); - - useEffect(() => { - updateContents(); - }, []); - - useEffect(() => { - const dockerFileItem = contents.find((item: FileType) => - item.path.includes("Dockerfile") - ); - - if (dockerFileItem) { - const path = dockerFileItem.path.startsWith("./") || dockerFileItem.path.startsWith("/") ? dockerFileItem.path : `./${dockerFileItem.path}`; - updatePorterApp({ dockerfile: path }); - updateDockerfileFound(); - } else { - setBuildpackView(); - } - }, [contents]); - - const renderContentList = () => { - contents.map((item: FileType, i: number) => { - let splits = item.path.split("/"); - let fileName = splits[splits.length - 1]; - if (fileName.includes("Dockerfile")) { - return false; - } - }); - - return true; - }; - - const fetchContents = () => { - if (currentProject == null) { - return; - } - - return api.getBranchContents( - "", - { dir: porterApp.build_context || "./" }, - { - project_id: currentProject.id, - git_repo_id: porterApp.git_repo_id, - kind: "github", - owner: porterApp.repo_name.split("/")[0], - name: porterApp.repo_name.split("/")[1], - branch: porterApp.git_branch, - } - ); - }; - - const fetchPorterYamlContent = async (porterYamlPath: string) => { - try { - if (currentProject == null) { - return; - } - const res = await api.getPorterYamlContents( - "", - { - path: porterYamlPath, - }, - { - project_id: currentProject.id, - git_repo_id: porterApp.git_repo_id, - kind: "github", - owner: porterApp.repo_name.split("/")[0], - name: porterApp.repo_name.split("/")[1], - branch: porterApp.git_branch, - } - ); - return res; - } catch (err) { - // console.log(err); - } - - }; - - const updateContents = async () => { - try { - const res = await fetchContents(); - let files = [] as FileType[]; - let folders = [] as FileType[]; - res.data.map((x: FileType, i: number) => { - x.type === "dir" ? folders.push(x) : files.push(x); - }); - - folders.sort((a: FileType, b: FileType) => { - return a.path < b.path ? 1 : 0; - }); - files.sort((a: FileType, b: FileType) => { - return a.path < b.path ? 1 : 0; - }); - let contents = folders.concat(files); - - setContents(contents); - setLoading(false); - setError(false); - } catch (err) { - console.log(err); - setLoading(false); - setError(true); - } - }; - - const NoPorterYamlContent = () => ( -
- No porter.yaml detected - - - - We were unable to find a porter.yaml file in your root directory. We - recommend that you add a porter.yaml file to your root directory - or specify the path here. - - - - Using porter.yaml - - -
- ); - return ( - <> - {showModal && ( - setShowModal(false)}> - - - Path to porter.yaml from repository root: - - - -
- - -
-
- )} - {renderContentList() && ( - <> - {possiblePorterYamlPath !== "" && ( - <> - Porter.yaml path: - - - - - - - )} - - - )} - - ); -}; - -export default DetectDockerfileAndPorterYaml; - -const Code = styled.span` - font-family: monospace; -`; diff --git a/dashboard/src/main/home/app-dashboard/build-settings/SharedBuildSettings.tsx b/dashboard/src/main/home/app-dashboard/build-settings/SharedBuildSettings.tsx deleted file mode 100644 index e4ea78a947..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/SharedBuildSettings.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import Input from "components/porter/Input"; -import Spacer from "components/porter/Spacer"; -import Text from "components/porter/Text"; -import React from "react"; -import styled from "styled-components"; -import { BuildMethod, PorterApp } from "../types/porterApp"; -import DetectDockerfileAndPorterYaml from "./DetectDockerfileAndPorterYaml"; -import RepositorySelector from "./RepositorySelector"; -import BranchSelector from "./BranchSelector"; -import AdvancedBuildSettings from "./AdvancedBuildSettings"; - -type Props = { - setPorterYaml: (yaml: string, filename: string) => void; - updatePorterApp: (attrs: Partial) => void; - porterApp: PorterApp; - autoDetectionOn: boolean; - canChangeRepo: boolean; - buildView: BuildMethod; - setBuildView: (buildView: BuildMethod) => void; -}; - -const SharedBuildSettings: React.FC = ({ - setPorterYaml, - updatePorterApp, - porterApp, - autoDetectionOn, - canChangeRepo, - buildView, - setBuildView, -}) => { - return ( - <> - Build settings - - Specify your GitHub repository. - - {porterApp.repo_name === "" && ( - <> - - - - - - - )} - {porterApp.repo_name !== "" && ( - <> - { }} - placeholder="" - /> - {canChangeRepo && - <> - { - updatePorterApp({ - repo_name: "", - git_branch: "", - dockerfile: "", - build_context: "./", - porter_yaml_path: "./porter.yaml", - }) - }} - > - keyboard_backspace - Select repo - - - - } - - Specify your GitHub branch. - - {porterApp.git_branch === "" && ( - <> - - updatePorterApp({ git_branch: branch })} - repo_name={porterApp.repo_name} - git_repo_id={porterApp.git_repo_id} - /> - - - )} - {porterApp.git_branch !== "" && ( - <> - { }} - placeholder="" - /> - { - updatePorterApp({ - git_branch: "", - dockerfile: "", - build_context: "./", - porter_yaml_path: "./porter.yaml", - }) - }} - > - keyboard_backspace - Select branch - - - Specify your application root path. - - updatePorterApp({ build_context: val })} - /> - - {/* TODO: refactor everything from the below 'component' into this file */} - {autoDetectionOn && ( - setBuildView("docker")} - setBuildpackView={() => setBuildView("buildpacks")} - /> - )} - - - )} - - )} - - ); -}; - -export default SharedBuildSettings; - -const DarkMatter = styled.div<{ antiHeight?: string }>` - width: 100%; - margin-top: ${(props) => props.antiHeight || "-15px"}; -`; - -const ExpandedWrapper = styled.div` - margin-top: 10px; - width: 100%; - border-radius: 3px; - max-height: 275px; -`; - -const BackButton = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 22px; - cursor: pointer; - font-size: 13px; - height: 35px; - padding: 5px 13px; - margin-bottom: -7px; - padding-right: 15px; - border: 1px solid #ffffff55; - border-radius: 100px; - width: ${(props: { width: string }) => props.width}; - color: white; - background: #ffffff11; - - :hover { - background: #ffffff22; - } - - > i { - color: white; - font-size: 16px; - margin-right: 6px; - } -`; - diff --git a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/AddCustomBuildpackComponent.tsx b/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/AddCustomBuildpackComponent.tsx deleted file mode 100644 index 7389cf8b89..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/AddCustomBuildpackComponent.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import InputRow from "components/form-components/InputRow"; -import React, { useState } from "react"; -import styled, { keyframes } from "styled-components"; -import { Buildpack } from "../../types/buildpack"; - -function isValidBuildpack(url: string): boolean { - const urnPrefix = "urn:cnb:registry:"; - if (url.startsWith(urnPrefix)) { - return true; - } - try { - new URL(url); - return true; - } catch (error) { - return false; - } -} - -const AddCustomBuildpackComponent: React.FC<{ - onAdd: (buildpack: Buildpack) => void; -}> = ({ onAdd }) => { - const [buildpackUrl, setBuildpackUrl] = useState(""); - const [error, setError] = useState(false); - - const handleAddCustomBuildpack = () => { - if (buildpackUrl === "" || !isValidBuildpack(buildpackUrl)) { - setError(true); - return; - } - setBuildpackUrl(""); - onAdd({ - buildpack: buildpackUrl, - name: buildpackUrl, - config: {}, - }); - }; - - return ( - - - - - GitHub or ZIP URL - { - setError(false); - setBuildpackUrl(newUrl as string); - }} - /> - Please enter a valid url - - - - - handleAddCustomBuildpack()}> - add - - - - ); -}; - -export default AddCustomBuildpackComponent; - -const fadeIn = keyframes` - from { - opacity: 0; - } - to { - opacity: 1; - } -`; - -const StyledCard = styled.div<{ marginBottom?: string }>` - display: flex; - align-items: center; - justify-content: space-between; - border: 1px solid #494b4f; - background: ${({ theme }) => theme.fg}; - margin-bottom: ${(props) => props.marginBottom || "30px"}; - border-radius: 8px; - padding: 14px; - overflow: hidden; - height: 60px; - font-size: 13px; - animation: ${fadeIn} 0.5s; -`; - -const ContentContainer = styled.div` - display: flex; - height: 100%; - width: 100%; - align-items: center; -`; - -const EventInformation = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const BuildpackInputContainer = styled.div` - font-family: "Work Sans", sans-serif; - font-weight: 500; - color: #ffffff; - padding-left: 15px; -`; - -const BuildpackUrlInput = styled(InputRow)` - width: auto; - min-width: 300px; - max-width: 600px; - margin: unset; - margin-left: 10px; - display: inline-block; -`; - -const ErrorText = styled.span` - color: red; - margin-left: 10px; - display: ${(props: { hasError: boolean }) => - props.hasError ? "inline-block" : "none"}; -`; - -const ActionContainer = styled.div` - display: flex; - align-items: center; - white-space: nowrap; - height: 100%; -`; - -const ActionButton = styled.button` - position: relative; - border: none; - background: none; - color: white; - padding: 5px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - cursor: pointer; - color: #aaaabb; - - :hover { - background: #ffffff11; - border: 1px solid #ffffff44; - } - - > span { - font-size: 20px; - } -`; \ No newline at end of file diff --git a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackCard.tsx b/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackCard.tsx deleted file mode 100644 index 5dd33205a8..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackCard.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useMemo } from "react"; -import { DeviconsNameList } from "assets/devicons-name-list"; -import styled, { keyframes } from "styled-components"; -import { Draggable } from "react-beautiful-dnd"; -import { Buildpack } from "main/home/app-dashboard/types/buildpack"; - -interface Props { - buildpack: Buildpack; - action: "add" | "remove"; - onClickFn: (buildpack: string) => void; - index: number; - draggable: boolean; -} - -const BuildpackCard: React.FC = ({ - buildpack, - action, - onClickFn, - index, - draggable, -}) => { - const iconClassName = useMemo(() => { - if (!buildpack.name) { - return ""; - } - - const splits = buildpack.name.split("/"); - if (splits.length !== 1) { - return ""; - } - - const devicon = DeviconsNameList.find( - (devicon) => splits[0].toLowerCase() === devicon.name - ); - if (!devicon) { - return ""; - } - return `devicon-${devicon.name}-plain colored` - }, [buildpack.name]); - - const renderedBuildpackName = useMemo(() => { - return buildpack.name ?? buildpack.buildpack; - }, [buildpack.name]); - - return draggable ? ( - - {(provided) => ( - - - {iconClassName && } - - {renderedBuildpackName} - - - - onClickFn(buildpack.buildpack)}> - - {action === "remove" ? "delete" : "add"} - - - - - )} - - ) : ( - - - {iconClassName && } - - {renderedBuildpackName} - - - - onClickFn(buildpack.buildpack)}> - - {action === "remove" ? "delete" : "add"} - - - - - ); -}; - -export default BuildpackCard; - -const fadeIn = keyframes` - from { - opacity: 0; - } - to { - opacity: 1; - } -`; - -const StyledCard = styled.div<{ marginBottom?: string }>` - display: flex; - align-items: center; - justify-content: space-between; - border: 1px solid #494b4f; - background: ${({ theme }) => theme.fg}; - margin-bottom: ${(props) => props.marginBottom || "30px"}; - border-radius: 8px; - padding: 14px; - overflow: hidden; - height: 60px; - font-size: 13px; - animation: ${fadeIn} 0.5s; -`; - -const ContentContainer = styled.div` - display: flex; - height: 100%; - width: 100%; - align-items: center; -`; - -const Icon = styled.span` - font-size: 20px; - margin-left: 10px; - margin-right: 20px -`; - -const EventInformation = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const EventName = styled.div` - font-family: "Work Sans", sans-serif; - font-weight: 500; - color: #ffffff; -`; - -const ActionContainer = styled.div` - display: flex; - align-items: center; - white-space: nowrap; - height: 100%; -`; - -const ActionButton = styled.button` - position: relative; - border: none; - background: none; - color: white; - padding: 5px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - cursor: pointer; - color: #aaaabb; - :hover { - background: #ffffff11; - border: 1px solid #ffffff44; - } - > span { - font-size: 20px; - } -`; \ No newline at end of file diff --git a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackConfigurationModal.tsx b/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackConfigurationModal.tsx deleted file mode 100644 index 33d591847b..0000000000 --- a/dashboard/src/main/home/app-dashboard/build-settings/buildpacks/BuildpackConfigurationModal.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import Spacer from 'components/porter/Spacer'; -import Text from 'components/porter/Text'; -import React from 'react'; -import BuildpackList from './BuildpackList'; -import AddCustomBuildpackComponent from './AddCustomBuildpackComponent'; -import Icon from 'components/porter/Icon'; -import Button from 'components/porter/Button'; -import Modal from 'components/porter/Modal'; -import styled from 'styled-components'; -import Select from 'components/porter/Select'; -import stars from "assets/stars-white.svg"; -import { Buildpack } from '../../types/buildpack'; -import { PorterApp } from '../../types/porterApp'; - -interface Props { - closeModal: () => void; - selectedStack: string; - sortedStackOptions: { value: string; label: string }[]; - setStackValue: (value: string) => void; - selectedBuildpacks: Buildpack[]; - setSelectedBuildpacks: (buildpacks: Buildpack[]) => void; - availableBuildpacks: Buildpack[]; - setAvailableBuildpacks: (buildpacks: Buildpack[]) => void; - porterApp: PorterApp; - updatePorterApp: (attrs: Partial) => void; - isDetectingBuildpacks: boolean; - detectBuildpacksError: string; - handleAddCustomBuildpack: (buildpack: Buildpack) => void; - detectAndSetBuildPacks: (detect: boolean) => void; -} -const BuildpackConfigurationModal: React.FC = ({ - closeModal, - selectedStack, - sortedStackOptions, - setStackValue, - selectedBuildpacks, - setSelectedBuildpacks, - availableBuildpacks, - setAvailableBuildpacks, - porterApp, - updatePorterApp, - isDetectingBuildpacks, - detectBuildpacksError, - handleAddCustomBuildpack, - detectAndSetBuildPacks, -}) => { - return ( - - Buildpack Configuration - - - Builder: - {selectedStack === "" && - <> - - - No builder detected. Click 'Detect buildpacks' below to scan your repository for available builders and buildpacks. - - - } - {selectedStack !== "" && - <> - - - - {showFileSelector && -
- { setPossiblePorterYamlPath(`./${path}`); }} - isFileSelectable={(path: string) => path.endsWith(".yaml")} - headerText={"Select your porter.yaml:"} - /> -
- } - -
- - -
-
- ) : null; -}; - -export default PorterYamlModal; - -const Code = styled.span` - font-family: monospace; -`; - -const InputWrapper = styled.div` - width: 500px; - display: flex; - justify-content: space-between; -`; \ No newline at end of file diff --git a/dashboard/src/main/home/app-dashboard/new-app-flow/GithubConnectModal.tsx b/dashboard/src/main/home/app-dashboard/new-app-flow/GithubConnectModal.tsx deleted file mode 100644 index 55b633341f..0000000000 --- a/dashboard/src/main/home/app-dashboard/new-app-flow/GithubConnectModal.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { RouteComponentProps, withRouter } from "react-router"; -import styled from "styled-components"; -import React, { useEffect, useState } from "react"; - -import Modal from "components/porter/Modal"; -import Text from "components/porter/Text"; -import Spacer from "components/porter/Spacer"; -import ExpandableSection from "components/porter/ExpandableSection"; -import Fieldset from "components/porter/Fieldset"; -import Button from "components/porter/Button"; - -import api from "shared/api"; -import Error from "components/porter/Error"; - -import Helper from "components/form-components/Helper"; -import github from "assets/github-white.png"; - -type Props = RouteComponentProps & { - closeModal: () => void; - hasClickedDoNotConnect: boolean; - handleDoNotConnect: () => void; - setAccessError: (error: boolean) => void; - setAccessLoading: (loading: boolean) => void; - setAccessData: (data: GithubAppAccessData) => void; - accessData: GithubAppAccessData; - accessError: boolean; -}; - -interface GithubAppAccessData { - username?: string; - accounts?: string[]; - accessError?: boolean; -} - -const GithubConnectModal: React.FC = ({ - closeModal, - hasClickedDoNotConnect, - handleDoNotConnect, - accessError, - setAccessError, - setAccessLoading, - setAccessData, - accessData, -}) => { - const [loading, setLoading] = React.useState(false); - const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}`; - const encoded_redirect_uri = encodeURIComponent(url); - - const renderGithubConnect = () => { - const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}`; - const encoded_redirect_uri = encodeURIComponent(url); - - if (accessError) { - return ( - <> - To deploy from GitHub, authorize Porter to view your repos. - - - No connected repos found. - - Authorize Porter to view your repos. - - - - - - - ); - } else if (!accessData.accounts || accessData.accounts?.length == 0) { - return ( - <> - - You are currently authorized as {accessData.username}. - - - - - Install the Porter GitHub app - - - - - ); - } - }; - useEffect(() => { - api - .getGithubAccounts("", {}, {}) - .then(({ data }) => { - setAccessData(data); - setAccessLoading(false); - }) - .catch(() => { - setAccessError(true); - setAccessLoading(false); - }); - }, []); - return ( - !hasClickedDoNotConnect && - (accessError || - !accessData.accounts || - accessData.accounts?.length === 0) && ( - <> - - - - Configure GitHub - - - {renderGithubConnect()} - - - ) - ); -}; - -export default withRouter(GithubConnectModal); - -const B = styled.b` - display: inline; - color: #ffffff; - margin-left: 5px; -`; - -const GitIcon = styled.img` - width: 15px; - height: 15px; - opacity: 0.9; - margin-right: 10px; - filter: brightness(120%); -`; - -const ListWrapper = styled.div` - width: 100%; - height: 240px; - background: #ffffff11; - display: flex; - align-items: center; - justify-content: center; - border-radius: 5px; - margin-top: 20px; - padding: 40px; -`; -const A = styled.a` - color: #8590ff; - text-decoration: underline; - margin-left: 5px; - cursor: pointer; -`; - -const ConnectToGithubButton = styled.a` - width: 240px; - justify-content: center; - border-radius: 5px; - display: flex; - flex-direction: row; - align-items: center; - font-size: 13px; - cursor: pointer; - font-family: "Work Sans", sans-serif; - color: white; - font-weight: 500; - padding: 10px; - overflow: hidden; - white-space: nowrap; - border: 1px solid #494b4f; - text-overflow: ellipsis; - cursor: ${(props: { disabled?: boolean }) => - props.disabled ? "not-allowed" : "pointer"}; - - background: ${(props: { disabled?: boolean }) => - props.disabled ? "#aaaabbee" : "#2E3338"}; - :hover { - background: ${(props: { disabled?: boolean }) => - props.disabled ? "" : "#353a3e"}; - } - - > i { - color: white; - width: 18px; - height: 18px; - font-weight: 600; - font-size: 12px; - border-radius: 20px; - display: flex; - align-items: center; - margin-right: 5px; - justify-content: center; - } - &:hover { - background: ${(props: { disabled?: boolean }) => - props.disabled ? "" : "#353a3e"}; - } - - &:not([disabled]) { - cursor: pointer; - } -`; - -const GitHubIcon = styled.img` - width: 20px; - filter: brightness(150%); - margin-right: 10px; -`; diff --git a/dashboard/src/main/home/app-dashboard/new-app-flow/SourceSettings.tsx b/dashboard/src/main/home/app-dashboard/new-app-flow/SourceSettings.tsx deleted file mode 100644 index d9b09bb5e0..0000000000 --- a/dashboard/src/main/home/app-dashboard/new-app-flow/SourceSettings.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import AnimateHeight from "react-animate-height"; -import React from "react"; -import Spacer from "components/porter/Spacer"; -import styled from "styled-components"; -import { SourceType } from "./SourceSelector"; -import { RouteComponentProps, withRouter } from "react-router"; -import SharedBuildSettings from "../build-settings/SharedBuildSettings"; -import { BuildMethod, PorterApp } from "../types/porterApp"; -import ImageSettings from "../image-settings/ImageSettings"; - -type Props = RouteComponentProps & { - source: SourceType | undefined; - imageUrl: string; - setImageUrl: (x: string) => void; - imageTag: string; - setImageTag: (x: string) => void; - setPorterYaml: (yaml: string, filename: string) => void; - porterApp: PorterApp; - setPorterApp: React.Dispatch>; - buildView: BuildMethod; - setBuildView: (buildView: BuildMethod) => void; - projectId: number; - resetImageInfo: () => void; -}; - -const SourceSettings: React.FC = ({ - source, - imageUrl, - setImageUrl, - imageTag, - setImageTag, - setPorterYaml, - porterApp, - setPorterApp, - buildView, - setBuildView, - projectId, - resetImageInfo, -}) => { - return ( - - - - {source === "github" ? ( - ) => setPorterApp((prev: PorterApp) => PorterApp.setAttributes(prev, attrs))} - autoDetectionOn={true} - canChangeRepo={true} - buildView={buildView} - setBuildView={setBuildView} - /> - ) : - - } - - - ); -}; - -export default withRouter(SourceSettings); - -const SourceSettingsContainer = styled.div``; - -const DarkMatter = styled.div<{ antiHeight?: string }>` - width: 100%; - margin-top: ${(props) => props.antiHeight || "-15px"}; -`; - -const Subtitle = styled.div` - padding: 11px 0px 16px; - font-family: "Work Sans", sans-serif; - font-size: 13px; - color: #aaaabb; - line-height: 1.6em; -`; - -const StyledSourceBox = styled.div` - width: 100%; - color: #ffffff; - padding: 14px 35px 20px; - position: relative; - font-size: 13px; - margin-top: 6px; - margin-bottom: 25px; - border-radius: 5px; - background: ${(props) => props.theme.fg}; - border: 1px solid #494b4f; -`; diff --git a/dashboard/src/main/home/app-dashboard/new-app-flow/schema.tsx b/dashboard/src/main/home/app-dashboard/new-app-flow/schema.tsx deleted file mode 100644 index 83f25501a5..0000000000 --- a/dashboard/src/main/home/app-dashboard/new-app-flow/schema.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray"; -import * as z from "zod"; -import { JobService, ReleaseService, Service, WebService, WorkerService } from "./serviceTypes"; -import { overrideObjectValues } from "./utils"; -import _ from "lodash"; - -const appConfigSchema = z.object({ - run: z.string().min(1), - config: z.any().optional(), - type: z.enum(['web', 'worker', 'job']).optional(), -}); - -export const AppsSchema = z.record(appConfigSchema); - -export const EnvSchema = z.record(z.string()); - -export const BuildSchema = z.object({ - method: z.string().refine(value => ["pack", "docker", "registry"].includes(value)), - context: z.string().optional(), - builder: z.string().optional(), - buildpacks: z.array(z.string()).optional(), - dockerfile: z.string().optional(), - image: z.string().optional() -}).refine(value => { - if (value.method === "pack") { - return value.builder != null; - } - if (value.method === "docker") { - return value.dockerfile != null; - } - if (value.method === "registry") { - return value.image != null; - } - return false; -}, - { message: "Invalid build configuration" }); - - -export const PorterYamlSchema = z.object({ - version: z.string().optional(), - build: BuildSchema.optional(), - env: EnvSchema.optional(), - apps: AppsSchema, - release: appConfigSchema.optional(), -}); - -export const createFinalPorterYaml = ( - services: Service[], - dashboardSetEnvVariables: KeyValueType[], - porterJson: PorterJson | undefined, - injectPortEnvVariable: boolean = false, -): PorterJson => { - const [apps, port] = createApps(services.filter(Service.isNonRelease), porterJson, injectPortEnvVariable); - const env = combineEnv(dashboardSetEnvVariables, porterJson?.env); - - // inject a port env variable if necessary - if (port != null) { - env.PORT = port; - } - - const release = services.find(Service.isRelease); - - return release != null && !_.isEmpty(release.startCommand.value) ? { - version: "v1stack", - env, - apps, - release: createRelease(release, porterJson), - } : { - version: "v1stack", - env, - apps, - }; -}; - -const combineEnv = ( - dashboardSetVariables: KeyValueType[], - porterYamlSetVariables: Record | undefined -): z.infer => { - const env: z.infer = {}; - for (const { key, value } of dashboardSetVariables) { - env[key] = value; - } - if (porterYamlSetVariables != null) { - for (const [key, value] of Object.entries(porterYamlSetVariables)) { - env[key] = value; - } - } - return env; -}; - -const createApps = ( - serviceList: (WorkerService | WebService | JobService)[], - porterJson: PorterJson | undefined, - injectPortEnvVariable: boolean, -): [z.infer, string | undefined] => { - const apps: z.infer = {}; - let port: string | undefined = undefined; - for (const service of serviceList) { - let config = Service.serialize(service); - - if ( - porterJson != null && - porterJson.apps != null && - porterJson.apps[service.name] != null && - porterJson.apps[service.name].config != null - ) { - config = overrideObjectValues( - config, - porterJson.apps[service.name].config - ); - } - - if (injectPortEnvVariable && service.type === "web") { - port = service.port.value; - } - - apps[service.name] = { - type: service.type, - run: service.startCommand.value, - config, - }; - } - - return [apps, port]; -}; - -const createRelease = (release: ReleaseService, porterJson: PorterJson | undefined): z.infer => { - let config = Service.serialize(release); - - if (porterJson?.release?.config != null) { - config = overrideObjectValues( - config, - porterJson.release.config - ); - } - - return { - type: 'job', - run: release.startCommand.value, - config, - } -} - -export type PorterJson = z.infer; diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/ExpandableEnvGroup.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/ExpandableEnvGroup.tsx deleted file mode 100644 index 64b97a6d98..0000000000 --- a/dashboard/src/main/home/app-dashboard/validate-apply/app-settings/ExpandableEnvGroup.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import React, { useState } from "react"; -import styled, { keyframes } from "styled-components"; -import { type PopulatedEnvGroup } from "./types"; -import Spacer from "components/porter/Spacer"; -import Icon from "components/porter/Icon"; - -type Props = { - index: number; - remove: (index: number) => void; - envGroup: PopulatedEnvGroup; - icon: JSX.Element; -}; - -const ExpandableEnvGroup: React.FC = ({ index, remove, envGroup, icon }) => { - const [isExpanded, setIsExpanded] = useState(false); - - return ( - - - {icon} - - - - {envGroup.name} - - - - { remove(index); }}> - delete - - { setIsExpanded((prev) => !prev); }} - > - - {isExpanded ? "arrow_drop_up" : "arrow_drop_down"} - - - - - {isExpanded ? ( - <> - {Object.entries(envGroup.variables ?? {}).map(([key, value], i) => ( - - - - - - ))} - {Object.entries(envGroup.secret_variables ?? {}).map( - ([key, value], i) => ( - - - - - - ) - )} - - ) : null} - - ); -}; - -export default ExpandableEnvGroup; - -const fadeIn = keyframes` - from { - opacity: 0; - } - to { - opacity: 1; - } -`; - -const StyledCard = styled.div` - border: 1px solid #ffffff44; - background: #ffffff11; - border-radius: 8px; - padding: 10px 14px; - overflow: hidden; - font-size: 13px; - animation: ${fadeIn} 0.5s; -`; - -const Flex = styled.div` - display: flex; - height: 25px; - align-items: center; - justify-content: space-between; -`; - -const ContentContainer = styled.div` - display: flex; - height: 40px; - width: 100%; - align-items: center; -`; - -const EventInformation = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const EventName = styled.div` - font-family: "Work Sans", sans-serif; - font-weight: 500; - color: #ffffff; -`; - -const ActionContainer = styled.div` - display: flex; - align-items: center; - white-space: nowrap; - height: 100%; -`; - -const ActionButton = styled.button` - position: relative; - border: none; - background: none; - color: white; - padding: 5px; - width: 30px; - height: 30px; - margin-left: 5px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - cursor: pointer; - color: #aaaabb; - border: 1px solid #ffffff00; - - :hover { - background: #ffffff11; - border: 1px solid #ffffff44; - } - - > span { - font-size: 20px; - } -`; - -const InputWrapper = styled.div` - display: flex; - align-items: center; - margin-top: 5px; -`; - -type InputProps = { - disabled?: boolean; - width: string; - borderColor?: string; -}; - -const KeyInput = styled.input` - outline: none; - border: none; - margin-bottom: 5px; - font-size: 13px; - background: #ffffff11; - border: 1px solid - ${(props) => (props.borderColor ? props.borderColor : "#ffffff55")}; - border-radius: 3px; - width: ${(props) => (props.width ? props.width : "270px")}; - color: ${(props) => (props.disabled ? "#ffffff44" : "white")}; - padding: 5px 10px; - height: 35px; -`; - -export const MultiLineInput = styled.textarea` - outline: none; - border: none; - margin-bottom: 5px; - font-size: 13px; - background: #ffffff11; - border: 1px solid - ${(props) => (props.borderColor ? props.borderColor : "#ffffff55")}; - border-radius: 3px; - min-width: ${(props) => (props.width ? props.width : "270px")}; - max-width: ${(props) => (props.width ? props.width : "270px")}; - color: ${(props) => (props.disabled ? "#ffffff44" : "white")}; - padding: 8px 10px 5px 10px; - min-height: 35px; - max-height: 100px; - white-space: nowrap; - - ::-webkit-scrollbar { - width: 8px; - :horizontal { - height: 8px; - } - } - - ::-webkit-scrollbar-corner { - width: 10px; - background: #ffffff11; - color: white; - } - - ::-webkit-scrollbar-track { - width: 10px; - -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); - } - - ::-webkit-scrollbar-thumb { - background-color: darkgrey; - outline: 1px solid slategrey; - } -`; diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Old_GPUResources.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Old_GPUResources.tsx deleted file mode 100644 index 0bebacb94a..0000000000 --- a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Old_GPUResources.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React, { useContext, useState } from "react"; -import { Switch } from "@material-ui/core"; -import { Controller, useFormContext } from "react-hook-form"; -import styled from "styled-components"; - -import Loading from "components/Loading"; -import Container from "components/porter/Container"; -import InputSlider from "components/porter/InputSlider"; -import Link from "components/porter/Link"; -import Spacer from "components/porter/Spacer"; -import Tag from "components/porter/Tag"; -import Text from "components/porter/Text"; -import ProvisionClusterModal from "main/home/sidebar/ProvisionClusterModal"; -import { type PorterAppFormData } from "lib/porter-apps"; - -import { Context } from "shared/Context"; -import addCircle from "assets/add-circle.png"; -import infra from "assets/cluster.svg"; - -type Props = { - clusterContainsGPUNodes: boolean; - maxGPU: number; - index: number; -}; - -// TODO: delete this file and all references once new infra tab is GA -const OldGPUResources: React.FC = ({ - clusterContainsGPUNodes, - maxGPU, - index, -}) => { - const { currentCluster } = useContext(Context); - const [clusterModalVisible, setClusterModalVisible] = - useState(false); - - const { control, watch } = useFormContext(); - const gpu = watch(`app.services.${index}.gpu.enabled`, { - readOnly: false, - value: false, - }); - return ( - <> - - ( - <> - - { - onChange({ - ...value, - enabled: { - ...value.enabled, - value: !value.enabled.value, - }, - gpuCoresNvidia: { - ...value.gpuCoresNvidia, - value: value.enabled.value ? 0 : 1, - }, - }); - }} - inputProps={{ "aria-label": "controlled" }} - /> - - - <> - Enable GPU - - - - {!clusterContainsGPUNodes && ( - <> - - - Your cluster has no GPU nodes available. - - - {currentCluster?.status !== "UPDATING" && ( - - { - setClusterModalVisible(true); - }} - > - - Add GPU nodes - - - )} - - )} - - - - {clusterModalVisible && ( - { - setClusterModalVisible(false); - }} - gpuModal={true} - gcp={currentCluster?.cloud_provider === "GCP"} - azure={currentCluster?.cloud_provider === "Azure"} - /> - )} - - )} - /> - {maxGPU > 1 && gpu.value && ( - <> - - ( - { - onChange({ - ...value, - gpuCoresNvidia: { - ...value.gpuCoresNvidia, - value: e, - }, - }); - }} - disabledTooltip={ - "You may only edit this field in your porter.yaml." - } - /> - )} - /> - - )} - {currentCluster?.status === "UPDATING" && !clusterContainsGPUNodes && ( - - - - - {"Cluster is updating..."} - - - - - View Status - - - - - )} - - ); -}; - -export default OldGPUResources; - -const CheckItemContainer = styled.div` - display: flex; - flex-direction: column; - border: 1px solid ${(props) => props.theme.border}; - border-radius: 5px; - font-size: 13px; - width: 100%; - margin-bottom: 10px; - padding-left: 10px; - background: ${(props) => props.theme.clickable.bg}; -`; - -const CheckItemTop = styled.div` - display: flex; - align-items: center; - padding: 10px; - background: ${(props) => props.theme.clickable.bg}; -`; - -const TagIcon = styled.img` - height: 12px; - margin-right: 3px; -`; diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/utils.ts b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/utils.ts deleted file mode 100644 index 30e64fcbab..0000000000 --- a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/utils.ts +++ /dev/null @@ -1,181 +0,0 @@ -export const MIB_TO_GIB = 1024; -export const MILI_TO_CORE = 1000; -type InstanceDetails = { - vCPU: number; - RAM: number; - GPU?: number; -}; - -type InstanceTypes = Record>; -type AzureInstanceTypes = Record; - -// use values from AWS as base constant, convert to MB -export const AWS_INSTANCE_LIMITS: InstanceTypes = Object.freeze({ - t3a: { - nano: { vCPU: 2, RAM: 0.5 }, - micro: { vCPU: 2, RAM: 1 }, - small: { vCPU: 2, RAM: 2 }, - medium: { vCPU: 2, RAM: 4 }, - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - }, - t3: { - nano: { vCPU: 2, RAM: 0.5 }, - micro: { vCPU: 2, RAM: 1 }, - small: { vCPU: 2, RAM: 2 }, - medium: { vCPU: 2, RAM: 4 }, - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - }, - t2: { - nano: { vCPU: 1, RAM: 0.5 }, - micro: { vCPU: 1, RAM: 1 }, - small: { vCPU: 1, RAM: 2 }, - medium: { vCPU: 2, RAM: 4 }, - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - }, - t4g: { - nano: { vCPU: 2, RAM: 0.5 }, - micro: { vCPU: 2, RAM: 1 }, - small: { vCPU: 2, RAM: 2 }, - medium: { vCPU: 2, RAM: 4 }, - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - }, - c6a: { - large: { vCPU: 2, RAM: 4 }, - xlarge: { vCPU: 4, RAM: 8 }, - "2xlarge": { vCPU: 8, RAM: 16 }, - "4xlarge": { vCPU: 16, RAM: 32 }, - "8xlarge": { vCPU: 32, RAM: 64 }, - }, - c6i: { - large: { vCPU: 2, RAM: 4 }, - xlarge: { vCPU: 4, RAM: 8 }, - "2xlarge": { vCPU: 8, RAM: 16 }, - "4xlarge": { vCPU: 16, RAM: 32 }, - "8xlarge": { vCPU: 32, RAM: 64 }, - "12xlarge": { vCPU: 48, RAM: 96 }, - }, - g4dn: { - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - "8xlarge": { vCPU: 32, RAM: 128 }, - }, - r6a: { - large: { vCPU: 2, RAM: 16 }, - xlarge: { vCPU: 4, RAM: 32 }, - "2xlarge": { vCPU: 8, RAM: 64 }, - "4xlarge": { vCPU: 16, RAM: 128 }, - "8xlarge": { vCPU: 32, RAM: 256 }, - }, - c5: { - large: { vCPU: 2, RAM: 4 }, - xlarge: { vCPU: 4, RAM: 8 }, - "2xlarge": { vCPU: 8, RAM: 16 }, - "4xlarge": { vCPU: 16, RAM: 32 }, - }, - m5: { - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - }, - m5n: { - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - }, - m6a: { - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - "8xlarge": { vCPU: 32, RAM: 128 }, - "12xlarge": { vCPU: 48, RAM: 192 }, - }, - m7i: { - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - "8xlarge": { vCPU: 32, RAM: 128 }, - "12xlarge": { vCPU: 48, RAM: 192 }, - }, - x2gd: { - medium: { vCPU: 1, RAM: 16 }, - large: { vCPU: 2, RAM: 32 }, - xlarge: { vCPU: 4, RAM: 64 }, - }, - m5n: { - large: { vCPU: 2, RAM: 8 }, - xlarge: { vCPU: 4, RAM: 16 }, - "2xlarge": { vCPU: 8, RAM: 32 }, - "4xlarge": { vCPU: 16, RAM: 64 }, - }, - // add GCP instance tyoes : TO DO add a dedicated section for GCP - e2: { - "standard-2": { vCPU: 2, RAM: 8 }, - "standard-4": { vCPU: 4, RAM: 16 }, - "standard-8": { vCPU: 8, RAM: 32 }, - "standard-16": { vCPU: 16, RAM: 64 }, - "standard-32": { vCPU: 32, RAM: 128 }, - "standard-64": { vCPU: 64, RAM: 256 }, - }, - c3: { - "highcpu-4": { vCPU: 4, RAM: 8 }, - "highcpu-8": { vCPU: 8, RAM: 16 }, - "highcpu-22": { vCPU: 22, RAM: 44 }, - "highcpu-44": { vCPU: 44, RAM: 88 }, - "highmem-4": { vCPU: 4, RAM: 32 }, - "highmem-8": { vCPU: 8, RAM: 64 }, - "highmem-22": { vCPU: 22, RAM: 176 }, - "highmem-44": { vCPU: 44, RAM: 352 }, - "standard-4": { vCPU: 4, RAM: 16 }, - "standard-8": { vCPU: 8, RAM: 32 }, - "standard-22": { vCPU: 22, RAM: 88 }, - "standard-44": { vCPU: 44, RAM: 176 }, - }, - c7g: { - large: { vCPU: 2, RAM: 4 }, - xlarge: { vCPU: 4, RAM: 8 }, - "2xlarge": { vCPU: 8, RAM: 16 }, - "4xlarge": { vCPU: 16, RAM: 32 }, - "8xlarge": { vCPU: 32, RAM: 64 }, - "12xlarge": { vCPU: 48, RAM: 96 }, - "16xlarge": { vCPU: 64, RAM: 128 }, - }, -}); - -export const GPU_INSTANCE_LIMIT: InstanceTypes = Object.freeze({ - g4dn: { - xlarge: { vCPU: 4, RAM: 16, GPU: 1 }, - "2xlarge": { vCPU: 8, RAM: 32, GPU: 1 }, - }, - p4d: { - "24xlarge": { vCPU: 96, RAM: 1152, GPU: 8 }, - }, - n1: { - "standard-1": { vCPU: 1, RAM: 3.75, GPU: 1 }, - "standard-2": { vCPU: 2, RAM: 7.5, GPU: 1 }, - "standard-4": { vCPU: 4, RAM: 15, GPU: 1 }, - "standard-8": { vCPU: 8, RAM: 30, GPU: 1 }, - "standard-16": { vCPU: 16, RAM: 60, GPU: 1 }, - "standard-32": { vCPU: 32, RAM: 120, GPU: 1 }, - "high-mem-2": { vCPU: 2, RAM: 13, GPU: 1 }, - "high-mem-4": { vCPU: 4, RAM: 26, GPU: 1 }, - "high-mem-8": { vCPU: 8, RAM: 52, GPU: 1 }, - "high-mem-16": { vCPU: 16, RAM: 104, GPU: 1 }, - "high-mem-32": { vCPU: 32, RAM: 208, GPU: 1 }, - "high-cpu-8": { vCPU: 2, RAM: 1.8, GPU: 1 }, - "high-cpu-16": { vCPU: 4, RAM: 3.6, GPU: 1 }, - "high-cpu-32": { vCPU: 8, RAM: 7.2, GPU: 1 }, - }, -}); diff --git a/dashboard/src/main/home/cluster-dashboard/ClusterPlaceholderContainer.tsx b/dashboard/src/main/home/cluster-dashboard/ClusterPlaceholderContainer.tsx deleted file mode 100644 index 9886e5ccc4..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/ClusterPlaceholderContainer.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { useContext } from "react"; - -import { Context } from "shared/Context"; -import ClusterPlaceholder from "./ClusterPlaceholder"; - -type PropsType = {}; - -const ClusterPlaceholderContainer: React.FC = () => { - const context = useContext(Context); - - return ; -} - -export default ClusterPlaceholderContainer; diff --git a/dashboard/src/main/home/cluster-dashboard/dashboard/Compliance.tsx b/dashboard/src/main/home/cluster-dashboard/dashboard/Compliance.tsx deleted file mode 100644 index 85554b0716..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/dashboard/Compliance.tsx +++ /dev/null @@ -1,408 +0,0 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; -import type { JsonValue } from "@bufbuild/protobuf"; -import { Cluster, Contract, EKS, EKSLogging } from "@porter-dev/api-contracts"; -import axios from "axios"; -import styled from "styled-components"; -import { match } from "ts-pattern"; - -import Loading from "components/Loading"; -import Button from "components/porter/Button"; -import Container from "components/porter/Container"; -import Error from "components/porter/Error"; -import Fieldset from "components/porter/Fieldset"; -import Spacer from "components/porter/Spacer"; -import Text from "components/porter/Text"; -import ToggleRow from "components/porter/ToggleRow"; -import SOC2Checks from "components/SOC2Checks"; - -import api from "shared/api"; -import { Context } from "shared/Context"; -import { type Soc2Data } from "shared/types"; -import sparkle from "assets/sparkle.svg"; - -import DonutChart from "./DonutChart"; - -type Props = { - credentialId: string; - provisionerError?: string; - selectedClusterVersion: JsonValue; -}; - -// Example SOC2 Check NOTE PLEASE ADD FUNC TO createContract and useEffect(() to correctly READ AND WRITE -// "Display_Name_Of_SOC2_Check": { -// "message": "Main Example Message about the Check", -// "link": "example link for more docs ", -// "enabled": (false or true if porter does it by default), -// "status": ""(Keep blank or ENABLED if porter does it by default) -// "enabledField": "text that goes next to the toggle", -// "info": " more information", -// "locked":(true if unmutable field like KMS), -// "disabledTooltip": "display if message is disabled", -// "hideToggle": true (if you want to hide the toggle -// } -const soc2DataDefault: Soc2Data = { - soc2_checks: { - "Public SSH Access": { - message: - "Porter-provisioned instances do not allow remote SSH access. Users are not allowed to invoke commands directly on the host, and all commands are invoked via the EKS Control Plane.", - enabled: true, - hideToggle: true, - status: "ENABLED", - }, - "Cluster Secret Encryption": { - message: - "Cluster secrets can be encrypted using an AWS KMS Key. Secrets will be encrypted at rest, and encryption cannot be disabled for secrets.", - enabled: false, - disabledTooltip: - "Enable KMS encryption for the cluster to enable SOC 2 compliance.", - link: "https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-eks-adds-envelope-encryption-for-secrets-with-aws-kms/", - locked: true, - status: "", - }, - "Control Plane Log Retention": { - message: - "EKS Control Plane logs are by default available for a minimal amount of time, typically 1 hour or less. EKS CloudTrail Forwarding automatically sends control plane logs to CloudTrail for longer retention and later inspection.", - enabled: false, - enabledField: "Retain CloudTrail logs for 365 days", - status: "", - }, - "Enhanced Image Vulnerability Scanning": { - message: - "AWS ECR scans for CVEs from the open-source Clair database on image push. Enhanced scanning provides continuous, automated scans against images as new vulnerabilities appear.", - link: "https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning-enhanced.html", - enabled: false, - info: "", - status: "", - }, - }, -}; - -const DEFAULT_ERROR_MESSAGE = - "An error occurred while provisioning your infrastructure. Please try again."; - -const Compliance: React.FC = (props) => { - const { currentProject, currentCluster, setShouldRefreshClusters } = - useContext(Context); - - const [isClicked, setIsClicked] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [soc2Enabled, setSoc2Enabled] = useState(false); - const [clusterRegion, setClusterRegion] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); - const [errorDetails, setErrorDetails] = useState(""); - const [soc2Data, setSoc2Data] = useState(soc2DataDefault); - const [isReadOnly, setIsReadOnly] = useState(false); - - const applySettings = async (): Promise => { - if (!currentCluster || !currentProject || !setShouldRefreshClusters) { - return; - } - - try { - setIsLoading(true); - setIsClicked(true); - setIsReadOnly(true); - - const contractResults = await api.getContracts( - "", - { cluster_id: currentCluster.id }, - { project_id: currentProject.id } - ); - - if (contractResults.data.length === 0) { - setErrorMessage("Unable to retrieve contract results"); - setErrorDetails(""); - return; - } - - const result = contractResults.data.reduce( - (prev: { CreatedAt: string }, current: { CreatedAt: string }) => - Date.parse(current.CreatedAt) > Date.parse(prev.CreatedAt) - ? current - : prev - ); - - const contract = createContract(result.base64_contract); - - await api.createContract("", contract, { - project_id: currentProject.id, - }); - setShouldRefreshClusters(true); - - setIsClicked(false); - setIsLoading(false); - setIsReadOnly(false); - } catch (err) { - let errMessage = - "Failed to provision cluster, please contact support@porter.run."; - if (axios.isAxiosError(err) && err.response?.data) { - errMessage = err.response.data.error.replace("unknown: ", ""); - } - - // hacky, need to standardize error contract with backend - setIsClicked(false); - setIsLoading(false); - void markStepStarted("provisioning-failed", errMessage); - - // enable edit again only in the case of an error - setIsClicked(false); - setIsReadOnly(false); - } finally { - setIsLoading(false); - } - }; - - const createContract = (base64Contract: string): Contract => { - // - const cloudTrailEnabled = - soc2Data.soc2_checks["Control Plane Log Retention"].enabled; - const kmsEnabled = - soc2Data.soc2_checks["Cluster Secret Encryption"].enabled; - const ecrScanningEnabled = - soc2Data.soc2_checks["Enhanced Image Vulnerability Scanning"].enabled; - - const contractData = JSON.parse(atob(base64Contract)); - const latestCluster: Cluster = Cluster.fromJson(contractData.cluster, { - ignoreUnknownFields: true, - }); - - const updatedKindValues = match(latestCluster.kindValues) - .with({ case: "eksKind" }, ({ value }) => ({ - value: new EKS({ - ...value, - enableKmsEncryption: soc2Enabled || kmsEnabled || false, - enableEcrScanning: - soc2Enabled || - ecrScanningEnabled || - value.enableEcrScanning || - false, - logging: new EKSLogging({ - enableApiServerLogs: soc2Enabled || cloudTrailEnabled || false, - enableAuditLogs: soc2Enabled || cloudTrailEnabled || false, - enableAuthenticatorLogs: soc2Enabled || cloudTrailEnabled || false, - enableCloudwatchLogsToS3: soc2Enabled || cloudTrailEnabled || false, - enableControllerManagerLogs: - soc2Enabled || cloudTrailEnabled || false, - enableSchedulerLogs: soc2Enabled || cloudTrailEnabled || false, - }), - }), - case: "eksKind" as const, - })) - .with({ case: "gkeKind" }, ({ value }) => ({ - value, - case: "gkeKind" as const, - })) - .with({ case: "aksKind" }, ({ value }) => ({ - value, - case: "aksKind" as const, - })) - .with({ case: undefined }, () => ({ - value: undefined, - case: undefined, - })) - .exhaustive(); - const cluster = new Cluster({ - ...latestCluster, - kindValues: updatedKindValues, - }); - - return new Contract({ - cluster, - }); - }; - - const getStatus = (): JSX.Element | string => { - if (isLoading) { - return ; - } - if (isReadOnly && props.provisionerError === "") { - return "Provisioning is still in progress..."; - } else if (errorMessage !== "") { - return ( - - ); - } - return ""; - }; - - const isDisabled = (): boolean | undefined => { - return ( - isUserProvisioning || - isClicked || - (currentCluster && !currentProject?.enable_reprovision) - ); - }; - - const markStepStarted = async ( - step: string, - errMessage?: string - ): Promise => { - try { - await api.updateOnboardingStep( - "", - { - step, - error_message: errMessage, - region: clusterRegion, - provider: "aws", - }, - { - project_id: currentProject ? currentProject.id : 0, - } - ); - } catch (err) {} - }; - - const isUserProvisioning = useMemo(() => { - return isReadOnly && props.provisionerError === ""; - }, [isReadOnly, props.provisionerError]); - - const determineStatus = (enabled: boolean): string => { - if (enabled) { - if (currentCluster?.status === "UPDATING") { - return "PENDING_ENABLED"; - } else return "ENABLED"; - } - return ""; - }; - - useEffect(() => { - const contract: Contract = Contract.fromJson(props.selectedClusterVersion, { - ignoreUnknownFields: true, - }); - - if (contract.cluster && contract.cluster.kindValues.case === "eksKind") { - const eksValues = contract.cluster.kindValues.value; - const cloudTrailEnabled = - eksValues.logging != null && - eksValues.logging.enableApiServerLogs && - eksValues.logging.enableAuditLogs && - eksValues.logging.enableAuthenticatorLogs && - eksValues.logging.enableControllerManagerLogs; - - setClusterRegion(eksValues.region); - - setSoc2Data((prevSoc2Data) => { - return { - ...prevSoc2Data, - soc2_checks: { - ...prevSoc2Data.soc2_checks, - "Control Plane Log Retention": { - ...prevSoc2Data.soc2_checks["Control Plane Log Retention"], - enabled: cloudTrailEnabled, - status: determineStatus(cloudTrailEnabled), - }, - "Cluster Secret Encryption": { - ...prevSoc2Data.soc2_checks["Cluster Secret Encryption"], - enabled: eksValues.enableKmsEncryption, - status: determineStatus(eksValues.enableKmsEncryption), - }, - "Enhanced Image Vulnerability Scanning": { - ...prevSoc2Data.soc2_checks[ - "Enhanced Image Vulnerability Scanning" - ], - enabled: eksValues.enableEcrScanning, - status: determineStatus(eksValues.enableEcrScanning), - }, - }, - }; - }); - - setSoc2Enabled( - cloudTrailEnabled && - eksValues.enableKmsEncryption && - eksValues.enableEcrScanning - ); - } - }, [props.selectedClusterVersion]); - - useEffect(() => { - if (!currentCluster) { - return; - } - - setIsReadOnly( - currentCluster.status === "UPDATING" || - currentCluster.status === "UPDATING_UNAVAILABLE" - ); - }, []); - - return ( - - -
- - SOC 2 Compliance Dashboard - - - - New - - - - -
- - - - - - - { - setSoc2Enabled((prev) => !prev); - }} - disabled={isReadOnly} - disabledTooltip={ - "Wait for provisioning to complete before editing this field." - } - > - - Enable All - - - -
- ); -}; - -export default Compliance; - -const StyledCompliance = styled.div``; - -const NewBadge = styled.div` - font-size: 13px; - padding: 5px 10px; - background: linear-gradient(110deg, #b6d5f2, #6836e2); - border-radius: 5px; - display: flex; - align-items: center; - - > img { - height: 14px; - margin-right: 5px; - } -`; diff --git a/dashboard/src/main/home/cluster-dashboard/dashboard/DonutChart.tsx b/dashboard/src/main/home/cluster-dashboard/dashboard/DonutChart.tsx deleted file mode 100644 index a11df88896..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/dashboard/DonutChart.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { ArcElement, CategoryScale, Chart, Legend, Tooltip } from "chart.js"; -import { Doughnut } from "react-chartjs-2"; - -import Container from "components/porter/Container"; -import Spacer from "components/porter/Spacer"; -import { type Soc2Check } from "shared/types"; - -Chart.register(ArcElement, Tooltip, Legend, CategoryScale); - -type DonutChartProps = { - data: Soc2Check; -}; - -const DonutChart: React.FC = ({ data }) => { - const [chartDataValues, setChartDataValues] = useState([0, 0, 0]); - - useEffect(() => { - const counts = { ENABLED: 0, DISABLED: 0, PENDING: 0 }; - - Object.values(data.soc2_checks).forEach((check) => { - let status = check.status || "DISABLED"; - if (status.includes("PENDING")) { - status = "PENDING"; - } - counts[status.toUpperCase()]++; - }); - - setChartDataValues([counts.ENABLED, counts.DISABLED, counts.PENDING]); - }, [data]); // Dependency array ensures this runs only when `data` changes - - const chartData = { - labels: ["Enabled", "Disabled", "Pending"], - datasets: [ - { - data: chartDataValues, - backgroundColor: ["#5eaa7d", "#e34040", "rgb(255, 205, 86)"], - borderColor: "#171b21", - borderWidth: 2, - hoverBorderColor: "#171b21", - hoverBorderWidth: 3, - borderJoinStyle: "round", - hoverBorderJoinStyle: "bevel", - }, - ], - }; - - const options = { - plugins: { - legend: false, - tooltip: {}, - }, - elements: { - arc: { - borderWidth: 3, - borderColor: "#fff", - borderAlign: "inner", - hoverOffset: 1, - }, - }, - responsive: true, - maintainAspectRatio: false, - }; - - const textCenter = { - id: "textCenter", - afterDatasetsDraw: (chart: unknown) => { - const { ctx, data } = chart; - ctx.save(); - ctx.font = "15px sans-serif"; - ctx.fillStyle = "#fff"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - - // Calculate the total - const total = data.datasets[0].data.reduce((a, b) => a + b, 0); - - // Coordinates for the text - const x = chart.getDatasetMeta(0).data[0].x; - const y = chart.getDatasetMeta(0).data[0].y; - - // Draw the first line of text - ctx.fillText(`${data.datasets[0].data[0]} / ${total}`, x, y - 10); // Adjust Y position as needed - - // Draw the second line of text - ctx.fillText(`checks enabled`, x, y + 10); // Adjust Y position as needed - - ctx.restore(); - }, - }; - - const CustomLegend = (): JSX.Element => ( -
- {chartData.datasets[0].backgroundColor.map((color, index) => ( -
- - {chartData.labels[index]} -
- ))} -
- ); - - return ( - <> - - -
- -
- - -
- - ); -}; - -export default DonutChart; diff --git a/dashboard/src/main/home/cluster-dashboard/dashboard/PorterAppDashboard.tsx b/dashboard/src/main/home/cluster-dashboard/dashboard/PorterAppDashboard.tsx deleted file mode 100644 index 8c02ae839d..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/dashboard/PorterAppDashboard.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import React, { useState, useContext, useEffect } from "react"; -import styled from "styled-components"; -import { RouteComponentProps, withRouter } from "react-router"; - -import gradient from "assets/gradient.png"; - -import { Context } from "shared/Context"; -import { InfraType } from "shared/types"; -import api from "shared/api"; -import { pushFiltered, pushQueryParams } from "shared/routing"; -import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc"; - -import ProvisionerSettings from "../provisioner/ProvisionerSettings"; -import ClusterPlaceholderContainer from "../ClusterPlaceholderContainer"; -import TabRegion from "components/TabRegion"; -import FormDebugger from "components/porter-form/FormDebugger"; -import TitleSection from "components/TitleSection"; -import ClusterSection from "../dashboard/ClusterSection"; -import { StatusPage } from "../onboarding/steps/ProvisionResources/forms/StatusPage"; -import Banner from "components/porter/Banner"; -import Spacer from "components/porter/Spacer"; - -type Props = RouteComponentProps & WithAuthProps & { - projectId: number | null; - setRefreshClusters: (x: boolean) => void; -}; - -const PorterAppDashboard: React.FC = ({ - projectId, - setRefreshClusters, - ...props -}) => { - const { currentProject, user, capabilities, usage } = useContext(Context); - const [infras, setInfras] = useState([]); - const [pressingCtrl, setPressingCtrl] = useState(false); - const [pressingK, setPressingK] = useState(false); - const [showFormDebugger, setShowFormDebugger] = useState(false); - const [tabOptions, setTabOptions] = useState([{ - label: "Connected clusters", - value: "overview" - }]); - - const handleKeyDown = (e: KeyboardEvent): void => { - if (e.key === "k") { - setPressingK(true); - } - if (e.key === "Meta" || e.key === "Control") { - setPressingCtrl(true); - } - if (e.key === "z" && pressingK && pressingCtrl) { - setPressingK(false); - setPressingCtrl(false); - setShowFormDebugger(!showFormDebugger); - } - }; - - const handleKeyUp = (e: KeyboardEvent): void => { - if (e.key === "Meta" || e.key === "Control" || e.key === "k") { - setPressingK(false); - setPressingCtrl(false); - } - }; - - useEffect(() => { - document.addEventListener("keydown", handleKeyDown); - document.addEventListener("keyup", handleKeyUp); - return () => { - document.removeEventListener("keydown", handleKeyDown); - document.removeEventListener("keyup", handleKeyUp); - }; - }, []); - - useEffect(() => { - if (currentProject) { - if (currentProject.simplified_view_enabled) { - pushFiltered(props, "/apps", ["project_id"]); - } - api - .getInfra( - "", - {}, - { - project_id: currentProject.id, - } - ) - .then((res) => setInfras(res.data)) - .catch(console.log); - } - }, [currentProject]); - - const currentTab = () => new URLSearchParams(props.location.search).get("tab") || "overview"; - - useEffect(() => { - if (usage && usage?.current?.clusters < usage?.limit?.clusters) { - tabOptions.push({ label: "Create a cluster", value: "create-cluster" }); - } - - tabOptions.push({ label: "Provisioner status", value: "provisioner" }); - - if (!capabilities?.provisioner) { - let newTabs = [{ label: "Project overview", value: "overview" }]; - setTabOptions(newTabs); - } else { - setTabOptions(tabOptions); - } - }, [currentProject]); - - const renderTabContents = () => { - - return ; - }; - - return ( - <> - {currentProject && ( - - {showFormDebugger ? ( - setShowFormDebugger(false)} - /> - ) : ( - <> - - {/* - - - {currentProject && currentProject.name[0].toUpperCase()} - - - {currentProject && currentProject.name} - {currentProject?.roles?.filter((obj: any) => { - return obj.user_id === user.userId; - })[0].kind === "admin" || ( - { - pushFiltered(props, "/project-settings", ["project_id"]); - }} - > - more_vert - - )} */} - Select Cluster - - - { - - // { - // pushQueryParams(props, { tab: x }); - // }} - // options={tabOptions} - // > - // {renderTabContents()} - // - - } - - )} - - )} - - ); -}; - -export default withRouter(withAuth(PorterAppDashboard)); - -const Br = styled.div` - width: 100%; - height: 1px; -`; - -const DashboardWrapper = styled.div` - padding-bottom: 100px; -`; - -const TopRow = styled.div` - display: flex; - align-items: center; -`; - -const Description = styled.div` - color: #aaaabb; - margin-top: 13px; - margin-left: 2px; - font-size: 13px; -`; - -const InfoLabel = styled.div` - width: 72px; - height: 20px; - display: flex; - align-items: center; - color: #aaaabb; - font-size: 13px; - > i { - color: #aaaabb; - font-size: 18px; - margin-right: 5px; - } -`; - -const InfoSection = styled.div` - margin-top: 20px; - font-family: "Work Sans", sans-serif; - margin-left: 0px; - margin-bottom: 30px; -`; - -const Overlay = styled.div` - height: 100%; - width: 100%; - position: absolute; - top: 0; - left: 0; - border-radius: 5px; - display: flex; - align-items: center; - justify-content: center; - font-size: 21px; - font-weight: 500; - font-family: "Work Sans", sans-serif; - color: white; -`; - -const DashboardImage = styled.img` - height: 35px; - width: 35px; - border-radius: 5px; -`; - -const DashboardIcon = styled.div` - position: relative; - height: 35px; - margin-right: 17px; - width: 35px; - border-radius: 5px; - display: flex; - align-items: center; - justify-content: center; - - > i { - font-size: 22px; - } -`; diff --git a/dashboard/src/main/home/cluster-dashboard/expanded-chart/DeploymentTypeStacks.tsx b/dashboard/src/main/home/cluster-dashboard/expanded-chart/DeploymentTypeStacks.tsx deleted file mode 100644 index a3b55726b9..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/expanded-chart/DeploymentTypeStacks.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useState } from "react"; -import styled from "styled-components"; - -import { integrationList } from "shared/common"; -import { ChartType } from "shared/types"; - -type Props = { - appData: any; -}; - -const DeploymentTypeStacks: React.FC = ({ appData }) => { - const [showRepoTooltip, setShowRepoTooltip] = useState(false); - - const githubRepository = appData?.app.repo_name; - const icon = githubRepository - ? integrationList.repo.icon - : integrationList.registry.icon; - - const repository = - githubRepository || - appData.cluster?.image_repo_uri || - appData.cluster?.config?.image?.repository; - - if (repository?.includes("hello-porter")) { - return null; - } - - return ( - - - { - setShowRepoTooltip(true); - }} - onMouseOut={() => { - setShowRepoTooltip(false); - }} - > - {repository} - - {showRepoTooltip && {repository}} - - ); -}; - -export default DeploymentTypeStacks; - -const DeploymentImageContainer = styled.div` - height: 20px; - font-size: 13px; - position: relative; - display: flex; - margin-left: 15px; - margin-bottom: -3px; - align-items: center; - font-weight: 400; - justify-content: center; - color: #ffffff66; - padding-left: 5px; -`; - -const Icon = styled.img` - width: 100%; -`; - -const DeploymentTypeIcon = styled(Icon)` - width: 20px; - margin-right: 10px; -`; - -const RepositoryName = styled.div` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 390px; - position: relative; - margin-right: 3px; -`; - -const Tooltip = styled.div` - position: absolute; - left: -40px; - top: 28px; - min-height: 18px; - max-width: calc(700px); - padding: 5px 7px; - background: #272731; - z-index: 999; - color: white; - font-size: 12px; - font-family: "Work Sans", sans-serif; - outline: 1px solid #ffffff55; - opacity: 0; - animation: faded-in 0.2s 0.15s; - animation-fill-mode: forwards; - @keyframes faded-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; diff --git a/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/DeploySection.tsx b/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/DeploySection.tsx deleted file mode 100644 index b2f1933870..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/DeploySection.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import Loading from "components/Loading"; -import { Context } from "shared/Context"; -import { ChartType } from "shared/types"; - -import EventTab from "./EventTab"; - -type PropsType = { - currentChart: ChartType; -}; - -type StateType = { - events: any[]; - loading: boolean; -}; - -export default class StatusSection extends Component { - state = { - events: [] as any[], - loading: true, - }; - - renderTabs = () => { - return this.state.events.map((c, i) => { - return ; - }); - }; - - renderStatusSection = () => { - if (this.state.loading) { - return ( - - - - ); - } - if (this.state.events.length > 0) { - return {this.renderTabs()}; - } else { - return ( - - category - No events to display. This might happen while your app is still - deploying. - - ); - } - }; - - componentDidMount() { - const { currentChart } = this.props; - let { currentCluster, currentProject, setCurrentError } = this.context; - - // api.getChartEvents('', { - // namespace: currentChart.namespace, - // cluster_id: currentCluster.id, - // storage: StorageType.Secret - // }, { - // id: currentProject.id, - // name: currentChart.name, - // revision: currentChart.version - // }, (err: any, res: any) => { - // if (err) { - // setCurrentError(JSON.stringify(err)); - // return - // } - // this.setState({ controllers: res.data, loading: false }) - // }); - this.setState({ events: [1, 2, 3], loading: false }); - } - - render() { - return ( - {this.renderStatusSection()} - ); - } -} - -StatusSection.contextType = Context; - -const StyledDeploySection = styled.div` - width: 100%; - height: 100%; - position: relative; - font-size: 13px; - padding: 0px; - user-select: text; - border-radius: 5px; - overflow: hidden; -`; - -const Wrapper = styled.div` - width: 100%; - height: 100%; - overflow-y: auto; - min-width: 250px; -`; - -const NoEvents = styled.div` - padding-top: 20%; - position: relative; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - color: #ffffff44; - font-size: 14px; - - > i { - font-size: 18px; - margin-right: 12px; - } -`; diff --git a/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/EventTab.tsx b/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/EventTab.tsx deleted file mode 100644 index 22977a471b..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/expanded-chart/deploy/EventTab.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -type PropsType = {}; - -type StateType = {}; - -export default class EventTab extends Component { - state = {}; - - render() { - return ( - - - cloud_upload - Deploy successful! -
Dec 12 at 11:55AM
-
-
- ); - } -} - -const StyledEventTab = styled.div` - width: 100%; - margin-bottom: 2px; - background: #ffffff11; - border-bottom-left-radius: ${(props: { isLast: boolean }) => - props.isLast ? "5px" : ""}; -`; - -const EventHeader = styled.div` - width: 100%; - height: 50px; - display: flex; - align-items: center; - justify-content: space-between; - color: #ffffff66; - user-select: none; - padding: 8px 18px; - padding-left: 22px; -`; diff --git a/dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/TempJobList.tsx b/dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/TempJobList.tsx deleted file mode 100644 index 70bbb48995..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/TempJobList.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useContext, useState } from "react"; -import styled from "styled-components"; - -import { PorterFormContext } from "components/porter-form/PorterFormContextProvider"; -import JobList from "./JobList"; -import SaveButton from "components/SaveButton"; -import CommandLineIcon from "assets/command-line-icon"; -import ConnectToJobInstructionsModal from "./ConnectToJobInstructionsModal"; -import Loading from "components/Loading"; - -interface Props { - isAuthorized: any; - saveValuesStatus: string; - setJobs: any; - jobs: any; - handleSaveValues: any; - expandJob: any; - currentChartVersion: number; - latestChartVersion: number; - isDeployedFromGithub: boolean; - repositoryUrl?: string; - chartName: string; - isLoading: boolean; -} - -/** - * Temporary functional component for allowing job rerun button to consume - * form context (until ExpandedJobChart is migrated to FC) - */ -const TempJobList: React.FC = (props) => { - const { getSubmitValues } = useContext(PorterFormContext); - const [showConnectionModal, setShowConnectionModal] = useState(false); - const [searchInput, setSearchInput] = useState(""); - - let saveButton = ( - - { - props.handleSaveValues(getSubmitValues(), true); - }} - status={props.saveValuesStatus} - makeFlush={true} - clearPosition={true} - rounded={true} - statusPosition="right" - > - play_arrow Run Job - - { - e.preventDefault(); - setShowConnectionModal(true); - }} - > - - Shell Access - - - ); - - if (!props.isAuthorized("job", "", ["get", "update", "create"])) { - saveButton = null; - } - - if (props.isLoading) { - return ; - } - - return ( - <> - {saveButton} - - setShowConnectionModal(false)} - chartName={props.chartName} - /> - - ); -}; - -export default TempJobList; - -const ButtonWrapper = styled.div` - display: flex; - margin: 5px 0 35px; - justify-content: space-between; -`; - -const CLIModalIconWrapper = styled.div` - height: 35px; - font-size: 13px; - font-weight: 500; - font-family: "Work Sans", sans-serif; - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 20px 6px 10px; - text-align: left; - border: 1px solid #ffffff55; - border-radius: 8px; - background: #ffffff11; - color: #ffffffdd; - cursor: pointer; - - :hover { - cursor: pointer; - background: #ffffff22; - > path { - fill: #ffffff77; - } - } - - > path { - fill: #ffffff99; - } -`; - -const CLIModalIcon = styled(CommandLineIcon)` - width: 32px; - height: 32px; - padding: 8px; - - > path { - fill: #ffffff99; - } -`; diff --git a/dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ConnectToLogsInstructionModal.tsx b/dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ConnectToLogsInstructionModal.tsx deleted file mode 100644 index e4b7b1395d..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ConnectToLogsInstructionModal.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import Modal from "main/home/modals/Modal"; -import React from "react"; -import styled from "styled-components"; - -const ConnectToLogsInstructionModal: React.FC<{ - show: boolean; - onClose: () => void; - chartName: string; - namespace: string; -}> = ({ show, chartName, namespace, onClose }) => { - if (!show) { - return null; - } - - return ( - onClose()} - width="700px" - height="300px" - title="Shell Access Instructions" - > - To get shell live logs for this pod, make sure you have the Porter CLI - installed (installation instructions  - - here - - ). -
-
- Run the following line of code: - - porter logs {chartName || "[APP-NAME]"} --follow --namespace{" "} - {namespace || "[NAMESPACE]"} - -
- ); -}; - -export default ConnectToLogsInstructionModal; - -const Code = styled.div` - background: #181b21; - padding: 10px 15px; - border: 1px solid #ffffff44; - border-radius: 5px; - margin: 10px 0px 15px; - color: #ffffff; - font-size: 13px; - user-select: text; - line-height: 1em; - font-family: monospace; -`; diff --git a/dashboard/src/main/home/cluster-dashboard/preview-environments/components/RecreateWorkflowFilesModal.tsx b/dashboard/src/main/home/cluster-dashboard/preview-environments/components/RecreateWorkflowFilesModal.tsx deleted file mode 100644 index 2803a35565..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/preview-environments/components/RecreateWorkflowFilesModal.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import Modal from "main/home/modals/Modal"; -import React from "react"; -import styled from "styled-components"; - -type Props = { - hide: boolean; - isReEnable: boolean; - onClose: () => void; -}; - -const RecreateWorkflowFilesModal = (props: Props) => { - const createNewWorkflows = () => {}; - - if (props.hide) { - return null; - } - - return ( - -
-
- We couldn't find any workflow files to process the{" "} - {props.isReEnable - ? "re enabling of this preview environment" - : "creation of this preview environment"} - . - - Do you want to create the workflow files? Or Remove the repository? - - - ⚠️ If the workflow files don't exist, Porter will not be able to - create any preview environment for this repository. - -
- - - props.onClose()}>Close - createNewWorkflows()}> - Create new workflows - - -
-
- ); -}; - -export default RecreateWorkflowFilesModal; - -const Button = styled.button` - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - font-size: 13px; - cursor: pointer; - font-family: "Work Sans", sans-serif; - border-radius: 10px; - color: white; - height: 35px; - padding: 10px 16px; - font-weight: 500; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - border: none; - :not(:last-child) { - margin-right: 10px; - } -`; - -const DeleteButton = styled(Button)` - ${({ disabled }: { disabled?: boolean }) => { - if (disabled) { - return ` - background: #aaaabbee; - :hover { - background: #aaaabbee; - } - `; - } - - return ` - background: #dd4b4b; - :hover { - background: #b13d3d; - }`; - }} -`; - -const CancelButton = styled(Button)` - background: #616feecc; - :hover { - background: #505edddd; - } -`; - -const ActionWrapper = styled.div` - display: flex; - align-items: center; -`; - -const Warning = styled.div` - font-size: 13px; - display: flex; - width: 100%; - margin-top: 10px; - line-height: 1.4em; - align-items: center; - color: white; - > i { - margin-right: 10px; - font-size: 18px; - } - color: ${(props: { highlight: boolean }) => - props.highlight ? "#f5cb42" : ""}; -`; - -const HighlightText = styled.div` - font-size: 16px; - font-weight: bold; - color: #ffffff; -`; diff --git a/dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx b/dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx deleted file mode 100644 index 5e085d7b71..0000000000 --- a/dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import React, { useState, useContext } from "react"; -import styled from "styled-components"; -import pr_icon from "assets/pull_request_icon.svg"; -import { PullRequest } from "../types"; -import api from "shared/api"; -import { Context } from "shared/Context"; -import { ActionButton } from "../components/ActionButton"; -import Loading from "components/Loading"; -import RecreateWorkflowFilesModal from "../components/RecreateWorkflowFilesModal"; -import { EllipsisTextWrapper, RepoLink } from "../components/styled"; - -const PullRequestCard = ({ - pullRequest, - onCreation, -}: { - pullRequest: PullRequest; - onCreation: (pullRequest: PullRequest) => void; -}) => { - const { currentProject, currentCluster, setCurrentError } = useContext( - Context - ); - const [showMergeInfoTooltip, setShowMergeInfoTooltip] = useState(false); - const [ - openRecreateWorkflowFilesModal, - setOpenRecreateWorkflowFilesModal, - ] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [hasError, setHasError] = useState(false); - - const createPreviewEnvironment = async () => { - setIsLoading(true); - try { - await api.createPreviewEnvironmentDeployment("", pullRequest, { - cluster_id: currentCluster?.id, - project_id: currentProject?.id, - }); - onCreation(pullRequest); - } catch (error) { - setCurrentError(error?.response?.data?.error || error); - setHasError(true); - setTimeout(() => { - setHasError(false); - }, 500); - } finally { - setIsLoading(false); - } - }; - - return ( - <> - setOpenRecreateWorkflowFilesModal(false)} - isReEnable={false} - /> - - - - - - {pullRequest.pr_title} - - - setShowMergeInfoTooltip(true)} - onMouseOut={() => setShowMergeInfoTooltip(false)} - > - {pullRequest.branch_from} - arrow_forward - {pullRequest.branch_into} - - {showMergeInfoTooltip && ( - - From: {pullRequest.branch_from} Into:{" "} - {pullRequest.branch_into} - - )} - - - open_in_new - View PR - - - - - - - - Not deployed - - - - - - - {isLoading ? ( - - ) : ( - <> - play_arrow - Activate Preview Environment - - )} - - - - - ); -}; - -export default PullRequestCard; - -const Flex = styled.div` - display: flex; - align-items: center; -`; - -const PRName = styled.div` - font-family: "Work Sans", sans-serif; - font-weight: 500; - color: #ffffff; - display: flex; - align-items: center; - margin-bottom: 10px; -`; - -const DeploymentCardWrapper = styled.div` - display: flex; - justify-content: space-between; - font-size: 13px; - height: 75px; - padding: 12px; - padding-left: 14px; - border-radius: 5px; - background: #26292e; - border: 1px solid #494b4f; - - animation: fadeIn 0.5s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const DataContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; -`; - -const StatusContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - height: 100%; -`; - -const PRIcon = styled.img` - font-size: 20px; - height: 17px; - margin-right: 10px; - color: #aaaabb; - opacity: 50%; -`; - -const Status = styled.span` - font-size: 13px; - display: flex; - align-items: center; - min-height: 17px; - color: #a7a6bb; -`; - -const StatusDot = styled.div` - width: 8px; - height: 8px; - margin-right: 10px; - background: #ffffff88; - border-radius: 20px; - margin-left: 3px; -`; - -const Tooltip = styled.div` - position: absolute; - left: 14px; - top: 20px; - min-height: 18px; - max-width: calc(700px); - padding: 5px 7px; - background: #272731; - z-index: 999; - color: white; - font-size: 12px; - font-family: "Work Sans", sans-serif; - outline: 1px solid #ffffff55; - opacity: 0; - animation: faded-in 0.2s 0.15s; - animation-fill-mode: forwards; - @keyframes faded-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const InfoWrapper = styled.div` - display: flex; - align-items: center; - margin-right: 8px; - position: relative; -`; - -const MergeInfo = styled.div` - font-size: 13px; - margin-left: 14px; - align-items: center; - color: #aaaabb66; - white-space: nowrap; - display: flex; - align-items: center; - text-overflow: ellipsis; - overflow: hidden; - max-width: 300px; - - > i { - font-size: 16px; - margin: 0 2px; - } -`; diff --git a/dashboard/src/main/home/dashboard/ClusterOverview.tsx b/dashboard/src/main/home/dashboard/ClusterOverview.tsx deleted file mode 100644 index 436995783d..0000000000 --- a/dashboard/src/main/home/dashboard/ClusterOverview.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import { Context } from "shared/Context"; -import api from "shared/api"; -import { ClusterType, DetailedClusterType } from "shared/types"; -import Helper from "components/form-components/Helper"; -import { pushFiltered } from "shared/routing"; - -import { RouteComponentProps, withRouter } from "react-router"; - -import Modal from "../modals/Modal"; -import Heading from "components/form-components/Heading"; - -type PropsType = RouteComponentProps & { - currentCluster: ClusterType; -}; - -type StateType = { - loading: boolean; - error: string; - clusters: DetailedClusterType[]; - showErrorModal?: { - clusterId: number; - show: boolean; - }; -}; - -class Templates extends Component { - state: StateType = { - loading: true, - error: "", - clusters: [], - showErrorModal: undefined, - }; - - componentDidMount() { - this.updateClusterList(); - } - - componentDidUpdate(prevProps: PropsType) { - if (prevProps.currentCluster?.name != this.props.currentCluster?.name) { - this.updateClusterList(); - } - } - - updateClusterList = async () => { - try { - const res = await api.getClusters( - "", - {}, - { id: this.context.currentProject.id } - ); - - if (res.data) { - this.setState({ clusters: res.data, loading: false, error: "" }); - } else { - this.setState({ loading: false, error: "Response data missing" }); - } - } catch (err) { - this.setState(err); - } - }; - - renderIcon = () => { - return ( - - - - - - - - - - - ); - }; - - renderClusters = () => { - return this.state.clusters.map( - (cluster: DetailedClusterType, i: number) => { - return ( - { - this.context.setCurrentCluster(cluster); - pushFiltered(this.props, "/applications", ["project_id"], { - cluster: cluster.name, - }); - }} - key={i} - > - {this.renderIcon()} - {cluster.vanity_name || cluster.name} - - ); - } - ); - }; - - renderErrorModal = () => { - const clusterError = - this.state.showErrorModal?.show && - this.state.clusters.find( - (c) => c.id === this.state.showErrorModal?.clusterId - ); - const ingressError = clusterError?.ingress_error; - return ( - <> - {clusterError && ( - this.setState({ showErrorModal: undefined })} - width="665px" - height="min-content" - > - Porter encountered an error. Full error log: - {ingressError.error} - - )} - - ); - }; - - render() { - return ( - - Connected clusters - {this.renderClusters()} - {this.renderErrorModal()} - - ); - } -} - -Templates.contextType = Context; - -export default withRouter(Templates); - -const CodeBlock = styled.span` - display: block; - background-color: #1b1d26; - color: white; - border-radius: 5px; - font-family: monospace; - user-select: text; - max-height: 400px; - width: 90%; - margin-left: 5%; - margin-top: 20px; - overflow-y: auto; - padding: 10px; - overflow-wrap: break-word; -`; - -const StyledClusterList = styled.div` - padding-left: 2px; - overflow: visible; -`; - -const DashboardIcon = styled.div` - position: relative; - height: 25px; - min-width: 25px; - width: 25px; - border-radius: 200px; - margin-right: 15px; - display: flex; - align-items: center; - justify-content: center; - background: #676c7c; - border: 1px solid #8e94aa; - > i { - font-size: 22px; - } -`; - -const TemplateTitle = styled.div` - text-align: center; - white-space: nowrap; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -`; - -const TemplateBlock = styled.div` - align-items: center; - user-select: none; - display: flex; - font-size: 13px; - font-weight: 500; - padding: 15px; - margin-bottom: 20px; - align-item: center; - cursor: pointer; - color: #ffffff; - position: relative; - border-radius: 5px; - background: ${props => props.theme.clickable.bg}; - border: 1px solid #494b4f; - :hover { - border: 1px solid #7a7b80; - } - - animation: fadeIn 0.3s 0s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const TemplateList = styled.div` - overflow-y: auto; - overflow: visible; -`; diff --git a/dashboard/src/main/home/dashboard/PipelinesSection.tsx b/dashboard/src/main/home/dashboard/PipelinesSection.tsx deleted file mode 100644 index 3cf53fca7d..0000000000 --- a/dashboard/src/main/home/dashboard/PipelinesSection.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -type PropsType = {}; - -type StateType = {}; - -export default class PipelinesSection extends Component { - state = {}; - - render() { - return ; - } -} - -const StyledPipelinesSection = styled.div` - width: 100%; - height: calc(100vh - 380px); - margin-top: 30px; - display: flex; - align-items: center; - color: #aaaabb; - border-radius: 5px; - text-align: center; - font-size: 13px; - background: #ffffff08; - font-family: "Work Sans", sans-serif; -`; diff --git a/dashboard/src/main/home/database-dashboard/DatabaseHeaderItem.tsx b/dashboard/src/main/home/database-dashboard/DatabaseHeaderItem.tsx deleted file mode 100644 index dbe25c198a..0000000000 --- a/dashboard/src/main/home/database-dashboard/DatabaseHeaderItem.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -import CopyToClipboard from "components/CopyToClipboard"; -import Container from "components/porter/Container"; -import Text from "components/porter/Text"; -import { type DatastoreMetadataWithSource } from "lib/databases/types"; - -import copy from "assets/copy-left.svg"; - -type DatabaseHeaderItemProps = { - item: DatastoreMetadataWithSource; -}; - -const DatabaseHeaderItem: React.FC = ({ item }) => { - const truncateText = (text: string, length: number): string => { - if (text.length <= length) { - return text; - } - return `${text.substring(0, length)}...`; - }; - - const titleizeText = (text: string): string => { - return text - .split("_") - .map((word) => { - return word[0].toUpperCase() + word.substring(1); - }) - .join(" "); - }; - - return ( - - {titleizeText(item.name)} - - - - {truncateText(item.value, 42)} - - - - - - - ); -}; - -export default DatabaseHeaderItem; - -const CopyIcon = styled.img` - cursor: pointer; - margin-left: 5px; - margin-right: 5px; - width: 10px; - height: 10px; -`; diff --git a/dashboard/src/main/home/database-dashboard/tabs/ConnectedAppsTab.tsx b/dashboard/src/main/home/database-dashboard/tabs/ConnectedAppsTab.tsx deleted file mode 100644 index 5497dfed99..0000000000 --- a/dashboard/src/main/home/database-dashboard/tabs/ConnectedAppsTab.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useContext, useMemo, useState } from "react"; -import _ from "lodash"; -import { useHistory } from "react-router"; -import styled from "styled-components"; - -import Container from "components/porter/Container"; -import Spacer from "components/porter/Spacer"; -import Text from "components/porter/Text"; -import SelectableAppList from "main/home/app-dashboard/apps/SelectableAppList"; -import { useDatastore } from "lib/hooks/useDatastore"; -import { useLatestAppRevisions } from "lib/hooks/useLatestAppRevisions"; - -import { Context } from "shared/Context"; - -import { useDatastoreContext } from "../DatabaseContextProvider"; -import ConnectAppsModal from "../shared/ConnectAppsModal"; - -const ConnectedAppsTab: React.FC = () => { - const [showConnectAppsModal, setShowConnectAppsModal] = useState(false); - const { projectId, datastore } = useDatastoreContext(); - // NB: the cluster id here is coming from the global context, but really it should be coming from - // the datastore context. However, we do not currently have a way to relate db to the cluster it lives in. - // This will be a bug for multi-cluster projects. - const { currentCluster: { id: clusterId = 0 } = {} } = useContext(Context); - const { revisions } = useLatestAppRevisions({ - projectId, - clusterId, - }); - const { attachDatastoreToAppInstances } = useDatastore(); - const history = useHistory(); - - const { connectedApps, remainingApps } = useMemo(() => { - const [connected, remaining] = _.partition( - revisions, - (r) => datastore.env?.linked_applications.includes(r.source.name) - ); - return { - connectedApps: connected.sort((a, b) => - a.source.name.localeCompare(b.source.name) - ), - remainingApps: remaining.sort((a, b) => - a.source.name.localeCompare(b.source.name) - ), - }; - }, [revisions, datastore.env?.linked_applications]); - - return ( - - - Connected Apps - - - ({ - app: ra, - key: ra.source.name, - onSelect: () => { - history.push( - `/apps/${ra.source.name}?target=${ra.app_revision.deployment_target.id}` - ); - }, - }))} - /> - - { - setShowConnectAppsModal(true); - }} - > - add - Connect apps to this datastore - - {showConnectAppsModal && ( - { - setShowConnectAppsModal(false); - }} - apps={remainingApps} - onSubmit={async (appInstanceIds: string[]) => { - await attachDatastoreToAppInstances({ - name: datastore.name, - clusterId, - appInstanceIds, - }); - }} - /> - )} - - ); -}; - -export default ConnectedAppsTab; - -const ConnectedAppsContainer = styled.div` - width: 100%; -`; - -const AddAddonButton = styled.div` - color: #aaaabb; - background: ${({ theme }) => theme.fg}; - border: 1px solid #494b4f; - :hover { - border: 1px solid #7a7b80; - color: white; - } - display: flex; - align-items: center; - border-radius: 5px; - height: 40px; - font-size: 13px; - width: 100%; - padding-left: 10px; - cursor: pointer; - .add-icon { - width: 30px; - font-size: 20px; - } -`; - -const I = styled.i` - color: white; - font-size: 14px; - display: flex; - align-items: center; - margin-right: 7px; - justify-content: center; -`; diff --git a/dashboard/src/main/home/database-dashboard/tabs/MetricsTab.tsx b/dashboard/src/main/home/database-dashboard/tabs/MetricsTab.tsx deleted file mode 100644 index f8cdd84500..0000000000 --- a/dashboard/src/main/home/database-dashboard/tabs/MetricsTab.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useEffect, useState } from "react"; -import styled from "styled-components"; - -type Props = { -}; - -const MetricsTab: React.FC = ({ -}) => { - const [isExpanded, setIsExpanded] = useState(false); - - useEffect(() => { - // Do something - }, []); - - return ( - - - ); -}; - -export default MetricsTab; - -const StyledTemplateComponent = styled.div` -width: 100%; -animation: fadeIn 0.3s 0s; -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -`; \ No newline at end of file diff --git a/dashboard/src/main/home/database-dashboard/utils.tsx b/dashboard/src/main/home/database-dashboard/utils.tsx deleted file mode 100644 index 3049d175f8..0000000000 --- a/dashboard/src/main/home/database-dashboard/utils.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { type SerializedDatastore } from "lib/databases/types"; - -export const datastoreField = ( - datastore: SerializedDatastore, - field: string -): string => { - if (datastore.metadata?.length === 0) { - return ""; - } - - const properties = datastore.metadata?.filter( - (metadata) => metadata.name === field - ); - if (properties === undefined || properties.length === 0) { - return ""; - } - - if (properties.length === 0) { - return ""; - } - - return properties[0].value; -}; diff --git a/dashboard/src/main/home/env-dashboard/EnvGroupList.tsx b/dashboard/src/main/home/env-dashboard/EnvGroupList.tsx deleted file mode 100644 index aa7a9b8efc..0000000000 --- a/dashboard/src/main/home/env-dashboard/EnvGroupList.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -import EnvGroup from "./EnvGroup"; -import { type RouteComponentProps, withRouter } from "react-router"; - -type Props = RouteComponentProps & { - envGroups: any[]; -}; - -const EnvGroupList: React.FunctionComponent = ({ envGroups }) => { - return ( - - {envGroups.map((envGroup: any, i: number) => { - return ( - - ); - })} - - ); -}; - -export default withRouter(EnvGroupList); - -const StyledEnvGroupList = styled.div` - padding-bottom: 85px; -`; \ No newline at end of file diff --git a/dashboard/src/main/home/infrastructure/ExpandedInfra.tsx b/dashboard/src/main/home/infrastructure/ExpandedInfra.tsx deleted file mode 100644 index bb0251df4d..0000000000 --- a/dashboard/src/main/home/infrastructure/ExpandedInfra.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import styled from "styled-components"; -import DynamicLink from "components/DynamicLink"; -import Loading from "components/Loading"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import { integrationList } from "shared/common"; - -import DeployList from "./components/DeployList"; -import InfraResourceList from "./components/InfraResourceList"; -import PorterFormWrapper from "components/porter-form/PorterFormWrapper"; -import { readableDate } from "shared/string_utils"; -import Placeholder from "components/OldPlaceholder"; -import Header from "components/expanded-object/Header"; -import { Infrastructure, KindMap, Operation } from "shared/types"; -import InfraSettings from "./components/InfraSettings"; -import { useParams } from "react-router"; - -type InfraTabOptions = "deploys" | "resources" | "settings"; - -type ExpandedInfraParams = { - infra_id_str: string; -}; - -const ExpandedInfra: React.FunctionComponent = () => { - const { infra_id_str } = useParams(); - const infra_id = parseInt(infra_id_str); - const [infra, setInfra] = useState(null); - const [infraForm, setInfraForm] = useState(null); - const [saveValuesStatus, setSaveValueStatus] = useState(null); - const [hasError, setHasError] = useState(false); - - const { currentProject, setCurrentError } = useContext(Context); - - useEffect(() => { - if (!currentProject) { - return; - } - - refreshInfra(); - }, [currentProject, infra_id]); - - const refreshInfra = () => { - api - .getInfraByID( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra_id, - } - ) - .then(({ data }) => { - setInfra(data); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - }); - }; - - useEffect(() => { - if (!currentProject || !infra) { - return; - } - - api - .getOperation( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra_id, - operation_id: infra.latest_operation.id, - } - ) - .then(({ data }) => { - setInfraForm(data.form); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - }); - }, [currentProject, infra, infra?.latest_operation?.id]); - - const update = (values: any, cb: () => void) => { - setSaveValueStatus("loading"); - - api - .updateInfra( - "", - { - values: values, - }, - { - project_id: currentProject.id, - infra_id: infra.id, - } - ) - .then(({ data }) => { - // the resulting data is now the latest operation - let newInfra = infra; - newInfra.latest_operation = data; - setInfra(newInfra); - setSaveValueStatus("successful"); - cb(); - }) - .catch((err) => { - console.error(err); - }); - }; - - const setLatestOperation = (operation: Operation) => { - let newInfra = infra; - newInfra.latest_operation = operation; - setInfra(newInfra); - }; - - if (hasError) { - return Error loading infra; - } - - if (!infra || !infraForm) { - return ; - } - - const renderTabContents = (newTab: InfraTabOptions) => { - switch (newTab) { - case "deploys": - return ( - - ); - case "resources": - return ; - case "settings": - return ; - } - }; - - return ( - -
e.stopPropagation()} - > - {KindMap[infra.kind].resource_name} - open_in_new - , - ]} - /> - - - - - ); -}; - -export default ExpandedInfra; - -const PorterFormContainer = styled.div` - position: relative; - min-width: 300px; -`; - -const StyledExpandedInfra = styled.div` - width: 100%; - z-index: 0; - animation: fadeIn 0.3s; - animation-timing-function: ease-out; - animation-fill-mode: forwards; - display: flex; - flex-direction: column; - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const ResourceLink = styled(DynamicLink)` - font-size: 13px; - font-weight: 400; - margin-left: 20px; - color: #aaaabb; - display: flex; - align-items: center; - - :hover { - text-decoration: underline; - color: white; - } - - > i { - margin-left: 7px; - font-size: 17px; - } -`; diff --git a/dashboard/src/main/home/infrastructure/InfrastructureList.tsx b/dashboard/src/main/home/infrastructure/InfrastructureList.tsx deleted file mode 100644 index 5ab801b38e..0000000000 --- a/dashboard/src/main/home/infrastructure/InfrastructureList.tsx +++ /dev/null @@ -1,337 +0,0 @@ -import DynamicLink from "components/DynamicLink"; -import React, { useContext, useEffect, useMemo, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import { useHistory, useLocation } from "react-router"; -import { pushFiltered } from "shared/routing"; - -import { Column } from "react-table"; -import styled from "styled-components"; -import Table from "components/OldTable"; - -import Loading from "components/Loading"; - -import _ from "lodash"; -import { integrationList } from "shared/common"; -import { Infrastructure, KindMap } from "shared/types"; -import { capitalize, readableDate } from "shared/string_utils"; -import Placeholder from "components/OldPlaceholder"; -import SaveButton from "components/SaveButton"; -import { useRouting } from "shared/routing"; -import Description from "components/Description"; - -const InfrastructureList = () => { - const [isLoading, setIsLoading] = useState(true); - const [hasError, setHasError] = useState(false); - const [infraList, setInfraList] = useState([]); - const { currentProject, setCurrentError } = useContext(Context); - const { pushFiltered } = useRouting(); - - useEffect(() => { - if (currentProject) { - let isSubscribed = true; - - api - .getInfra( - "", - { - version: "v2", - }, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - if (!isSubscribed) { - return; - } - - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setInfraList(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - - return () => { - isSubscribed = false; - }; - } - }, [currentProject]); - - const columns = useMemo>>(() => { - if (infraList.length == 0) { - return []; - } - - return [ - { - Header: "Kind", - accessor: "kind", - Cell: ({ row }) => { - let original = row.original as Infrastructure; - - return ( - - - {integrationList[original.kind]?.label} - - ); - }, - }, - { - Header: "Name", - accessor: "name", - }, - { - Header: "Status", - accessor: "status", - Cell: ({ row }) => { - let original = row.original as Infrastructure; - - return ( - - - {capitalize(original.status)} - - ); - }, - }, - { - Header: "Resource", - accessor: "aws_integration_id", - Cell: ({ row }) => { - let original = row.original as Infrastructure; - - return ( - e.stopPropagation()} - > - {KindMap[original.kind].resource_name} - open_in_new - - ); - }, - }, - { - Header: "Last Updated", - accessor: "updated_at", - Cell: ({ row }) => { - return readableDate(row.original.updated_at); - }, - }, - { - Header: "Version", - accessor: "source_version", - }, - ]; - }, [infraList]); - - if (isLoading) { - return ( - - - - ); - } - - if (hasError) { - return Error; - } - - const renderTable = () => { - if (infraList.length == 0) { - return No infrastructure found.; - } - - return ( - { - let original = row.original as Infrastructure; - pushFiltered(`/infrastructure/${original.id}`, ["project_id"]); - }} - /> - ); - }; - - return ( - - - - build_circle - - Managed infrastructure (legacy) - - - - Managed infrastructure is infrastructure controlled and updated - through Porter. - - - - - - - pushFiltered(`/infrastructure/provision`, ["project_id"]) - } - > - add - Create Infrastructure - - - - {renderTable()} - - ); -}; - -export default InfrastructureList; - -const KindContainer = styled.div` - display: flex; - align-items: center; - min-width: 250px; -`; - -const Kind = styled.div` - margin-left: 8px; -`; - -const DatabasesListWrapper = styled.div` - margin-top: 35px; -`; - -const StyledTableWrapper = styled.div` - padding: 14px; - position: relative; - border-radius: 8px; - background: #26292e; - border: 1px solid #494b4f; - width: 100%; - height: 100%; - :not(:last-child) { - margin-bottom: 25px; - } -`; - -const ControlRow = styled.div` - display: flex; - margin-left: auto; - justify-content: space-between; - align-items: center; - margin-bottom: 35px; - padding-left: 0px; -`; - -const Icon = styled.img` - height: 20px; -`; - -const DashboardIcon = styled.div` - height: 45px; - min-width: 45px; - width: 45px; - border-radius: 5px; - margin-right: 17px; - display: flex; - align-items: center; - justify-content: center; - background: #676c7c; - border: 2px solid #8e94aa; - > i { - font-size: 22px; - } -`; - -const InfoSection = styled.div` - margin-top: 36px; - font-family: "Work Sans", sans-serif; - margin-left: 0px; - margin-bottom: 35px; -`; - -const LineBreak = styled.div` - width: calc(100% - 0px); - height: 1px; - background: #494b4f; - margin: 10px 0px 35px; -`; - -const StyledTitleSection = styled.div` - margin-bottom: 15px; - display: flex; - align-items: center; - justify-content: start; - font-size: 24px; - font-weight: 600; -`; - -const Status = styled.span` - font-size: 13px; - display: flex; - align-items: center; - margin-left: 1px; - min-height: 17px; - color: #a7a6bb; -`; - -const StatusDot = styled.div` - width: 8px; - height: 8px; - background: ${(props: { status: string }) => - props.status === "created" - ? "#4797ff" - : props.status === "failed" - ? "#ed5f85" - : props.status === "completed" - ? "#00d12a" - : "#f5cb42"}; - border-radius: 20px; - margin-left: 3px; - margin-right: 15px; -`; - -const ResourceLink = styled(DynamicLink)` - font-size: 13px; - font-weight: 400; - margin-left: 7px; - color: #aaaabb; - display: flex; - align-items: center; - - :hover { - text-decoration: underline; - color: white; - } - - > i { - margin-left: 7px; - font-size: 17px; - } -`; - -const SaveButtonContainer = styled.div` - position: relative; - width: 100%; -`; diff --git a/dashboard/src/main/home/infrastructure/InfrastructureRouter.tsx b/dashboard/src/main/home/infrastructure/InfrastructureRouter.tsx deleted file mode 100644 index eb0b3b107d..0000000000 --- a/dashboard/src/main/home/infrastructure/InfrastructureRouter.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useContext, useLayoutEffect } from "react"; -import { Route, Switch } from "react-router-dom"; -import { withRouter } from "react-router"; -import { useParams } from "react-router-dom"; -import InfrastructureList from "./InfrastructureList"; -import ExpandedInfra from "./ExpandedInfra"; -import ProvisionInfra from "./components/ProvisionInfra"; -import { Context } from "shared/Context"; -import { useRouting } from "shared/routing"; - -const InfrastructureRouter = () => { - const { currentCluster, currentProject } = useContext(Context); - const { pushFiltered } = useRouting(); - - useLayoutEffect(() => { - if (!currentProject || !currentProject.managed_infra_enabled) { - pushFiltered("/dashboard", []); - } - }, [currentProject]); - - return ( - - - - - - - - - - - - - - - ); -}; - -export default withRouter(InfrastructureRouter); diff --git a/dashboard/src/main/home/infrastructure/components/DeployList.tsx b/dashboard/src/main/home/infrastructure/components/DeployList.tsx deleted file mode 100644 index bf5a06bf15..0000000000 --- a/dashboard/src/main/home/infrastructure/components/DeployList.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import { - Infrastructure, - Operation, - OperationStatus, - OperationType, -} from "shared/types"; -import { readableDate } from "shared/string_utils"; -import Placeholder from "components/OldPlaceholder"; -import { useWebsockets } from "shared/hooks/useWebsockets"; -import ExpandedOperation from "./ExpandedOperation"; - -type Props = { - infra: Infrastructure; - setLatestOperation: (operation: Operation) => void; - refreshInfra: () => void; -}; - -const DeployList: React.FunctionComponent = ({ - infra, - setLatestOperation, - refreshInfra, -}) => { - const [isLoading, setIsLoading] = useState(true); - const [hasError, setHasError] = useState(false); - const [operationList, setOperationList] = useState([]); - const [selectedOperation, setSelectedOperation] = useState(null); - const { currentProject, setCurrentError } = useContext(Context); - - const refreshOperationList = () => { - api - .listOperations( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setOperationList(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }; - - useEffect(() => { - refreshOperationList(); - }, [currentProject, infra, infra?.latest_operation?.id]); - - const { newWebsocket, openWebsocket, closeWebsocket } = useWebsockets(); - - const parseOperationWebsocketEvent = (evt: MessageEvent) => { - let { status } = JSON.parse(evt.data); - - // if the status is operation completed, mark that operation as completed - if (status == "OPERATION_COMPLETED") { - refreshOperationList(); - } - }; - - const setupOperationWebsocket = (websocketID: string) => { - let apiPath = `/api/projects/${currentProject.id}/infras/${infra.id}/operations/${infra.latest_operation.id}/state`; - - const wsConfig = { - onopen: () => { - console.log(`connected to websocket:`, websocketID); - }, - onmessage: parseOperationWebsocketEvent, - onclose: () => { - console.log(`closing websocket:`, websocketID); - }, - onerror: (err: ErrorEvent) => { - console.log(err); - closeWebsocket(websocketID); - }, - }; - - newWebsocket(websocketID, apiPath, wsConfig); - openWebsocket(websocketID); - }; - - useEffect(() => { - if (!currentProject || !infra || !infra.latest_operation) { - return; - } - - // if the operation list is empty or does not match the latest infra operation, don't - // open a websocket - if ( - operationList.length == 0 || - infra.latest_operation.id !== operationList[0].id - ) { - return; - } - - // if the latest_operation is in progress, open a websocket - if (operationList[0].status === "starting") { - const websocketID = operationList[0].id + "_state"; - - setupOperationWebsocket(websocketID); - - return () => { - closeWebsocket(websocketID); - }; - } - }, [currentProject, infra, operationList]); - - if (isLoading) { - return ( - - - - ); - } - - if (operationList.length == 0) { - return No operations available; - } - - if (hasError) { - return Error; - } - - const getOperationDescription = ( - type: OperationType, - status: OperationStatus - ): string => { - switch (type) { - case "retry_create": - case "create": - if (status == "starting") { - return "Infrastructure creation in progress"; - } else if (status == "completed") { - return "Infrastructure creation completed."; - } else if (status == "errored") { - return "This infrastructure encountered an error while creating."; - } - case "update": - if (status == "starting") { - return "Infrastructure update in progress"; - } else if (status == "completed") { - return "Infrastructure update completed."; - } else if (status == "errored") { - return "This infrastructure encountered an error while updating."; - } - case "retry_delete": - case "delete": - if (status == "starting") { - return "Infrastructure deletion in progress"; - } else if (status == "completed") { - return "Infrastructure deletion completed."; - } else if (status == "errored") { - return "This infrastructure encountered an error while deleting."; - } - } - }; - - const backFromExpandedOperation = (operation?: Operation) => { - if (operation) { - setLatestOperation(operation); - } - - setSelectedOperation(null); - }; - - const renderContents = () => { - if (selectedOperation) { - return ( - - ); - } - - return operationList.map((operation, i) => { - return ( - - setSelectedOperation(operation)} - > - - - {operation.status === "errored" - ? "report_problem" - : operation.status === "completed" - ? "check_circle" - : "cached"} - - - - {getOperationDescription(operation.type, operation.status)} - - - - - - - access_time - - {readableDate(operation.last_updated)} - - - - navigate_next - - - - ); - }); - }; - - return ( - - {renderContents()} - - ); -}; - -export default DeployList; - -const DatabasesListWrapper = styled.div``; - -const StyledDeploysGrid = styled.div` - display: grid; - grid-row-gap: 15px; - grid-template-columns: 1; -`; - -const StyledCard = styled.div<{ status: string }>` - display: flex; - align-items: center; - justify-content: space-between; - border: 1px solid - ${({ status }) => (status === "critical" ? "#ff385d" : "#ffffff44")}; - background: #ffffff08; - margin-bottom: 3px; - border-radius: 10px; - padding: 14px; - overflow: hidden; - height: 60px; - font-size: 13px; - cursor: pointer; - :hover { - background: #ffffff11; - border: 1px solid - ${({ status }) => (status === "critical" ? "#ff385d" : "#ffffff66")}; - } - animation: fadeIn 0.5s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - .next-icon { - display: none; - color: #ffffff55; - } - - :hover .next-icon { - display: inline-block; - } -`; - -const NextIconContainer = styled.div` - width: 30px; - padding-top: 2px; -`; - -const ContentContainer = styled.div` - display: flex; - height: 100%; - width: 100%; - align-items: center; -`; - -const Icon = styled.span<{ status: OperationStatus }>` - font-size: 20px; - margin-left: 10px; - margin-right: 20px; - color: ${({ status }) => (status === "errored" ? "#ff385d" : "#aaaabb")}; -`; - -const DeployInformation = styled.div` - display: flex; - flex-direction: column; - justify-content: space-around; - height: 100%; -`; - -const DeployHeader = styled.div` - font-family: "Work Sans", sans-serif; - font-weight: 500; - color: #ffffff; -`; - -const MetaContainer = styled.div` - display: flex; - align-items: center; - white-space: nowrap; - height: 100%; -`; - -const TimestampContainer = styled.div` - display: flex; - white-space: nowrap; - align-items: center; - color: #ffffff55; - margin-right: 10px; - font-size: 13px; - min-width: 130px; - justify-content: space-between; -`; - -const TimestampIcon = styled.span` - margin-right: 7px; - font-size: 18px; -`; diff --git a/dashboard/src/main/home/infrastructure/components/ExpandedOperation.tsx b/dashboard/src/main/home/infrastructure/components/ExpandedOperation.tsx deleted file mode 100644 index 98df04abf3..0000000000 --- a/dashboard/src/main/home/infrastructure/components/ExpandedOperation.tsx +++ /dev/null @@ -1,401 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import { - Infrastructure, - Operation, - OperationStatus, - OperationType, -} from "shared/types"; -import { readableDate } from "shared/string_utils"; -import Placeholder from "components/OldPlaceholder"; -import { useWebsockets } from "shared/hooks/useWebsockets"; -import Heading from "components/form-components/Heading"; -import SaveButton from "components/SaveButton"; -import PorterFormWrapper from "components/porter-form/PorterFormWrapper"; -import Description from "components/Description"; -import { OperationDetails } from "components/ProvisionerStatus"; - -type Props = { - infra: Infrastructure; - operation_id: string; - back: (operation?: Operation) => void; - refreshInfra: () => void; -}; - -const ExpandedOperation: React.FunctionComponent = ({ - infra, - operation_id, - back, - refreshInfra, -}) => { - const [isLoading, setIsLoading] = useState(true); - const [hasError, setHasError] = useState(false); - const [operation, setOperation] = useState(null); - const [logs, setLogs] = useState(null); - const { currentProject, setCurrentError } = useContext(Context); - - const { newWebsocket, openWebsocket, closeWebsocket } = useWebsockets(); - - useEffect(() => { - api - .getOperation( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra.id, - operation_id: operation_id, - } - ) - .then(({ data }) => { - setOperation(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject, operation_id]); - - const parseLogWebsocketEvent = (evt: MessageEvent) => { - setLogs((logs) => { - if (!logs) { - return [evt.data]; - } - - let newLogs = [...logs]; - newLogs.push(evt.data); - return newLogs; - }); - }; - - const setupLogWebsocket = (websocketID: string) => { - let apiPath = `/api/projects/${currentProject.id}/infras/${operation.infra_id}/operations/${operation.id}/log_stream`; - - const wsConfig = { - onopen: () => { - console.log(`connected to websocket:`, websocketID); - }, - onmessage: parseLogWebsocketEvent, - onclose: () => { - console.log(`closing websocket:`, websocketID); - }, - onerror: (err: ErrorEvent) => { - console.log(err); - closeWebsocket(websocketID); - }, - }; - - newWebsocket(websocketID, apiPath, wsConfig); - openWebsocket(websocketID); - }; - - useEffect(() => { - if (!currentProject || !operation) { - return; - } - - // if the operation is in progress, open a websocket - if (operation.status === "starting") { - const websocketID = operation.id + "_log_stream"; - - setupLogWebsocket(websocketID); - - return () => { - closeWebsocket(websocketID); - }; - } else { - // if the operation is completed, get logs from the endpoint - api - .getOperationLogs( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra.id, - operation_id: operation_id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data.logs)) { - throw Error("Data is not an array"); - } - - setLogs(data.logs); - }) - .catch(() => { - setLogs(["No logs available."]); - }); - } - }, [currentProject, operation]); - - const retry = () => { - let pathParams = { - project_id: currentProject.id, - infra_id: infra.id, - }; - - let apiCall = api.updateInfra; - - if (operation.type == "create" || operation.type == "retry_create") { - apiCall = api.retryCreateInfra; - } else if (operation.type == "delete" || operation.type == "retry_delete") { - apiCall = api.retryDeleteInfra; - } - - apiCall("", {}, pathParams) - .then(({ data }) => { - back(data); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - }); - }; - - if (isLoading) { - return ( - - - - ); - } - - if (hasError) { - return Error; - } - - const getOperationDescription = ( - type: OperationType, - status: OperationStatus, - time: string - ): string => { - switch (type) { - case "retry_create": - case "create": - if (status == "starting") { - return ( - "Infrastructure creation in progress, started at " + - readableDate(time) - ); - } else if (status == "completed") { - return "Infrastructure creation completed at " + readableDate(time); - } else if (status == "errored") { - return "This infrastructure encountered an error while creating."; - } - case "update": - if (status == "starting") { - return ( - "Infrastructure update in progress, started at " + - readableDate(time) - ); - } else if (status == "completed") { - return "Infrastructure update completed at " + readableDate(time); - } else if (status == "errored") { - return "This infrastructure encountered an error while updating."; - } - case "retry_delete": - case "delete": - if (status == "starting") { - return ( - "Infrastructure deletion in progress, started at " + - readableDate(time) - ); - } else if (status == "completed") { - return "Infrastructure deletion completed at " + readableDate(time); - } else if (status == "errored") { - return "This infrastructure encountered an error while deleting."; - } - } - }; - - const renderRerunButton = () => { - let buttonText = "Retry Operation"; - - if (operation.type == "create" || operation.type == "retry_create") { - buttonText = "Retry Creation"; - } else if (operation.type == "delete" || operation.type == "retry_delete") { - buttonText = "Retry Deletion"; - } else if (operation.type == "update") { - buttonText = "Retry"; - } - return ( - - ); - }; - - const renderLogs = () => { - if (!logs) { - return ( - - - - ); - } - - return logs.map((l, i) => {l}); - }; - - const renderOperationDetails = () => { - if (infra.latest_operation.id == operation.id) { - return ( - <> - Infrastructure progress: - - - ); - } - - return ( - <> - - {getOperationDescription( - operation.type, - operation.status, - operation.last_updated - )} - -
- - ); - }; - - return ( - - - back()}> - navigate_before - All Deploys - - - - Deployment Summary - {renderOperationDetails()} - {renderRerunButton()} - - - Configuration - - Your infrastructure was deployed with the following configuration: - - - - - - - - Deployment Logs - - The following are the Terraform logs from your deployment: - - - {renderLogs()} - - - ); -}; - -export default ExpandedOperation; - -const PorterFormContainer = styled.div` - position: relative; - min-width: 300px; -`; - -const Br = styled.div` - width: 100%; - height: 20px; -`; - -const StyledCard = styled.div` - display: grid; - grid-row-gap: 15px; - grid-template-columns: 1; -`; - -const BackArrowContainer = styled.div` - width: 100%; - height: 24px; -`; - -const BackArrow = styled.div` - > i { - color: #aaaabb; - font-size: 18px; - margin-right: 6px; - } - - color: #aaaabb; - display: flex; - align-items: center; - font-size: 14px; - cursor: pointer; - width: 120px; -`; - -const MetadataContainer = styled.div` - margin-bottom: 3px; - border-radius: 6px; - background: #2e3135; - padding: 0 20px 16px 20px; - overflow-y: auto; - min-height: 180px; - font-size: 13px; -`; - -const LogTitleContainer = styled.div` - padding: 0 20px; - margin-bottom: 20px; -`; - -const LogSectionContainer = styled.div` - margin-bottom: 3px; - border-radius: 6px; - background: #2e3135; - overflow: hidden; - max-height: 500px; - font-size: 13px; -`; - -const LogContainer = styled.div` - padding: 14px; - font-size: 13px; - background: #121318; - user-select: text; - overflow-wrap: break-word; - overflow-y: auto; - min-height: 55px; - color: #aaaabb; - height: 400px; -`; - -const Log = styled.div` - font-family: monospace, sans-serif; - font-size: 12px; - color: white; -`; diff --git a/dashboard/src/main/home/infrastructure/components/InfraResourceList.tsx b/dashboard/src/main/home/infrastructure/components/InfraResourceList.tsx deleted file mode 100644 index f4fcf526bf..0000000000 --- a/dashboard/src/main/home/infrastructure/components/InfraResourceList.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import { TFState } from "shared/types"; -import Placeholder from "components/OldPlaceholder"; - -type Props = { - infra_id: number; -}; - -const InfraResourceList: React.FunctionComponent = ({ infra_id }) => { - const [isLoading, setIsLoading] = useState(true); - const [hasError, setHasError] = useState(false); - const [infraState, setInfraState] = useState(null); - const { currentProject, setCurrentError } = useContext(Context); - - useEffect(() => { - api - .getInfraState( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra_id, - } - ) - .then(({ data }) => { - setInfraState(data); - - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject]); - - if (isLoading) { - return ( - - - - ); - } - - if (!infraState) { - return No resources available; - } - - if (hasError) { - return Error; - } - - const renderContents = () => { - return Object.keys({ ...(infraState?.resources || {}) }).map((key) => { - return ( - - {key} - - ); - }); - }; - - return ( - - {renderContents()} - - ); -}; - -export default InfraResourceList; - -const InfraResourceListWrapper = styled.div` - width: 100%; - height: 100%; -`; - -const ListContainer = styled.div` - width: 100%; - border-radius: 3px; - border: 1px solid #ffffff44; - max-height: 400px; - background: #ffffff11; - overflow-y: auto; -`; - -const StyledResource = styled.div` - display: flex; - width: 100%; - font-size: 13px; - border-bottom: 1px solid - ${(props: { lastItem: boolean; isSelected: boolean }) => - props.lastItem ? "#00000000" : "#606166"}; - color: #ffffff; - user-select: none; - align-items: center; - padding: 10px; - cursor: pointer; - background: #ffffff11; - :hover { - background: #ffffff22; - - > i { - background: #ffffff22; - } - } - - > img, - i { - width: 18px; - height: 18px; - margin-left: 12px; - margin-right: 12px; - font-size: 20px; - } -`; diff --git a/dashboard/src/main/home/infrastructure/components/InfraSettings.tsx b/dashboard/src/main/home/infrastructure/components/InfraSettings.tsx deleted file mode 100644 index afeaaa3f93..0000000000 --- a/dashboard/src/main/home/infrastructure/components/InfraSettings.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useContext, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Heading from "components/form-components/Heading"; -import SaveButton from "components/SaveButton"; -import Description from "components/Description"; -import ConfirmOverlay from "components/ConfirmOverlay"; - -type Props = { - infra_id: number; - onDelete: () => void; -}; - -const InfraSettings: React.FunctionComponent = ({ - infra_id, - onDelete, -}) => { - const { currentProject, setCurrentError, setCurrentOverlay } = useContext( - Context - ); - - const deleteInfra = () => { - api - .deleteInfra( - "", - {}, - { - project_id: currentProject.id, - infra_id: infra_id, - } - ) - .then(() => { - setCurrentOverlay(null); - onDelete(); - }) - .catch((err) => { - console.error(err); - setCurrentError(err.response?.data?.error); - }); - }; - - return ( - <> - - - Delete Infrastructure - - This will destroy all of the existing cloud infrastructure attached - to this module. - -
- - - setCurrentOverlay({ - message: `Are you sure you want to delete this infrastructure?`, - onYes: deleteInfra, - onNo: () => setCurrentOverlay(null), - }) - } - text="Delete Infrastructure" - color="#b91133" - disabled={false} - makeFlush={true} - clearPosition={true} - saveText="Deletion process started, see the Deploys tab for info." - /> -
-
- - ); -}; - -export default InfraSettings; - -const Br = styled.div` - width: 100%; - height: 20px; -`; - -const StyledCard = styled.div` - display: grid; - grid-row-gap: 15px; - grid-template-columns: 1; -`; - -const MetadataContainer = styled.div` - margin-bottom: 3px; - border-radius: 6px; - background: #2e3135; - padding: 0 20px; - overflow-y: auto; - min-height: 180px; - font-size: 13px; -`; diff --git a/dashboard/src/main/home/infrastructure/components/ProvisionInfra.tsx b/dashboard/src/main/home/infrastructure/components/ProvisionInfra.tsx deleted file mode 100644 index 0b91c0c0a8..0000000000 --- a/dashboard/src/main/home/infrastructure/components/ProvisionInfra.tsx +++ /dev/null @@ -1,561 +0,0 @@ -import React, { useContext, useState, useEffect } from "react"; -import styled from "styled-components"; - -import { Context } from "shared/Context"; -import api from "shared/api"; - -import Loading from "components/Loading"; -import TitleSection from "components/TitleSection"; - -import PorterFormWrapper from "components/porter-form/PorterFormWrapper"; -import Placeholder from "components/OldPlaceholder"; -import AWSCredentialsList from "./credentials/AWSCredentialList"; -import Heading from "components/form-components/Heading"; -import GCPCredentialsList from "./credentials/GCPCredentialList"; -import { getQueryParam, useRouting } from "shared/routing"; -import { - InfraTemplateMeta, - InfraTemplate, - InfraCredentials, - ClusterType, -} from "shared/types"; -import Description from "components/Description"; -import Select from "components/porter-form/field-components/Select"; -import ClusterList from "./credentials/ClusterList"; -import { useLocation, useParams } from "react-router"; -import qs from "qs"; -import AzureCredentialsList from "./credentials/AzureCredentialList"; - -type Props = {}; - -type ProvisionParams = { - name: string; -}; - -type ProvisionQueryParams = { - version?: string; -}; - -const ProvisionInfra: React.FunctionComponent = () => { - const { name } = useParams(); - const location = useLocation(); - const version = getQueryParam({ location }, "version"); - const origin = getQueryParam({ location }, "origin"); - const { currentProject, setCurrentError } = useContext(Context); - const [templates, setTemplates] = useState([]); - const [currentTemplate, setCurrentTemplate] = useState(null); - const [selectedClusterID, setSelectedClusterID] = useState(null); - const [currentCredential, setCurrentCredential] = useState( - null - ); - - const [isLoading, setIsLoading] = useState(false); - const [hasError, setHasError] = useState(false); - - const { pushFiltered } = useRouting(); - - useEffect(() => { - if (currentProject && !name) { - api - .listInfraTemplates( - "", - {}, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - let templates = data.sort((a, b) => - a.name > b.name ? 1 : b.name > a.name ? -1 : 0 - ); - - setTemplates(templates); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - } - }, [currentProject, name]); - - useEffect(() => { - if (currentProject && name) { - let templateVersion = version || "latest"; - - setIsLoading(true); - - api - .getInfraTemplate( - "", - {}, - { - project_id: currentProject.id, - version: templateVersion, - name: name, - } - ) - .then(({ data }) => { - setCurrentTemplate(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - } else if (!name) { - setCurrentTemplate(null); - } - }, [currentProject, name, version]); - - const onSubmit = (values: any) => { - setIsLoading(true); - - api - .provisionInfra( - "", - { - kind: currentTemplate.kind, - values: values, - aws_integration_id: currentCredential["aws_integration_id"], - do_integration_id: currentCredential["do_integration_id"], - gcp_integration_id: currentCredential["gcp_integration_id"], - azure_integration_id: currentCredential["azure_integration_id"], - cluster_id: selectedClusterID || null, - }, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - setIsLoading(false); - - if (origin) { - pushFiltered(origin, ["project_id"]); - } else if (data?.infra_id) { - pushFiltered(`/infrastructure/${data?.infra_id}`, ["project_id"]); - } else { - pushFiltered(`/infrastructure`, ["project_id"]); - } - }) - .catch((err) => { - console.error(err); - setIsLoading(false); - }); - }; - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - const renderIcon = (icon: string) => { - if (icon) { - return ; - } - - return ( - - layers - - ); - }; - - const renderTemplates = () => { - return templates.map((template) => { - let { name, icon, description } = template; - - return ( - - pushFiltered( - `/infrastructure/provision/${template.name}`, - ["project_id"], - { - version: template.version, - } - ) - } - > - {renderIcon(icon)} - {name} - {description} - - ); - }); - }; - - const renderStepContents = () => { - const numSteps = 2 + currentTemplate?.form?.isClusterScoped; - - // // if credentials need to be set and the list doesn't contain the necessary creds, - // // render a credentials form - if ( - currentTemplate.required_credential != "" && - currentCredential == null - ) { - if (currentTemplate.required_credential == "aws_integration_id") { - return ( - - Step 1 of {numSteps} - Link AWS Credentials - - setCurrentCredential({ - aws_integration_id: i, - }) - } - /> - - ); - } else if (currentTemplate.required_credential == "gcp_integration_id") { - return ( - - Step 1 of {numSteps} - Link GCP Credentials - - setCurrentCredential({ - gcp_integration_id: i, - }) - } - /> - - ); - } else if ( - currentTemplate.required_credential == "azure_integration_id" - ) { - return ( - - Step 1 of {numSteps} - Link Azure Credentials - - setCurrentCredential({ - azure_integration_id: i, - }) - } - /> - - ); - } - } - - if (currentTemplate?.form?.isClusterScoped && !selectedClusterID) { - return ( - - Step 2 of {numSteps} - Select a Cluster - { - setSelectedClusterID(cluster_id); - }} - /> - - ); - } - - return ( - - - Step {numSteps} of {numSteps} - Configure Settings - - - - - - ); - }; - - const renderTitleSection = () => { - if (currentTemplate) { - return ( - <> - {`Provision ${currentTemplate.name}`} - - - {`Input the required configuration settings.`} - - - - - ); - } - - return ( - <> - Provision Infrastructure - - - Select the infrastructure template you would like to use for - provisioning. - - - - - ); - }; - - const renderContents = () => { - if (currentTemplate) { - let { name, icon, description } = currentTemplate; - return ( - - - - pushFiltered(origin || `/infrastructure/provision`, [ - "project_id", - ]) - } - > - navigate_before - {origin ? "Back" : "All Templates"} - - - - - {renderIcon(icon)} - {name} - {description} - - {renderStepContents()} - - - ); - } - - return {renderTemplates()}; - }; - - return ( - - {renderTitleSection()} - {renderContents()} - - ); -}; - -export default ProvisionInfra; - -const LineBreak = styled.div` - width: calc(100% - 0px); - height: 1px; - background: #494b4f; - margin: 10px 0px 35px; -`; - -const Icon = styled.img` - height: 42px; - margin-top: 35px; - margin-bottom: 13px; -`; - -const Polymer = styled.div` - > i { - font-size: 34px; - margin-top: 38px; - margin-bottom: 20px; - } -`; - -const TemplateDescription = styled.div` - margin-bottom: 26px; - color: #ffffff66; - text-align: center; - font-weight: default; - padding: 0px 25px; - height: 2.4em; - font-size: 12px; - display: -webkit-box; - overflow: hidden; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; -`; - -const TemplateTitle = styled.div` - margin-bottom: 12px; - width: 80%; - text-align: center; - font-size: 14px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const TemplateBlock = styled.div` - border: 1px solid #ffffff00; - align-items: center; - user-select: none; - border-radius: 8px; - display: flex; - font-size: 13px; - font-weight: 500; - padding: 3px 0px 5px; - flex-direction: column; - align-item: center; - justify-content: space-between; - height: 200px; - cursor: pointer; - color: #ffffff; - position: relative; - background: #26282f; - box-shadow: 0 4px 15px 0px #00000044; - :hover { - background: #ffffff11; - } - - animation: fadeIn 0.3s 0s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const TemplateList = styled.div` - overflow: visible; - margin-top: 35px; - padding-bottom: 150px; - display: grid; - grid-column-gap: 25px; - grid-row-gap: 25px; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); -`; - -const TemplatesWrapper = styled.div` - position: relative; - min-width: 300px; - margin: 0 auto; -`; - -const StyledTitleSection = styled(TitleSection)` - display: flex; - align-items: center; - width: 50%; -`; - -const StepContainer = styled.div` - display: flex; - justify-content: space-between; -`; - -const TemplateMetadataContainer = styled.div` - min-width: 300px; - width: 27%; - height: 200px; - border: 1px solid #ffffff00; - align-items: center; - user-select: none; - border-radius: 8px; - display: flex; - font-size: 13px; - font-weight: 500; - padding: 3px 0px 5px; - flex-direction: column; - align-item: center; - justify-content: space-between; - color: #ffffff; - position: relative; - background: #26282f; - box-shadow: 0 4px 15px 0px #00000044; - animation: fadeIn 0.3s 0s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const ActionContainer = styled.div` - min-width: 500px; - width: 70%; - min-height: 600px; - border: 1px solid #ffffff00; - align-items: center; - user-select: none; - border-radius: 8px; - font-size: 13px; - font-weight: 500; - padding: 3px 0px 5px; - color: #ffffff; - position: relative; - background: #2e3135; - margin-left: 20px; - padding: 0 40px; - - animation: fadeIn 0.3s 0s; - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - -const BackArrowContainer = styled.div` - width: 100%; - height: 24px; -`; - -const BackArrow = styled.div` - > i { - color: #aaaabb; - font-size: 18px; - margin-right: 6px; - } - - color: #aaaabb; - display: flex; - align-items: center; - font-size: 14px; - cursor: pointer; - width: 120px; -`; - -const ExpandedContainer = styled.div` - display: grid; - grid-row-gap: 15px; - grid-template-columns: 1; -`; - -const FormContainer = styled.div` - position: relative; - margin: 20px 0; -`; - -const InfoSection = styled.div` - margin-top: 36px; - font-family: "Work Sans", sans-serif; - margin-left: 0px; - margin-bottom: 35px; -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialForm.tsx b/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialForm.tsx deleted file mode 100644 index 44a585df0f..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialForm.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import InputRow from "components/form-components/InputRow"; -import SelectRow from "components/form-components/SelectRow"; -import SaveButton from "components/SaveButton"; - -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import { Operation, OperationStatus, OperationType } from "shared/types"; -import { readableDate } from "shared/string_utils"; -import Placeholder from "components/OldPlaceholder"; - -type Props = { - setCreatedCredential: (aws_integration_id: number) => void; - cancel: () => void; -}; - -const regionOptions = [ - { value: "us-east-1", label: "US East (N. Virginia) us-east-1" }, - { value: "us-east-2", label: "US East (Ohio) us-east-2" }, - { value: "us-west-1", label: "US West (N. California) us-west-1" }, - { value: "us-west-2", label: "US West (Oregon) us-west-2" }, - { value: "af-south-1", label: "Africa (Cape Town) af-south-1" }, - { value: "ap-east-1", label: "Asia Pacific (Hong Kong) ap-east-1" }, - { value: "ap-south-1", label: "Asia Pacific (Mumbai) ap-south-1" }, - { value: "ap-northeast-2", label: "Asia Pacific (Seoul) ap-northeast-2" }, - { value: "ap-southeast-1", label: "Asia Pacific (Singapore) ap-southeast-1" }, - { value: "ap-southeast-2", label: "Asia Pacific (Sydney) ap-southeast-2" }, - { value: "ap-northeast-1", label: "Asia Pacific (Tokyo) ap-northeast-1" }, - { value: "ca-central-1", label: "Canada (Central) ca-central-1" }, - { value: "eu-central-1", label: "Europe (Frankfurt) eu-central-1" }, - { value: "eu-west-1", label: "Europe (Ireland) eu-west-1" }, - { value: "eu-west-2", label: "Europe (London) eu-west-2" }, - { value: "eu-south-1", label: "Europe (Milan) eu-south-1" }, - { value: "eu-west-3", label: "Europe (Paris) eu-west-3" }, - { value: "eu-north-1", label: "Europe (Stockholm) eu-north-1" }, - { value: "me-south-1", label: "Middle East (Bahrain) me-south-1" }, - { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" }, -]; - -const AWSCredentialForm: React.FunctionComponent = ({ - setCreatedCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [accessId, setAccessId] = useState(""); - const [secretKey, setSecretKey] = useState(""); - const [assumeRoleArn, setAssumeRoleArn] = useState(""); - const [buttonStatus, setButtonStatus] = useState(""); - const [awsRegion, setAWSRegion] = useState("us-east-1"); - const [isLoading, setIsLoading] = useState(false); - const [hasError, setHasError] = useState(false); - - const submit = () => { - setIsLoading(true); - - api - .createAWSIntegration( - "", - { - aws_region: awsRegion, - aws_access_key_id: accessId, - aws_secret_access_key: secretKey, - aws_assume_role_arn: assumeRoleArn, - }, - { - id: currentProject.id, - } - ) - .then(({ data }) => { - setCreatedCredential(data.id); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }; - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - return ( - <> - { - setAccessId(x); - }} - label="👤 AWS Access ID" - placeholder="ex: AKIAIOSFODNN7EXAMPLE" - width="100%" - isRequired={true} - /> - { - setSecretKey(x); - }} - label="🔒 AWS Secret Key" - placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○" - width="100%" - isRequired={true} - /> - { - setAWSRegion(x); - }} - label="📍 AWS Region" - /> - { - setAssumeRoleArn(x); - }} - label="👤 (Optional) AWS Assume Role ARN" - placeholder="ex: arn:aws:iam::01234567890:role/my_assumed_role" - width="100%" - isRequired={false} - /> - - - - - ); -}; - -export default AWSCredentialForm; - -const Flex = styled.div` - display: flex; - color: #ffffff; - align-items: center; - margin-top: 30px; - > i { - color: #aaaabb; - font-size: 20px; - margin-right: 10px; - } -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialList.tsx b/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialList.tsx deleted file mode 100644 index 20372674cf..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialList.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; -import AWSCredentialForm from "./AWSCredentialForm"; -import CredentialList from "./CredentialList"; -import Description from "components/Description"; - -type Props = { - selectCredential: (aws_integration_id: number) => void; -}; - -type AWSCredential = { - created_at: string; - id: number; - user_id: number; - project_id: number; - aws_arn: string; -}; - -const AWSCredentialsList: React.FunctionComponent = ({ - selectCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [isLoading, setIsLoading] = useState(true); - const [awsCredentials, setAWSCredentials] = useState(null); - const [shouldCreateCred, setShouldCreateCred] = useState(false); - const [hasError, setHasError] = useState(false); - - useEffect(() => { - api - .getAWSIntegration( - "", - {}, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setAWSCredentials(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject]); - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - const renderContents = () => { - if (shouldCreateCred) { - return ( - {}} - /> - ); - } - - return ( - <> - - Select your credentials from the list below, or create a new - credential: - - { - return { - id: cred.id, - display_name: cred.aws_arn, - created_at: cred.created_at, - }; - })} - selectCredential={selectCredential} - shouldCreateCred={() => setShouldCreateCred(true)} - addNewText="Add New AWS Credential" - /> - - ); - }; - - return {renderContents()}; -}; - -export default AWSCredentialsList; - -const AWSCredentialWrapper = styled.div` - margin-top: 20px; -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialForm.tsx b/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialForm.tsx deleted file mode 100644 index 3f7e6bf414..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialForm.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useContext, useState } from "react"; -import InputRow from "components/form-components/InputRow"; -import SaveButton from "components/SaveButton"; - -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; - -type Props = { - setCreatedCredential: (aws_integration_id: number) => void; - cancel: () => void; -}; - -const AzureCredentialForm: React.FunctionComponent = ({ - setCreatedCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [clientId, setClientId] = useState(""); - const [servicePrincipalKey, setServicePrincipalKey] = useState(""); - const [tenantId, setTenantId] = useState(""); - const [subscriptionId, setSubscriptionId] = useState(""); - const [buttonStatus, setButtonStatus] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [hasError, setHasError] = useState(false); - - const submit = () => { - setIsLoading(true); - - api - .createAzureIntegration( - "", - { - azure_client_id: clientId, - azure_subscription_id: subscriptionId, - azure_tenant_id: tenantId, - service_principal_key: servicePrincipalKey, - }, - { - id: currentProject.id, - } - ) - .then(({ data }) => { - setCreatedCredential(data.id); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }; - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - return ( - <> - { - setClientId(x); - }} - label="👤 Azure Client ID" - placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd" - width="100%" - isRequired={true} - /> - { - setServicePrincipalKey(x); - }} - label="🔒 Azure Service Principal Key" - placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○" - width="100%" - isRequired={true} - /> - { - setTenantId(x); - }} - label="Azure Tenant ID" - placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd" - width="100%" - isRequired={true} - /> - { - setSubscriptionId(x); - }} - label="Azure Subscription ID" - placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd" - width="100%" - isRequired={true} - /> - - - - - ); -}; - -export default AzureCredentialForm; - -const Flex = styled.div` - display: flex; - color: #ffffff; - align-items: center; - > i { - color: #aaaabb; - font-size: 20px; - margin-right: 10px; - } -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialList.tsx b/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialList.tsx deleted file mode 100644 index 1df5ed6de5..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialList.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; -import AzureCredentialForm from "./AzureCredentialForm"; -import CredentialList from "./CredentialList"; -import Description from "components/Description"; - -type Props = { - selectCredential: (azure_integration_id: number) => void; -}; - -type AzureCredential = { - created_at: string; - id: number; - user_id: number; - project_id: number; - azure_client_id: string; -}; - -const AzureCredentialsList: React.FunctionComponent = ({ - selectCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [isLoading, setIsLoading] = useState(true); - const [azCredentials, setAzureCredentials] = useState( - null - ); - const [shouldCreateCred, setShouldCreateCred] = useState(false); - const [hasError, setHasError] = useState(false); - - useEffect(() => { - api - .getAzureIntegration( - "", - {}, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setAzureCredentials(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject]); - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - const renderContents = () => { - if (shouldCreateCred) { - return ( - {}} - /> - ); - } - - return ( - <> - - Select your credentials from the list below, or create a new - credential: - - { - return { - id: cred.id, - display_name: cred.azure_client_id, - created_at: cred.created_at, - }; - })} - selectCredential={selectCredential} - shouldCreateCred={() => setShouldCreateCred(true)} - addNewText="Add New Azure Credential" - /> - - ); - }; - - return {renderContents()}; -}; - -export default AzureCredentialsList; - -const AzureCredentialWrapper = styled.div` - margin-top: 20px; -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/ClusterList.tsx b/dashboard/src/main/home/infrastructure/components/credentials/ClusterList.tsx deleted file mode 100644 index c8a8e7e87c..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/ClusterList.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; -import Description from "components/Description"; -import { ClusterType } from "shared/types"; -import SelectRow from "components/form-components/SelectRow"; -import SaveButton from "components/SaveButton"; - -type Props = { - selectCluster: (cluster_id: number) => void; -}; - -const ClusterList: React.FunctionComponent = ({ selectCluster }) => { - const { currentProject, setCurrentError } = useContext(Context); - const [isLoading, setIsLoading] = useState(true); - const [clusters, setClusters] = useState([]); - const [selectedClusterID, setSelectedClusterID] = useState(); - const [hasError, setHasError] = useState(false); - - useEffect(() => { - api - .getClusters( - "", - {}, - { - id: currentProject.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setClusters(data); - setSelectedClusterID(data[0]?.id); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject]); - - if (hasError) { - return Error; - } - - if (isLoading || !clusters) { - return ( - - - - ); - } - - if (clusters.length == 0) { - return ( - - At least one cluster must exist to create this resource - - ); - } - - return ( - <> - - Select your credentials from the list below, or create a new credential: - - { - return { - label: cluster.name, - value: "" + cluster.id, - }; - })} - width="100%" - scrollBuffer={true} - value={"" + selectedClusterID} - dropdownMaxHeight="240px" - setActiveValue={(x: string) => { - setSelectedClusterID(parseInt(x)); - }} - label="Cluster Options" - /> - selectCluster(selectedClusterID)} - makeFlush={true} - clearPosition={true} - statusPosition={"right"} - /> - - ); -}; - -export default ClusterList; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/CredentialList.tsx b/dashboard/src/main/home/infrastructure/components/credentials/CredentialList.tsx deleted file mode 100644 index 40dadb3082..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/CredentialList.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import { readableDate } from "shared/string_utils"; - -type Props = { - selectCredential: (id: number) => void; - credentials: GenericCredential[]; - addNewText: string; - shouldCreateCred: () => void; - isLink?: boolean; - linkHref?: string; -}; - -type GenericCredential = { - id: number; - display_name: string; - created_at: string; -}; - -const CredentialList: React.FunctionComponent = (props) => { - const renderCreateSection = () => { - let inner = ( - - account_circle - {props.addNewText} - - ); - - if (props.isLink) { - return {inner}; - } - - return ( - {inner} - ); - }; - - return ( - <> - {props.credentials.map((cred) => { - return ( - props.selectCredential(cred.id)} - > - - account_circle - {cred.display_name || "Name N/A"} - - Connected at {readableDate(cred.created_at)} - - ); - })} - {renderCreateSection()} - - ); -}; - -export default CredentialList; - -const PreviewRow = styled.div` - display: flex; - align-items: center; - padding: 12px 15px; - color: #ffffff55; - background: #ffffff01; - border: 1px solid #aaaabb; - justify-content: space-between; - font-size: 13px; - border-radius: 5px; - cursor: pointer; - margin: 16px 0; - - :hover { - background: #ffffff10; - } -`; - -const Flex = styled.div` - display: flex; - color: #ffffff; - align-items: center; - > i { - color: #aaaabb; - font-size: 20px; - margin-right: 10px; - } -`; - -const Right = styled.div` - text-align: right; -`; - -const CreateNewRow = styled(PreviewRow)` - background: none; -`; - -const CreateNewRowLink = styled.a` - background: none; - display: flex; - align-items: center; - padding: 12px 15px; - color: #ffffff55; - background: #ffffff01; - border: 1px solid #aaaabb; - justify-content: space-between; - font-size: 13px; - border-radius: 5px; - cursor: pointer; - margin: 16px 0; - - :hover { - background: #ffffff10; - } -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialForm.tsx b/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialForm.tsx deleted file mode 100644 index e89245216f..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialForm.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useContext, useState } from "react"; -import InputRow from "components/form-components/InputRow"; -import SaveButton from "components/SaveButton"; - -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; -import Helper from "components/form-components/Helper"; -import UploadArea from "components/form-components/UploadArea"; - -type Props = { - setCreatedCredential: (aws_integration_id: number) => void; - cancel: () => void; -}; - -const GCPCredentialForm: React.FunctionComponent = ({ - setCreatedCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [buttonStatus, setButtonStatus] = useState(""); - const [projectId, setProjectId] = useState(""); - const [serviceAccountKey, setServiceAccountKey] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [hasError, setHasError] = useState(false); - - const submit = () => { - setIsLoading(true); - api - .createGCPIntegration( - "", - { - gcp_key_data: serviceAccountKey, - gcp_project_id: projectId, - }, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - setCreatedCredential(data.id); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }; - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - return ( - <> - { - setProjectId(x); - }} - label="🏷️ GCP Project ID" - placeholder="ex: blindfold-ceiling-24601" - width="100%" - isRequired={true} - /> - - Service account credentials for GCP permissions. - setServiceAccountKey(x)} - label="🔒 GCP Key Data (JSON)" - placeholder="Choose a file or drag it here." - width="100%" - height="100%" - isRequired={true} - /> - - - - - ); -}; - -export default GCPCredentialForm; - -const Flex = styled.div` - display: flex; - color: #ffffff; - align-items: center; - > i { - color: #aaaabb; - font-size: 20px; - margin-right: 10px; - } -`; diff --git a/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialList.tsx b/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialList.tsx deleted file mode 100644 index 70b27c82b3..0000000000 --- a/dashboard/src/main/home/infrastructure/components/credentials/GCPCredentialList.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import { Context } from "shared/Context"; -import api from "shared/api"; -import styled from "styled-components"; -import Loading from "components/Loading"; -import Placeholder from "components/OldPlaceholder"; -import GCPCredentialForm from "./GCPCredentialForm"; -import CredentialList from "./CredentialList"; -import Description from "components/Description"; - -type Props = { - selectCredential: (gcp_integration_id: number) => void; -}; - -type GCPCredential = { - created_at: string; - id: number; - user_id: number; - project_id: number; - gcp_sa_email: string; -}; - -const GCPCredentialsList: React.FunctionComponent = ({ - selectCredential, -}) => { - const { currentProject, setCurrentError } = useContext(Context); - const [isLoading, setIsLoading] = useState(true); - const [gcpCredentials, setGCPCredentials] = useState(null); - const [shouldCreateCred, setShouldCreateCred] = useState(false); - const [hasError, setHasError] = useState(false); - - useEffect(() => { - api - .getGCPIntegration( - "", - {}, - { - project_id: currentProject.id, - } - ) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw Error("Data is not an array"); - } - - setGCPCredentials(data); - setIsLoading(false); - }) - .catch((err) => { - console.error(err); - setHasError(true); - setCurrentError(err.response?.data?.error); - setIsLoading(false); - }); - }, [currentProject]); - - if (hasError) { - return Error; - } - - if (isLoading) { - return ( - - - - ); - } - - const renderContents = () => { - if (shouldCreateCred) { - return ( - {}} - /> - ); - } - - return ( - <> - - Select your credentials from the list below, or create a new - credential: - - { - return { - id: cred.id, - display_name: cred.gcp_sa_email, - created_at: cred.created_at, - }; - })} - selectCredential={selectCredential} - shouldCreateCred={() => setShouldCreateCred(true)} - addNewText="Add New GCP Credential" - /> - - ); - }; - - return {renderContents()}; -}; - -export default GCPCredentialsList; - -const GCPCredentialWrapper = styled.div` - margin-top: 20px; -`; diff --git a/dashboard/src/main/home/integrations/edit-integration/DockerHubForm.tsx b/dashboard/src/main/home/integrations/edit-integration/DockerHubForm.tsx deleted file mode 100644 index 4b89a5ef06..0000000000 --- a/dashboard/src/main/home/integrations/edit-integration/DockerHubForm.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import InputRow from "components/form-components/InputRow"; -import SaveButton from "components/SaveButton"; - -type PropsType = { - closeForm: () => void; -}; - -type StateType = { - registryURL: string; - dockerEmail: string; - dockerUsername: string; - dockerPassword: string; -}; - -export default class DockerHubForm extends Component { - state = { - registryURL: "", - dockerEmail: "", - dockerUsername: "", - dockerPassword: "", - }; - - isDisabled = (): boolean => { - let { - registryURL, - dockerEmail, - dockerUsername, - dockerPassword, - } = this.state; - if ( - registryURL === "" || - dockerEmail === "" || - dockerUsername === "" || - dockerPassword === "" - ) { - return true; - } - return false; - }; - - handleSubmit = () => { - // TODO: implement once api is restructured - }; - - render() { - return ( - - - this.setState({ registryURL: x })} - label="📦 Registry URL" - placeholder="ex: index.docker.io" - width="100%" - /> - this.setState({ dockerEmail: x })} - label="✉️ Docker Email" - placeholder="ex: captain@ahab.com" - width="100%" - /> - this.setState({ dockerUsername: x })} - label="👤 Docker Username" - placeholder="ex: whale_watcher_2000" - width="100%" - /> - this.setState({ dockerPassword: x })} - label="🔒 Docker Password" - placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○" - width="100%" - /> - - - - ); - } -} - -const CredentialWrapper = styled.div` - padding: 5px 40px 25px; - background: #ffffff11; - border-radius: 5px; -`; - -const StyledForm = styled.div` - position: relative; - padding-bottom: 75px; -`; diff --git a/dashboard/src/main/home/integrations/edit-integration/ECRForm.tsx b/dashboard/src/main/home/integrations/edit-integration/ECRForm.tsx deleted file mode 100644 index 778e7ee124..0000000000 --- a/dashboard/src/main/home/integrations/edit-integration/ECRForm.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import { Context } from "shared/Context"; -import api from "shared/api"; - -import InputRow from "components/form-components/InputRow"; -import SaveButton from "components/SaveButton"; -import Heading from "components/form-components/Heading"; -import Helper from "components/form-components/Helper"; - -type PropsType = { - closeForm: () => void; -}; - -type StateType = { - credentialsName: string; - awsRegion: string; - awsAccessId: string; - awsSecretKey: string; -}; - -export default class ECRForm extends Component { - state = { - credentialsName: "", - awsRegion: "", - awsAccessId: "", - awsSecretKey: "", - }; - - isDisabled = (): boolean => { - let { awsRegion, awsAccessId, awsSecretKey, credentialsName } = this.state; - if ( - awsRegion === "" || - awsAccessId === "" || - awsSecretKey === "" || - credentialsName === "" - ) { - return true; - } - return false; - }; - - catchErr = (err: any) => console.log(err); - - handleSubmit = () => { - let { awsRegion, awsAccessId, awsSecretKey, credentialsName } = this.state; - let { currentProject } = this.context; - - api - .createAWSIntegration( - "", - { - aws_region: awsRegion, - aws_access_key_id: awsAccessId, - aws_secret_access_key: awsSecretKey, - }, - { id: currentProject.id } - ) - .then((res) => - api.connectECRRegistry( - "", - { - name: credentialsName, - aws_integration_id: res.data.id, - }, - { id: currentProject.id } - ) - ) - .then(() => this.props.closeForm()) - .catch(this.catchErr); - }; - - render() { - return ( - - - Porter Settings - - Give a name to this set of registry credentials (just for Porter). - - this.setState({ credentialsName: x })} - label="🏷️ Registry Name" - placeholder="ex: paper-straw" - width="100%" - /> - AWS Settings - AWS access credentials. - this.setState({ awsRegion: x })} - label="📍 AWS Region" - placeholder="ex: mars-north-12" - width="100%" - /> - this.setState({ awsAccessId: x })} - label="👤 AWS Access ID" - placeholder="ex: AKIAIOSFODNN7EXAMPLE" - width="100%" - /> - this.setState({ awsSecretKey: x })} - label="🔒 AWS Secret Key" - placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○" - width="100%" - /> - - - - ); - } -} - -ECRForm.contextType = Context; - -const CredentialWrapper = styled.div` - padding: 5px 40px 25px; - background: #ffffff11; - border-radius: 5px; -`; - -const StyledForm = styled.div` - position: relative; - padding-bottom: 75px; -`; diff --git a/dashboard/src/main/home/integrations/edit-integration/EKSForm.tsx b/dashboard/src/main/home/integrations/edit-integration/EKSForm.tsx deleted file mode 100644 index 294716480a..0000000000 --- a/dashboard/src/main/home/integrations/edit-integration/EKSForm.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { Component } from "react"; -import styled from "styled-components"; - -import InputRow from "components/form-components/InputRow"; -import TextArea from "components/form-components/TextArea"; -import SaveButton from "components/SaveButton"; -import Heading from "components/form-components/Heading"; -import Helper from "components/form-components/Helper"; - -type PropsType = { - closeForm: () => void; -}; - -type StateType = { - clusterName: string; - clusterEndpoint: string; - clusterCA: string; - awsAccessId: string; - awsSecretKey: string; -}; - -export default class EKSForm extends Component { - state = { - clusterName: "", - clusterEndpoint: "", - clusterCA: "", - awsAccessId: "", - awsSecretKey: "", - }; - - isDisabled = (): boolean => { - let { - clusterName, - clusterEndpoint, - clusterCA, - awsAccessId, - awsSecretKey, - } = this.state; - if ( - clusterName === "" || - clusterEndpoint === "" || - clusterCA === "" || - awsAccessId === "" || - awsSecretKey === "" - ) { - return true; - } - return false; - }; - - handleSubmit = () => { - // TODO: implement once api is restructured - }; - - render() { - return ( - - - Cluster Settings - Credentials for accessing your GKE cluster. - this.setState({ clusterName: x })} - label="🏷️ Cluster Name" - placeholder="ex: briny-pagelet" - width="100%" - /> - this.setState({ clusterEndpoint: x })} - label="🌐 Cluster Endpoint" - placeholder="ex: 00.00.000.00" - width="100%" - /> -