ソースを参照

Health checks stacks (#3051)

* health checks

* health checks

* Health

* Health

* Health

* Health

* Health

* Health

* Health

* Additional Changes

* Additional Changes

* Additional Changes

* Additional Changes

* Additional Changes

* Roll Out

* Roll Out

* CleanUp
sdess09 3 年 前
コミット
768a1841ef

+ 103 - 75
dashboard/src/main/home/app-dashboard/expanded-app/StatusFooter.tsx

@@ -7,10 +7,7 @@ import { Context } from "shared/Context";
 import Text from "components/porter/Text";
 import Container from "components/porter/Container";
 import Button from "components/porter/Button";
-import {
-  NewWebsocketOptions,
-  useWebsockets,
-} from "shared/hooks/useWebsockets";
+import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
 import {
   getAvailability,
   getAvailabilityStacks,
@@ -140,10 +137,8 @@ const StatusFooter: React.FC<Props> = ({
       apiEndpoint += `selectors=${selectors}`;
     }
 
-
     const options: NewWebsocketOptions = {};
-    options.onopen = () => {
-    };
+    options.onopen = () => {};
 
     options.onmessage = async (evt: MessageEvent) => {
       let event = JSON.parse(evt.data);
@@ -161,7 +156,9 @@ const StatusFooter: React.FC<Props> = ({
       }
 
       if (event.Kind === "deployment") {
-        let [available, total, stale, unavailable] = getAvailabilityStacks(object);
+        let [available, total, stale, unavailable] = getAvailabilityStacks(
+          object
+        );
 
         setAvailable(available);
         setTotal(total);
@@ -172,8 +169,7 @@ const StatusFooter: React.FC<Props> = ({
       await updatePods();
     };
 
-    options.onclose = () => {
-    };
+    options.onclose = () => {};
 
     options.onerror = (err: ErrorEvent) => {
       console.log(err);
@@ -203,7 +199,7 @@ const StatusFooter: React.FC<Props> = ({
         prev[prev.length - 1].push(currentPod);
         return prev;
       },
-        []);
+      []);
 
     return podsDividedByReplicaSet;
   }, [pods]);
@@ -248,7 +244,8 @@ const StatusFooter: React.FC<Props> = ({
           );
 
           // console.log(containerStatus)
-          const crashLoopReason = containerStatus?.lastState?.terminated?.message ?? "";
+          const crashLoopReason =
+            containerStatus?.lastState?.terminated?.message ?? "";
 
           return {
             namespace: pod?.metadata?.namespace,
@@ -259,7 +256,8 @@ const StatusFooter: React.FC<Props> = ({
             restartCount,
             containerStatus,
             podAge: pod?.metadata?.creationTimestamp ? podAge : "N/A",
-            revisionNumber: pod?.metadata?.annotations?.["helm.sh/revision"] || "N/A",
+            revisionNumber:
+              pod?.metadata?.annotations?.["helm.sh/revision"] || "N/A",
             crashLoopReason,
           };
         });
@@ -293,75 +291,104 @@ const StatusFooter: React.FC<Props> = ({
             </Button>
           </Container>
         )}
-    </StyledStatusFooter>
+      </StyledStatusFooter>
     );
-  };
+  }
 
   return (
     <>
-      {replicaSetArray != null && replicaSetArray.length > 0 && replicaSetArray.map((replicaSet, i) => {
-        return (
-          <>
-            <StyledStatusFooterTop key={i} expanded={expanded}>
-              <StyledContainer row spaced>
-                {replicaSet.some(r => r.crashLoopReason != "") ?
-                  <>
-                    <Running>
-                      <StatusDot color="#ff0000" />
-                      <Text color="helper">
-                        {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"} ${replicaSet.length === 1 ? "is" : "are"} failing to run Revision ${replicaSet[0].revisionNumber}`}
-                      </Text>
-                    </Running>
-                    <Button
-                      onClick={() => {
-                        expanded ? setHeight(0) : setHeight(122);
-                        setExpanded(!expanded);
-                      }}
-                      height="20px"
-                      color="#ffffff11"
-                      withBorder
-                    >
-                      {expanded ? <I className="material-icons">arrow_drop_up</I>
-                        : <I className="material-icons">arrow_drop_down</I>}
-                      <Text color="helper">
-                        See failure reason
-                      </Text>
-                    </Button>
-                  </> :
-                  // check if there are more recent replicasets and if the previous replicaset has a crashloop reason
-                  i > 0 && !replicaSetArray[i - 1].some(p => p.crashLoopReason != "") ?
+      {replicaSetArray != null &&
+        replicaSetArray.length > 0 &&
+        replicaSetArray.map((replicaSet, i) => {
+          return (
+            <>
+              <StyledStatusFooterTop key={i} expanded={expanded}>
+                <StyledContainer row spaced>
+                  {replicaSet.some((r) => r.crashLoopReason != "") ? (
+                    <>
+                      <Running>
+                        <StatusDot color="#ff0000" />
+                        <Text color="helper">
+                          {`${replicaSet.length} replica${
+                            replicaSet.length === 1 ? "" : "s"
+                          } ${
+                            replicaSet.length === 1 ? "is" : "are"
+                          } failing to run Revision ${
+                            replicaSet[0].revisionNumber
+                          }`}
+                        </Text>
+                      </Running>
+                      <Button
+                        onClick={() => {
+                          expanded ? setHeight(0) : setHeight(122);
+                          setExpanded(!expanded);
+                        }}
+                        height="20px"
+                        color="#ffffff11"
+                        withBorder
+                      >
+                        {expanded ? (
+                          <I className="material-icons">arrow_drop_up</I>
+                        ) : (
+                          <I className="material-icons">arrow_drop_down</I>
+                        )}
+                        <Text color="helper">See failure reason</Text>
+                      </Button>
+                    </>
+                  ) : // check if there are more recent replicasets and if the previous replicaset has a crashloop reason
+                  i > 0 &&
+                    !replicaSetArray[i - 1].some(
+                      (p) => p.crashLoopReason != ""
+                    ) ? (
                     <Running>
                       <StatusDot color="#FFA500" />
                       <Text color="helper">
-                        {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"} ${replicaSet.length === 1 ? "is" : "are"} still running at Revision ${replicaSet[0].revisionNumber}. Spinning down...`}
+                        {`${replicaSet.length} replica${
+                          replicaSet.length === 1 ? "" : "s"
+                        } ${
+                          replicaSet.length === 1 ? "is" : "are"
+                        } still running at Revision ${
+                          replicaSet[0].revisionNumber
+                        }. Spinning down...`}
                       </Text>
-                    </Running> :
+                    </Running>
+                  ) : (
                     <Running>
-                      {replicaSet.length ? <StatusDot /> : <StatusDot color="#ffffff33" />}
+                      {replicaSet.length ? (
+                        <StatusDot />
+                      ) : (
+                        <StatusDot color="#ffffff33" />
+                      )}
                       <Text color="helper">
-                        {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"} ${replicaSet.length === 1 ? "is" : "are"} running at Revision ${replicaSet[0].revisionNumber}`}
+                        {`${replicaSet.length} replica${
+                          replicaSet.length === 1 ? "" : "s"
+                        } ${
+                          replicaSet.length === 1 ? "is" : "are"
+                        } running at Revision ${replicaSet[0].revisionNumber}`}
                       </Text>
                     </Running>
-                }
-              </StyledContainer>
-            </StyledStatusFooterTop>
-            {replicaSet.some(r => r.crashLoopReason != "") &&
-              <AnimateHeight height={height}>
-                <StyledStatusFooter>
-                  <Message>
-                    {replicaSet.find(r => r.crashLoopReason != "")?.crashLoopReason}
-                  </Message>
-                </StyledStatusFooter>
-              </AnimateHeight>
-            }
-          </>
-        )
-      })}
+                  )}
+                </StyledContainer>
+              </StyledStatusFooterTop>
+              {replicaSet.some((r) => r.crashLoopReason != "") && (
+                <AnimateHeight height={height}>
+                  <StyledStatusFooter>
+                    <Message>
+                      {
+                        replicaSet.find((r) => r.crashLoopReason != "")
+                          ?.crashLoopReason
+                      }
+                    </Message>
+                  </StyledStatusFooter>
+                </AnimateHeight>
+              )}
+            </>
+          );
+        })}
     </>
   );
 };
 
-
 export default withRouter(StatusFooter);
 
 const StatusDot = styled.div<{ color?: string }>`
@@ -370,22 +397,22 @@ const StatusDot = styled.div<{ color?: string }>`
   height: 7px;
   border-radius: 50%;
   margin-right: 10px;
-  background: ${props => props.color || "#38a88a"};
+  background: ${(props) => props.color || "#38a88a"};
 
   box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);
-	transform: scale(1);
-	animation: pulse 2s infinite;
+  transform: scale(1);
+  animation: pulse 2s infinite;
   @keyframes pulse {
     0% {
       transform: scale(0.95);
       box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
     }
-  
+
     70% {
       transform: scale(1);
       box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
     }
-  
+
     100% {
       transform: scale(0.95);
       box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
@@ -449,7 +476,7 @@ const StyledStatusFooter = styled.div`
   }
 `;
 
-const StyledStatusFooterTop = styled(StyledStatusFooter) <{
+const StyledStatusFooterTop = styled(StyledStatusFooter)<{
   expanded: boolean;
 }>`
   height: 40px;
@@ -481,8 +508,9 @@ const StyledContainer = styled.div<{
   row: boolean;
   spaced: boolean;
 }>`
-  display: ${props => props.row ? "flex" : "block"};
+  display: ${(props) => (props.row ? "flex" : "block")};
   align-items: center;
-  justify-content: ${props => props.spaced ? "space-between" : "flex-start"};
+  justify-content: ${(props) =>
+    props.spaced ? "space-between" : "flex-start"};
   width: 100%;
-`;
+`;

+ 23 - 31
dashboard/src/main/home/app-dashboard/new-app-flow/ServiceContainer.tsx

@@ -12,6 +12,7 @@ import WebTabs from "./WebTabs";
 import WorkerTabs from "./WorkerTabs";
 import JobTabs from "./JobTabs";
 import { Service } from "./serviceTypes";
+import { StyledStatusFooter } from "../expanded-app/StatusFooter";
 import StatusFooter from "../expanded-app/StatusFooter";
 import ReleaseTabs from "./ReleaseTabs";
 
@@ -32,7 +33,9 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   defaultExpanded,
   setExpandedJob,
 }) => {
-  const [showExpanded, setShowExpanded] = React.useState<boolean>(defaultExpanded);
+  const [showExpanded, setShowExpanded] = React.useState<boolean>(
+    defaultExpanded
+  );
   const [height, setHeight] = React.useState<Height>("auto");
 
   // TODO: calculate heights instead of hardcoding them
@@ -90,12 +93,8 @@ const ServiceContainer: React.FC<ServiceProps> = ({
     if (chart?.chart?.values == null) {
       return false;
     }
-    return (
-      !_.isEmpty((
-        Object.values(chart.chart.values)[0] as any
-      )?.global)
-    );
-  }
+    return !_.isEmpty((Object.values(chart.chart.values)[0] as any)?.global);
+  };
 
   return (
     <>
@@ -113,30 +112,24 @@ const ServiceContainer: React.FC<ServiceProps> = ({
           {service.name.trim().length > 0 ? service.name : "New Service"}
         </ServiceTitle>
         {service.canDelete && (
-          <ActionButton
-            onClick={deleteService}
-          >
+          <ActionButton onClick={deleteService}>
             <span className="material-icons">delete</span>
           </ActionButton>
         )}
       </ServiceHeader>
-      <AnimateHeight
-        height={showExpanded ? height : 0}
-      >
+      <AnimateHeight height={showExpanded ? height : 0}>
         <StyledSourceBox
           showExpanded={showExpanded}
           chart={chart}
-          hasFooter={getHasBuiltImage()}
+          hasFooter={chart && service && getHasBuiltImage()}
         >
           {renderTabs(service)}
         </StyledSourceBox>
       </AnimateHeight>
-      {(
-        chart &&
+      {chart &&
         service &&
         // Check if has built image
-        getHasBuiltImage()
-      ) && (
+        getHasBuiltImage() && (
           <StatusFooter
             setExpandedJob={setExpandedJob}
             chart={chart}
@@ -156,9 +149,9 @@ const ServiceTitle = styled.div`
 `;
 
 const StyledSourceBox = styled.div<{
-  showExpanded: boolean,
-  chart: any,
-  hasFooter?: boolean,
+  showExpanded: boolean;
+  chart: any;
+  hasFooter?: boolean;
 }>`
   width: 100%;
   color: #ffffff;
@@ -168,8 +161,8 @@ const StyledSourceBox = styled.div<{
   background: ${(props) => props.theme.fg};
   border: 1px solid #494b4f;
   border-top: 0;
-  border-bottom-left-radius: ${props => props.hasFooter ? "0" : "5px"};
-  border-bottom-right-radius: ${props => props.hasFooter ? "0" : "5px"};
+  border-bottom-left-radius: ${(props) => (props.hasFooter ? "0" : "5px")};
+  border-bottom-right-radius: ${(props) => (props.hasFooter ? "0" : "5px")};
 `;
 
 const ActionButton = styled.button`
@@ -184,7 +177,6 @@ const ActionButton = styled.button`
   border-radius: 50%;
   cursor: pointer;
   color: #aaaabb;
-
   :hover {
     color: white;
   }
@@ -196,9 +188,9 @@ const ActionButton = styled.button`
 `;
 
 const ServiceHeader = styled.div<{
-  showExpanded: boolean,
-  chart: any,
-  bordersRounded?: boolean,
+  showExpanded: boolean;
+  chart: any;
+  bordersRounded?: boolean;
 }>`
   flex-direction: row;
   display: flex;
@@ -215,16 +207,16 @@ const ServiceHeader = styled.div<{
     border: 1px solid #7a7b80;
   }
 
-  border-bottom-left-radius: ${props => props.bordersRounded ? "" : "0"};
-  border-bottom-right-radius: ${props => props.bordersRounded ? "" : "0"};
+  border-bottom-left-radius: ${(props) => (props.bordersRounded ? "" : "0")};
+  border-bottom-right-radius: ${(props) => (props.bordersRounded ? "" : "0")};
 
   .dropdown {
     font-size: 30px;
     cursor: pointer;
     border-radius: 20px;
     margin-left: -10px;
-    transform: ${(props: { showExpanded: boolean, chart: any }) =>
-    props.showExpanded ? "" : "rotate(-90deg)"};
+    transform: ${(props: { showExpanded: boolean; chart: any }) =>
+      props.showExpanded ? "" : "rotate(-90deg)"};
   }
 
   animation: fadeIn 0.3s 0s;

+ 578 - 71
dashboard/src/main/home/app-dashboard/new-app-flow/WebTabs.tsx

@@ -1,24 +1,28 @@
 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";
+import ExpandableSection from "components/porter/ExpandableSection";
 
 interface Props {
-  service: WebService
-  editService: (service: WebService) => void
-  setHeight: (height: Height) => void
+  service: WebService;
+  editService: (service: WebService) => void;
+  setHeight: (height: h) => void;
+  hasFooter?: boolean;
 }
 
 const WebTabs: React.FC<Props> = ({
   service,
   editService,
   setHeight,
+  hasFooter,
 }) => {
-  const [currentTab, setCurrentTab] = React.useState<string>('main');
+  const [currentTab, setCurrentTab] = React.useState<string>("main");
 
   const renderMain = () => {
     return (
@@ -43,7 +47,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 +62,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 +101,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 +113,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 +123,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 +164,555 @@ 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={
+        <>
+          <Text size={16}> Configure Liveness Probe settings</Text>
+
+          <PaddingContainer>
+            <Spacer y={1} />
+            <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>
+            <Spacer y={1} />
+
             <>
-              <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."}
-        />
+              <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={1} />
+              <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={1} />
+              <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."
+                }
+              />
+            </>
+          </PaddingContainer>
+        </>
+        <Spacer y={1} />
+        <>
+          <Text size={16}> Configure Start Up Probe settings</Text>
+
+          <PaddingContainer>
+            <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>
+            <Spacer y={1} />
+
+            <>
+              <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={1} />
+
+              <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={1} />
+              <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."
+                }
+              />
+            </>
+          </PaddingContainer>
+        </>
+        <Spacer y={1} />
+        <>
+          <Text size={16}> Configure Readiness Probe settings</Text>
+          <PaddingContainer>
+            <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>
+            <Spacer y={1} />
+
+            <>
+              <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."
+                }
+              />
+            </>
+          </PaddingContainer>
+        </>
       </>
     );
   };
 
+  const renderAdvanced = () => {
+    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."
+            }
+          />
+          {renderHealth()}
+        </>
+      </>
+    );
+  };
   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()}
+      <>
+        <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(1179);
+            }
+            setCurrentTab(value);
+          }}
+        />
+        {currentTab === "main" && renderMain()}
+        {currentTab === "resources" && renderResources()}
+        {currentTab === "advanced" && renderAdvanced()}
+      </>
     </>
-  )
-}
+  );
+};
+
+export default WebTabs;
+
+const ScrollableDiv = styled.div`
+  overflow-y: auto;
+  padding: 0 25px;
+  width: calc(100% + 50px);
+  margin-left: -25px;
+  max-height: 400px;
+`;
+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});
+`;
+const StyledAnimateHeight = styled(AnimateHeight)`
+  & > * {
+    padding-left: 5px;
+  }
+`;
+const PaddingContainer = styled.div`
+  padding-left: 15px;
+`;

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

@@ -28,6 +28,60 @@ type Autoscaling = {
     targetMemoryUtilizationPercentage: ServiceString,
 }
 
+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 => {
         return {
@@ -119,6 +173,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 +197,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 +280,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 +359,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),
+                },
+            }
         }
     }
 }