Procházet zdrojové kódy

Merge branch 'healthChecksStacks' of github.com:porter-dev/porter

Justin Rhee před 3 roky
rodič
revize
40e0374044

+ 3 - 0
dashboard/src/components/TabSelector.tsx

@@ -89,12 +89,15 @@ const Buffer = styled.div`
 const TabWrapper = styled.div`
   display: flex;
   overflow-x: auto;
+  overflow-y: auto;
+
   padding-bottom: 15px;
   margin-bottom: -15px;
 `;
 
 const Tab = styled.div`
   height: 30px;
+  overflow-y: auto;
   margin-right: ${(props: { lastItem: boolean; highlight: string }) =>
     props.lastItem ? "" : "30px"};
   display: flex;

+ 672 - 78
dashboard/src/main/home/app-dashboard/new-app-flow/WebTabs.tsx

@@ -1,25 +1,38 @@
 import Input from "components/porter/Input";
-import React, { useEffect } from "react"
+import React, { useEffect, useRef } from "react";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
 import Checkbox from "components/porter/Checkbox";
 import { WebService } from "./serviceTypes";
-import { Height } from "react-animate-height";
+import AnimateHeight, { Height } from "react-animate-height";
+import styled from "styled-components";
 
 interface Props {
-  service: WebService
-  editService: (service: WebService) => void
-  setHeight: (height: Height) => void
+  service: WebService;
+  editService: (service: WebService) => void;
+  setHeight: (height: Height) => void;
 }
 
-const WebTabs: React.FC<Props> = ({
-  service,
-  editService,
-  setHeight,
-}) => {
-  const [currentTab, setCurrentTab] = React.useState<string>('main');
-
+const WebTabs: React.FC<Props> = ({ service, editService, setHeight }) => {
+  const [currentTab, setCurrentTab] = React.useState<string>("main");
+  const [showSettingsLive, setShowSettingsLive] = React.useState<boolean>(
+    false
+  );
+  const [showSettingsStart, setShowSettingsStart] = React.useState<boolean>(
+    false
+  );
+  const [showSettingsReady, setShowSettingsReady] = React.useState<boolean>(
+    false
+  );
+  const containerRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    calculateContainerHeight();
+  }, [currentTab]);
+  const calculateContainerHeight = () => {
+    const containerHeight = containerRef.current?.offsetHeight || 0;
+    setHeight(containerHeight);
+  };
   const renderMain = () => {
     return (
       <>
@@ -43,7 +56,12 @@ const WebTabs: React.FC<Props> = ({
           value={service.startCommand.value}
           width="300px"
           disabled={service.startCommand.readOnly}
-          setValue={(e) => { editService({ ...service, startCommand: { readOnly: false, value: e } }) }}
+          setValue={(e) => {
+            editService({
+              ...service,
+              startCommand: { readOnly: false, value: e },
+            });
+          }}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         />
         <Spacer y={1} />
@@ -53,20 +71,33 @@ const WebTabs: React.FC<Props> = ({
           value={service.port.value}
           disabled={service.port.readOnly}
           width="300px"
-          setValue={(e) => { editService({ ...service, port: { readOnly: false, value: e } }) }}
+          setValue={(e) => {
+            editService({ ...service, port: { readOnly: false, value: e } });
+          }}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         />
         <Spacer y={1} />
         <Checkbox
           checked={service.ingress.enabled.value}
           disabled={service.ingress.enabled.readOnly}
-          toggleChecked={() => { editService({ ...service, ingress: { ...service.ingress, enabled: { readOnly: false, value: !service.ingress.enabled.value } } }) }}
+          toggleChecked={() => {
+            editService({
+              ...service,
+              ingress: {
+                ...service.ingress,
+                enabled: {
+                  readOnly: false,
+                  value: !service.ingress.enabled.value,
+                },
+              },
+            });
+          }}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         >
           <Text color="helper">Generate a Porter URL for external traffic</Text>
         </Checkbox>
       </>
-    )
+    );
   };
 
   const renderResources = () => {
@@ -79,7 +110,9 @@ const WebTabs: React.FC<Props> = ({
           value={service.cpu.value}
           disabled={service.cpu.readOnly}
           width="300px"
-          setValue={(e) => { editService({ ...service, cpu: { readOnly: false, value: e } }) }}
+          setValue={(e) => {
+            editService({ ...service, cpu: { readOnly: false, value: e } });
+          }}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         />
         <Spacer y={1} />
@@ -89,7 +122,9 @@ const WebTabs: React.FC<Props> = ({
           value={service.ram.value}
           disabled={service.ram.readOnly}
           width="300px"
-          setValue={(e) => { editService({ ...service, ram: { readOnly: false, value: e } }) }}
+          setValue={(e) => {
+            editService({ ...service, ram: { readOnly: false, value: e } });
+          }}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         />
         <Spacer y={1} />
@@ -97,15 +132,37 @@ const WebTabs: React.FC<Props> = ({
           label="Replicas"
           placeholder="ex: 1"
           value={service.replicas.value}
-          disabled={service.replicas.readOnly || service.autoscaling.enabled.value}
+          disabled={
+            service.replicas.readOnly || service.autoscaling.enabled.value
+          }
           width="300px"
-          setValue={(e) => { editService({ ...service, replicas: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.replicas.readOnly ? "You may only edit this field in your porter.yaml." : "Disable autoscaling to specify replicas."}
+          setValue={(e) => {
+            editService({
+              ...service,
+              replicas: { readOnly: false, value: e },
+            });
+          }}
+          disabledTooltip={
+            service.replicas.readOnly
+              ? "You may only edit this field in your porter.yaml."
+              : "Disable autoscaling to specify replicas."
+          }
         />
         <Spacer y={1} />
         <Checkbox
           checked={service.autoscaling.enabled.value}
-          toggleChecked={() => { editService({ ...service, autoscaling: { ...service.autoscaling, enabled: { readOnly: false, value: !service.autoscaling.enabled.value } } }) }}
+          toggleChecked={() => {
+            editService({
+              ...service,
+              autoscaling: {
+                ...service.autoscaling,
+                enabled: {
+                  readOnly: false,
+                  value: !service.autoscaling.enabled.value,
+                },
+              },
+            });
+          }}
           disabled={service.autoscaling.enabled.readOnly}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         >
@@ -116,96 +173,633 @@ const WebTabs: React.FC<Props> = ({
           label="Min replicas"
           placeholder="ex: 1"
           value={service.autoscaling.minReplicas.value}
-          disabled={service.autoscaling.minReplicas.readOnly || !service.autoscaling.enabled.value}
+          disabled={
+            service.autoscaling.minReplicas.readOnly ||
+            !service.autoscaling.enabled.value
+          }
           width="300px"
-          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, minReplicas: { readOnly: false, value: e } } }) }}
-          disabledTooltip={service.autoscaling.minReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify min replicas."}
+          setValue={(e) => {
+            editService({
+              ...service,
+              autoscaling: {
+                ...service.autoscaling,
+                minReplicas: { readOnly: false, value: e },
+              },
+            });
+          }}
+          disabledTooltip={
+            service.autoscaling.minReplicas.readOnly
+              ? "You may only edit this field in your porter.yaml."
+              : "Enable autoscaling to specify min replicas."
+          }
         />
         <Spacer y={1} />
         <Input
           label="Max replicas"
           placeholder="ex: 10"
           value={service.autoscaling.maxReplicas.value}
-          disabled={service.autoscaling.maxReplicas.readOnly || !service.autoscaling.enabled.value}
+          disabled={
+            service.autoscaling.maxReplicas.readOnly ||
+            !service.autoscaling.enabled.value
+          }
           width="300px"
-          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, maxReplicas: { readOnly: false, value: e } } }) }}
-          disabledTooltip={service.autoscaling.maxReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify max replicas."}
+          setValue={(e) => {
+            editService({
+              ...service,
+              autoscaling: {
+                ...service.autoscaling,
+                maxReplicas: { readOnly: false, value: e },
+              },
+            });
+          }}
+          disabledTooltip={
+            service.autoscaling.maxReplicas.readOnly
+              ? "You may only edit this field in your porter.yaml."
+              : "Enable autoscaling to specify max replicas."
+          }
         />
         <Spacer y={1} />
         <Input
           label="Target CPU utilization (%)"
           placeholder="ex: 50"
           value={service.autoscaling.targetCPUUtilizationPercentage.value}
-          disabled={service.autoscaling.targetCPUUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
+          disabled={
+            service.autoscaling.targetCPUUtilizationPercentage.readOnly ||
+            !service.autoscaling.enabled.value
+          }
           width="300px"
-          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetCPUUtilizationPercentage: { readOnly: false, value: e } } }) }}
-          disabledTooltip={service.autoscaling.targetCPUUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target CPU utilization."}
+          setValue={(e) => {
+            editService({
+              ...service,
+              autoscaling: {
+                ...service.autoscaling,
+                targetCPUUtilizationPercentage: { readOnly: false, value: e },
+              },
+            });
+          }}
+          disabledTooltip={
+            service.autoscaling.targetCPUUtilizationPercentage.readOnly
+              ? "You may only edit this field in your porter.yaml."
+              : "Enable autoscaling to specify target CPU utilization."
+          }
         />
         <Spacer y={1} />
         <Input
           label="Target RAM utilization (%)"
           placeholder="ex: 50"
           value={service.autoscaling.targetMemoryUtilizationPercentage.value}
-          disabled={service.autoscaling.targetMemoryUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
+          disabled={
+            service.autoscaling.targetMemoryUtilizationPercentage.readOnly ||
+            !service.autoscaling.enabled.value
+          }
           width="300px"
-          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetMemoryUtilizationPercentage: { readOnly: false, value: e } } }) }}
-          disabledTooltip={service.autoscaling.targetMemoryUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target RAM utilization."}
+          setValue={(e) => {
+            editService({
+              ...service,
+              autoscaling: {
+                ...service.autoscaling,
+                targetMemoryUtilizationPercentage: {
+                  readOnly: false,
+                  value: e,
+                },
+              },
+            });
+          }}
+          disabledTooltip={
+            service.autoscaling.targetMemoryUtilizationPercentage.readOnly
+              ? "You may only edit this field in your porter.yaml."
+              : "Enable autoscaling to specify target RAM utilization."
+          }
         />
       </>
-    )
+    );
   };
 
-  const renderAdvanced = () => {
+  const renderHealth = () => {
     return (
       <>
         <Spacer y={1} />
-        <Input
-          label={
-            <>
-              <span>Custom domain</span>
-              <a
-                href="https://docs.porter.run/deploying-applications/https-and-domains/custom-domains"
-                target="_blank"
-              >
-                &nbsp;(?)
-              </a>
-            </>}
-          placeholder="ex: my-app.my-domain.com"
-          value={service.ingress.hosts.value}
-          disabled={service.ingress.hosts.readOnly}
-          width="300px"
-          setValue={(e) => { editService({ ...service, ingress: { ...service.ingress, hosts: { readOnly: false, value: e } } }) }}
-          disabledTooltip={"You may only edit this field in your porter.yaml."}
-        />
+        <Checkbox
+          checked={service.health.livenessProbe?.enabled.value}
+          toggleChecked={() => {
+            editService({
+              ...service,
+              health: {
+                ...service.health,
+                livenessProbe: {
+                  ...service.health.livenessProbe,
+                  enabled: {
+                    readOnly: false,
+                    value: !service.health.livenessProbe?.enabled.value,
+                  },
+                },
+              },
+            });
+          }}
+        >
+          <Text color="helper">Enable Liveness Probe</Text>
+        </Checkbox>
+        <>
+          <StyledAdvancedBuildSettings
+            showSettings={
+              showSettingsLive && service.health.livenessProbe?.enabled.value
+            }
+            isCurrent={true}
+            onClick={() => {
+              if (service.health.livenessProbe?.enabled.value) {
+                setShowSettingsLive(!showSettingsLive);
+              }
+            }}
+            disabled={!service.health.livenessProbe?.enabled.value}
+          >
+            <AdvancedBuildTitle>
+              <i className="material-icons dropdown">arrow_drop_down</i>
+              Configure Liveness Probe Settings
+            </AdvancedBuildTitle>
+          </StyledAdvancedBuildSettings>
+          <AnimateHeight
+            height={
+              showSettingsLive && service.health.livenessProbe?.enabled.value
+                ? "auto"
+                : 0
+            }
+            duration={1000}
+          >
+            <Spacer y={0.5} />
+            <Input
+              label="Liveness Check Endpoint "
+              placeholder="ex: 80"
+              value={service.health.livenessProbe.path.value}
+              disabled={service.health.livenessProbe.path.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    livenessProbe: {
+                      ...service.health.livenessProbe,
+                      path: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+            <Input
+              label="Failure Threshold"
+              placeholder="ex: 80"
+              value={service.health.livenessProbe.failureThreshold.value}
+              disabled={service.health.livenessProbe.failureThreshold.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    livenessProbe: {
+                      ...service.health.livenessProbe,
+                      failureThreshold: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+            <Input
+              label="Retry Interval"
+              placeholder="ex: 80"
+              value={service.health.livenessProbe.periodSeconds.value}
+              disabled={service.health.livenessProbe.periodSeconds.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    livenessProbe: {
+                      ...service.health.livenessProbe,
+                      periodSeconds: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+          </AnimateHeight>
+        </>
+        <Spacer y={1} />
+        <Checkbox
+          checked={service.health.startupProbe?.enabled.value}
+          toggleChecked={() => {
+            editService({
+              ...service,
+              health: {
+                ...service.health,
+                startupProbe: {
+                  ...service.health.startupProbe,
+                  enabled: {
+                    readOnly: false,
+                    value: !service.health.startupProbe?.enabled.value,
+                  },
+                },
+              },
+            });
+          }}
+          //disabled={service.autoscaling.enabled.readOnly}
+          //disabledTooltip={"You may only edit this field in your porter.yaml."}
+        >
+          <Text color="helper">Enable Start Up Probe</Text>
+        </Checkbox>
+        <>
+          <StyledAdvancedBuildSettings
+            showSettings={
+              showSettingsStart && service.health.startupProbe?.enabled.value
+            }
+            isCurrent={true}
+            onClick={() => {
+              if (service.health.startupProbe?.enabled.value) {
+                setShowSettingsStart(!showSettingsStart);
+              }
+            }}
+            disabled={!service.health.startupProbe?.enabled.value}
+          >
+            <AdvancedBuildTitle>
+              <i className="material-icons dropdown">arrow_drop_down</i>
+              Configure Start Up Probe Settings
+            </AdvancedBuildTitle>
+          </StyledAdvancedBuildSettings>
+          <AnimateHeight
+            height={
+              showSettingsStart && service.health.startupProbe?.enabled.value
+                ? "auto"
+                : 0
+            }
+            duration={1000}
+          >
+            <Spacer y={0.5} />
+
+            <Input
+              label="Start Up Check Endpoint "
+              placeholder="ex: 80"
+              value={service.health.startupProbe.path.value}
+              disabled={service.health.startupProbe.path.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    startupProbe: {
+                      ...service.health.startupProbe,
+                      path: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+
+            <Input
+              label="Failure Threshold"
+              placeholder="ex: 80"
+              value={service.health.startupProbe.failureThreshold.value}
+              disabled={service.health.startupProbe.failureThreshold.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    startupProbe: {
+                      ...service.health.startupProbe,
+                      failureThreshold: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+            <Input
+              label="Retry Interval"
+              placeholder="ex: 80"
+              value={service.health.startupProbe.periodSeconds.value}
+              disabled={service.health.startupProbe.periodSeconds.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    startupProbe: {
+                      ...service.health.startupProbe,
+                      periodSeconds: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+          </AnimateHeight>
+        </>
+
+        <Spacer y={1} />
+        <Checkbox
+          checked={service.health.readinessProbe?.enabled.value}
+          toggleChecked={() => {
+            editService({
+              ...service,
+              health: {
+                ...service.health,
+                readinessProbe: {
+                  ...service.health.readinessProbe,
+                  enabled: {
+                    readOnly: false,
+                    value: !service.health.readinessProbe?.enabled.value,
+                  },
+                },
+              },
+            });
+          }}
+          //disabled={service.autoscaling.enabled.readOnly}
+          //disabledTooltip={"You may only edit this field in your porter.yaml."}
+        >
+          <Text color="helper">Enable Readiness Probe</Text>
+        </Checkbox>
+
+        <>
+          <StyledAdvancedBuildSettings
+            showSettings={
+              showSettingsReady && service.health.readinessProbe?.enabled.value
+            }
+            isCurrent={true}
+            onClick={() => {
+              if (service.health.readinessProbe?.enabled.value) {
+                setShowSettingsReady(!showSettingsReady);
+              }
+            }}
+            disabled={!service.health.readinessProbe?.enabled.value}
+          >
+            <AdvancedBuildTitle>
+              <i className="material-icons dropdown">arrow_drop_down</i>
+              Configure Readiness Probe settings
+            </AdvancedBuildTitle>
+          </StyledAdvancedBuildSettings>
+          <AnimateHeight
+            height={
+              showSettingsReady && service.health.readinessProbe?.enabled.value
+                ? "auto"
+                : 0
+            }
+            duration={1000}
+          >
+            <Spacer y={0.5} />
+
+            <Input
+              label="Readiness Check Endpoint "
+              placeholder="ex: 80"
+              value={service.health.readinessProbe.path.value}
+              disabled={service.health.readinessProbe.path.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    readinessProbe: {
+                      ...service.health.readinessProbe,
+                      path: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+
+            <Input
+              label="Failure Threshold"
+              placeholder="ex: 80"
+              value={service.health.readinessProbe.failureThreshold.value}
+              disabled={service.health.readinessProbe.failureThreshold.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    readinessProbe: {
+                      ...service.health.readinessProbe,
+                      failureThreshold: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            <Spacer y={0.5} />
+
+            <Input
+              label="Initial Delay Threshold"
+              placeholder="ex: 80"
+              value={service.health.readinessProbe.initialDelaySeconds.value}
+              disabled={
+                service.health.readinessProbe.initialDelaySeconds.readOnly
+              }
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  health: {
+                    ...service.health,
+                    readinessProbe: {
+                      ...service.health.readinessProbe,
+                      initialDelaySeconds: {
+                        readOnly: false,
+                        value: e,
+                      },
+                    },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+          </AnimateHeight>
+        </>
       </>
     );
   };
 
+  const renderAdvanced = () => {
+    return (
+      <>
+        <ScrollableDiv>
+          <>
+            <Spacer y={1} />
+            <Input
+              label={
+                <>
+                  <span>Custom domain</span>
+                  <a
+                    href="https://docs.porter.run/deploying-applications/https-and-domains/custom-domains"
+                    target="_blank"
+                  >
+                    &nbsp;(?)
+                  </a>
+                </>
+              }
+              placeholder="ex: my-app.my-domain.com"
+              value={service.ingress.hosts.value}
+              disabled={service.ingress.hosts.readOnly}
+              width="300px"
+              setValue={(e) => {
+                editService({
+                  ...service,
+                  ingress: {
+                    ...service.ingress,
+                    hosts: { readOnly: false, value: e },
+                  },
+                });
+              }}
+              disabledTooltip={
+                "You may only edit this field in your porter.yaml."
+              }
+            />
+            {renderHealth()}
+          </>
+        </ScrollableDiv>
+      </>
+    );
+  };
   return (
     <>
-      <TabSelector
-        options={[
-          { label: 'Main', value: 'main' },
-          { label: 'Resources', value: 'resources' },
-          { label: 'Advanced', value: 'advanced' },
-        ]}
-        currentTab={currentTab}
-        setCurrentTab={(value: string) => {
-          if (value === 'main') {
-            setHeight(288);
-          } else if (value === 'resources') {
-            setHeight(713);
-          } else if (value === 'advanced') {
-            setHeight(159);
-          }
-          setCurrentTab(value);
-        }}
-      />
-      {currentTab === 'main' && renderMain()}
-      {currentTab === 'resources' && renderResources()}
-      {currentTab === 'advanced' && renderAdvanced()}
+      <div ref={containerRef} style={{ paddingBottom: "35px" }}>
+        <TabSelector
+          options={[
+            { label: "Main", value: "main" },
+            { label: "Resources", value: "resources" },
+            { label: "Advanced", value: "advanced" },
+          ]}
+          currentTab={currentTab}
+          setCurrentTab={(value: string) => setCurrentTab(value)}
+        />
+        {currentTab === "main" && renderMain()}
+        {currentTab === "resources" && renderResources()}
+        {currentTab === "advanced" && renderAdvanced()}
+      </div>
     </>
-  )
-}
+  );
+};
+
+export default WebTabs;
+
+const StyledAdvancedBuildSettings = styled.div`
+  color: ${({ showSettings }) => (showSettings ? "white" : "#aaaabb")};
+  background: ${({ theme }) => theme.fg};
+  border: 1px solid #494b4f;
+  ${({ disabled }) =>
+    !disabled &&
+    `
+    :hover {
+      border: 1px solid #7a7b80;
+      color: white;
+    }
+  `}
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 15px;
+  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 ScrollableDiv = styled.div`
+  overflow-y: auto;
+  padding: 0 25px;
+  width: calc(100% + 50px);
+  margin-left: -25px;
+  max-height: 375px;
+`;
+const Footer = styled.div`
+  position: relative;
+  width: calc(100% + 50px);
+  margin-left: -25px;
+  padding: 0 25px;
+  background: ${({ theme }) => theme.fg};
+  margin-bottom: -30px;
+  padding-bottom: 30px;
+`;
+
+const Shade = styled.div`
+  position: absolute;
 
-export default WebTabs;
+  top: -15px;
+  left: 0;
+  height: 50px;
+  width: 100%;
+  background: linear-gradient(to bottom, #00000000, ${({ theme }) => theme.fg});
+`;

+ 261 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -27,6 +27,104 @@ type Autoscaling = {
     targetCPUUtilizationPercentage: ServiceString,
     targetMemoryUtilizationPercentage: ServiceString,
 }
+//   livenessCommand:
+//     command: ls -l
+//     enabled: false
+//     failureThreshold: 3
+//     initialDelaySeconds: 5
+//     periodSeconds: 5
+//     successThreshold: 1
+//     timeoutSeconds: 1
+//   livenessProbe:
+//     auth:
+//       enabled: false
+//       password: ''
+//       username: ''
+//     enabled: false
+//     failureThreshold: 3
+//     initialDelaySeconds: 0
+//     path: /livez
+//     periodSeconds: 5
+//     scheme: HTTP
+//     successThreshold: 1
+//     timeoutSeconds: 1
+//   readinessProbe:
+//     auth:
+//       enabled: false
+//       password: ''
+//       username: ''
+//     enabled: false
+//     failureThreshold: 3
+//     initialDelaySeconds: 0
+//     path: /readyz
+//     periodSeconds: 5
+//     scheme: HTTP
+//     successThreshold: 1
+//     timeoutSeconds: 1
+//   startupProbe:
+//     auth:
+//       enabled: false
+//       password: ''
+//       username: ''
+//     enabled: false
+//     failureThreshold: 3
+//     path: /startupz
+//     periodSeconds: 5
+//     scheme: HTTP
+//     timeoutSeconds: 1
+type livenessCommand = {
+    command: ServiceString,
+    enabled: ServiceBoolean,
+    failureThreshold: ServiceString,
+    initialDelaySeconds: ServiceString,
+    periodSeconds: ServiceString,
+    successThreshold: ServiceString,
+    timeoutSeconds: ServiceString,
+}
+type Auth ={
+    enabled: ServiceBoolean,
+    password: ServiceString,
+    username: ServiceString,
+}
+type  LivenessProbe = {
+    enabled: ServiceBoolean,
+    failureThreshold: ServiceString,
+    initialDelaySeconds: ServiceString,
+    path: ServiceString,
+    periodSeconds: ServiceString,
+    scheme: ServiceString,
+    successThreshold: ServiceString,
+    timeoutSeconds: ServiceString,
+    auth: Auth,
+}
+type  ReadinessProbe = {
+    auth: Auth,
+    enabled: ServiceBoolean,
+    failureThreshold: ServiceString,
+    initialDelaySeconds: ServiceString,
+    path: ServiceString,
+    periodSeconds: ServiceString,
+    scheme: ServiceString,
+    successThreshold: ServiceString,
+    timeoutSeconds: ServiceString,
+}
+type  StartUpProbe = {
+    auth: Auth,
+    enabled: ServiceBoolean,
+    failureThreshold: ServiceString,
+    path: ServiceString,
+    periodSeconds: ServiceString,
+    scheme: ServiceString,
+    timeoutSeconds: ServiceString,
+}
+
+type Health = {
+    livenessProbe: LivenessProbe,
+    startupProbe: StartUpProbe,
+    readinessProbe: ReadinessProbe,
+    livenessCommand: livenessCommand,
+}
+
 
 const ServiceField = {
     string: (defaultValue: string, overrideValue?: string): ServiceString => {
@@ -119,6 +217,7 @@ export type WebService = SharedServiceParams & Omit<WorkerService, 'type'> & {
     type: 'web';
     port: ServiceString;
     ingress: Ingress;
+    health: Health;
 }
 const WebService = {
     default: (name: string, porterJson?: PorterJson): WebService => ({
@@ -142,6 +241,60 @@ const WebService = {
         },
         port: ServiceField.string('3000', porterJson?.apps?.[name]?.config?.container?.port),
         canDelete: porterJson?.apps?.[name] == null,
+        health: {
+            startupProbe:{
+                auth:{
+                    enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth?.enabled),
+                    password: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth?.password),
+                    username: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth?.username)
+                },
+                enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.startupProbe?.enabled),
+                failureThreshold: ServiceField.string('3', porterJson?.apps?.[name]?.config?.health?.startupProbe?.failureThreshold),
+                path: ServiceField.string('/startupz', porterJson?.apps?.[name]?.config?.health?.startupProbe?.path),
+                periodSeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.startupProbe?.periodSeconds),
+                scheme: ServiceField.string('HTTP', porterJson?.apps?.[name]?.config?.health?.startupProbe?.scheme),
+                timeoutSeconds: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.startupProbe?.timeoutSeconds),
+            },
+            readinessProbe:{
+                auth:{
+                    enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth?.enabled),
+                    password: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth?.password),
+                    username: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth?.username)
+                },
+                enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.readinessProbe?.enabled),
+                failureThreshold: ServiceField.string('3', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.failureThreshold),
+                initialDelaySeconds: ServiceField.string('0', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.initialDelaySeconds),
+                path: ServiceField.string('/readyz', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.path),
+                periodSeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.periodSeconds),
+                scheme: ServiceField.string('HTTP', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.scheme),
+                timeoutSeconds: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.timeoutSeconds),
+                successThreshold: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.successThreshold),
+            },
+            livenessCommand:{
+                command: ServiceField.string('ls -l', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.command),
+                enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.livenessCommand?.enabled),
+                failureThreshold: ServiceField.string('3', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.failureThreshold),
+                initialDelaySeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.initialDelaySeconds),
+                periodSeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.periodSeconds),
+                timeoutSeconds: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.timeoutSeconds),
+                successThreshold: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.successThreshold),
+            },
+            livenessProbe:{
+                auth:{
+                    enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth?.enabled),
+                    password: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth?.password),
+                    username: ServiceField.string('', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth?.username)
+                },
+                failureThreshold: ServiceField.string('3', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.failureThreshold),
+                initialDelaySeconds: ServiceField.string('0', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.initialDelaySeconds),
+                path: ServiceField.string('/livez', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.path),
+                periodSeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.periodSeconds),
+                scheme: ServiceField.string('HTTP', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.scheme),
+                successThreshold: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.successThreshold),
+                timeoutSeconds: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.timeoutSeconds),
+                enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.livenessProbe?.enabled),
+            },
+        }
     }),
     serialize: (service: WebService) => {
         return {
@@ -171,6 +324,60 @@ const WebService = {
                 hosts: service.ingress.hosts.value ? [service.ingress.hosts.value] : [],
                 custom_domain: service.ingress.hosts.value ? true : false,
                 porter_hosts: service.ingress.porterHosts.value ? [service.ingress.porterHosts.value] : [],
+            },
+            health: {
+                startupProbe: service.health.startupProbe ? {
+                    auth:{
+                        enabled: service.health.livenessProbe.auth?.enabled ? service.health.startupProbe.auth.enabled.value : false,
+                        password: service.health.livenessProbe.auth?.password ? service.health.startupProbe.auth.password.value : '',
+                        username: service.health.livenessProbe.auth?.username ? service.health.startupProbe.auth.username.value : '',
+                    },
+                    enabled: service.health.startupProbe.enabled ? service.health.startupProbe.enabled.value : false,
+                    failureThreshold:  service.health.startupProbe.failureThreshold ? service.health.startupProbe.failureThreshold.value : '3',
+                    path: service.health.startupProbe.path ? service.health.startupProbe.path.value : '/startupz',
+                    periodSeconds: service.health.startupProbe.periodSeconds ? service.health.startupProbe.periodSeconds.value : '5',
+                    scheme: service.health.startupProbe.scheme ? service.health.startupProbe.scheme.value : 'HTTP',
+                    timeoutSeconds: service.health.startupProbe.timeoutSeconds ? service.health.startupProbe.timeoutSeconds.value : '1',
+                } : {},
+                readinessProbe: service.health.readinessProbe ? {
+                    auth:{
+                        enabled: service.health.readinessProbe.auth?.enabled ? service.health.readinessProbe.auth.enabled.value : false,
+                        password: service.health.readinessProbe.auth?.password ? service.health.readinessProbe.auth.password.value : '',
+                        username: service.health.readinessProbe.auth?.username ? service.health.readinessProbe.auth.username.value : '',
+                    },
+                    enabled: service.health.readinessProbe.enabled ? service.health.readinessProbe.enabled.value : false,
+                    failureThreshold:service.health.readinessProbe.failureThreshold ? service.health.readinessProbe.failureThreshold.value : '3',
+                    initialDelaySeconds: service.health.readinessProbe.initialDelaySeconds ? service.health.readinessProbe.initialDelaySeconds.value : '0',
+                    path: service.health.readinessProbe.path ? service.health.readinessProbe.path.value : '/readyz',
+                    periodSeconds: service.health.readinessProbe.periodSeconds ? service.health.readinessProbe.periodSeconds.value : '5',
+                    scheme: service.health.readinessProbe.scheme ? service.health.readinessProbe.scheme.value : 'HTTP',
+                    timeoutSeconds: service.health.readinessProbe.timeoutSeconds ? service.health.readinessProbe.timeoutSeconds.value : '1',
+                    successThreshold: service.health.readinessProbe.successThreshold ? service.health.readinessProbe.successThreshold.value : '1',
+                } : {},
+                livenessCommand: service.health.livenessCommand ? {
+                    command: service.health.livenessCommand.command ? service.health.livenessCommand.command.value : 'ls -l',
+                    enabled: service.health.livenessCommand.enabled ? service.health.livenessCommand.enabled.value : false,
+                    failureThreshold: service.health.livenessCommand.failureThreshold ? service.health.livenessCommand.failureThreshold.value : '3',
+                    initialDelaySeconds: service.health.livenessCommand.initialDelaySeconds ? service.health.livenessCommand.initialDelaySeconds.value : '5',
+                    periodSeconds: service.health.livenessCommand.periodSeconds ? service.health.livenessCommand.periodSeconds.value : '5',
+                    timeoutSeconds:service.health.livenessCommand.timeoutSeconds ? service.health.livenessCommand.timeoutSeconds.value : '1' ,
+                    successThreshold: service.health.livenessCommand.successThreshold ? service.health.livenessCommand.successThreshold.value : '1',
+                } : {},
+                livenessProbe: service.health.livenessProbe ? {
+                    auth:{
+                        enabled: service.health.livenessProbe.auth ? service.health.livenessProbe.auth.enabled.value : false,
+                        password: service.health.livenessProbe.auth ? service.health.livenessProbe.auth.password.value : '',
+                        username: service.health.livenessProbe.auth ? service.health.livenessProbe.auth.username.value : '',
+                    },
+                    failureThreshold: service.health.livenessProbe.failureThreshold ? service.health.livenessProbe.failureThreshold.value : '3',
+                    initialDelaySeconds: service.health.livenessProbe.initialDelaySeconds ? service.health.livenessProbe.initialDelaySeconds.value : '0',
+                    path: service.health.livenessProbe.path ? service.health.livenessProbe.path.value : '/livez',
+                    periodSeconds: service.health.livenessProbe.periodSeconds ? service.health.livenessProbe.periodSeconds.value : '5',
+                    scheme: service.health.livenessProbe.scheme ? service.health.livenessProbe.scheme.value : 'HTTP',
+                    successThreshold: service.health.livenessProbe.successThreshold ? service.health.livenessProbe.successThreshold.value : '1',
+                    timeoutSeconds: service.health.livenessProbe.timeoutSeconds ? service.health.livenessProbe.timeoutSeconds.value : '1',
+                    enabled:  service.health.livenessProbe.enabled ? service.health.livenessProbe.enabled.value : false,
+                } : {},
             }
         }
     },
@@ -196,6 +403,60 @@ const WebService = {
             },
             port: ServiceField.string(values.container?.port ?? '', porterJson?.apps?.[name]?.config?.container?.port),
             canDelete: porterJson?.apps?.[name] == null,
+            health: {
+                startupProbe:{
+                    auth:{
+                        enabled: ServiceField.boolean(values.health.startupProbe.auth.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth?.enabled),
+                        password: ServiceField.string(values.health.startupProbe.auth.password ?? '', porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth.password),
+                        username: ServiceField.string(values.health.startupProbe.auth.username ?? '', porterJson?.apps?.[name]?.config?.health?.startupProbe?.auth.username)
+                    },
+                    enabled: ServiceField.boolean(values.health.startupProbe.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.startupProbe?.enabled),
+                    failureThreshold: ServiceField.string(values.health.startupProbe.failureThreshold ?? '3', porterJson?.apps?.[name]?.config?.health?.startupProbe?.failureThreshold),
+                    path: ServiceField.string(values.health.startupProbe.path ?? '/startupz', porterJson?.apps?.[name]?.config?.health?.startupProbe?.path),
+                    periodSeconds: ServiceField.string(values.health.startupProbe.periodSeconds ?? '5', porterJson?.apps?.[name]?.config?.health?.startupProbe?.periodSeconds),
+                    scheme: ServiceField.string(values.health.startupProbe.scheme ?? 'HTTP', porterJson?.apps?.[name]?.config?.health?.startupProbe?.scheme),
+                    timeoutSeconds:ServiceField.string(values.health.startupProbe.timeoutSeconds ?? '1', porterJson?.apps?.[name]?.config?.health?.startupProbe?.timeoutSeconds),
+                },
+                readinessProbe:{
+                    auth:{
+                        enabled: ServiceField.boolean(values.health.readinessProbe.auth.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth?.enabled),
+                        password: ServiceField.string(values.health.readinessProbe.auth.password ?? '', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth.password),
+                        username: ServiceField.string(values.health.readinessProbe.auth.username ?? '', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.auth.username)
+                    },
+                    enabled: ServiceField.boolean(values.health.readinessProbe.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.readinessProbe?.enabled),
+                    failureThreshold: ServiceField.string(values.health.readinessProbe.failureThreshold ?? '3', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.failureThreshold),
+                    path: ServiceField.string(values.health.readinessProbe.path ?? '/startupz', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.path),
+                    periodSeconds: ServiceField.string(values.health.readinessProbe.periodSeconds ?? '5', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.periodSeconds),
+                    scheme: ServiceField.string(values.health.readinessProbe.scheme ?? 'HTTP', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.scheme),
+                    timeoutSeconds:ServiceField.string(values.health.readinessProbe.timeoutSeconds ?? '1', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.timeoutSeconds),
+                    initialDelaySeconds: ServiceField.string(values.health.readinessProbe.initialDelaySeconds ?? '5', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.initialDelaySeconds),
+                    successThreshold: ServiceField.string(values.health.readinessProbe.successThreshold ?? '1', porterJson?.apps?.[name]?.config?.health?.readinessProbe?.successThreshold),
+                },
+                livenessCommand:{
+                    command: ServiceField.string('ls -l', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.command),
+                    enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.health?.livenessCommand?.enabled),
+                    failureThreshold: ServiceField.string('3', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.failureThreshold),
+                    initialDelaySeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.initialDelaySeconds),
+                    periodSeconds: ServiceField.string('5', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.periodSeconds),
+                    timeoutSeconds: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.timeoutSeconds),
+                    successThreshold: ServiceField.string('1', porterJson?.apps?.[name]?.config?.health?.livenessCommand?.successThreshold),
+                },
+                livenessProbe:{
+                    auth:{
+                        enabled: ServiceField.boolean(values.health.livenessProbe.auth.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth?.enabled),
+                        password: ServiceField.string(values.health.livenessProbe.auth.password ?? '', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth.password),
+                        username: ServiceField.string(values.health.livenessProbe.auth.username ?? '', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.auth.username)
+                    },
+                    enabled: ServiceField.boolean(values.health.livenessProbe.enabled ?? false, porterJson?.apps?.[name]?.config?.health?.livenessProbe?.enabled),
+                    failureThreshold: ServiceField.string(values.health.livenessProbe.failureThreshold ?? '3', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.failureThreshold),
+                    path: ServiceField.string(values.health.livenessProbe.path ?? '/startupz', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.path),
+                    periodSeconds: ServiceField.string(values.health.livenessProbe.periodSeconds ?? '5', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.periodSeconds),
+                    scheme: ServiceField.string(values.health.livenessProbe.scheme ?? 'HTTP', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.scheme),
+                    timeoutSeconds:ServiceField.string(values.health.livenessProbe.timeoutSeconds ?? '1', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.timeoutSeconds),
+                    initialDelaySeconds: ServiceField.string(values.health.livenessProbe.initialDelaySeconds ?? '5', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.initialDelaySeconds),
+                    successThreshold: ServiceField.string(values.health.livenessProbe.successThreshold ?? '1', porterJson?.apps?.[name]?.config?.health?.livenessProbe?.successThreshold),
+                },
+            }
         }
     }
 }