Ver Fonte

support ingress custom annotations (#3297)

Feroze Mohideen há 2 anos atrás
pai
commit
15f7b3f976

+ 40 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -6,7 +6,7 @@ import { PorterJson } from "./schema";
 export type Service = WorkerService | WebService | JobService | ReleaseService;
 export type ServiceType = 'web' | 'worker' | 'job' | 'release';
 
-type ServiceString = {
+export type ServiceString = {
     readOnly: boolean;
     value: string;
 }
@@ -14,11 +14,31 @@ type ServiceBoolean = {
     readOnly: boolean;
     value: boolean;
 }
+export type ServiceArray<T extends ServiceString | ServiceBoolean> = {
+    key: string;
+    value: T;
+}[];
+const ServiceArray = {
+    serialize: <T extends ServiceString | ServiceBoolean>(serviceArray: ServiceArray<T>) => {
+        const map: Record<string, string> = {};
+        serviceArray.map(({ key, value }: {
+            key: string;
+            value: T;
+        }) => {
+            if (key != '') {
+                map[key] = value.value.toString();
+            }
+        });
+        return map;
+    }
+}
+
 type Ingress = {
     enabled: ServiceBoolean;
     customDomain: ServiceString;
     hosts: ServiceString;
     porterHosts: ServiceString;
+    annotations: ServiceArray<ServiceString>;
 }
 type Autoscaling = {
     enabled: ServiceBoolean,
@@ -71,6 +91,22 @@ const ServiceField = {
             value: overrideValue ?? defaultValue,
         }
     },
+    array: (defaultMap: Record<string, string>, overrideMap?: Record<string, string>): ServiceArray<ServiceString> => {
+        const serviceMap: Record<string, ServiceString> = {};
+        for (const key in defaultMap) {
+            serviceMap[key] = ServiceField.string(defaultMap[key]);
+        }
+        for (const key in overrideMap) {
+            serviceMap[key] = ServiceField.string('', overrideMap[key]);
+        }
+        if (Object.keys(serviceMap).length == 0) {
+            return [];
+        }
+        return Object.keys(serviceMap).map((key) => ({
+            key,
+            value: serviceMap[key],
+        }));
+    }
 }
 
 type SharedServiceParams = {
@@ -109,6 +145,7 @@ const WebService = {
             customDomain: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
             hosts: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
             porterHosts: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.porter_hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.porter_hosts[0] : undefined),
+            annotations: ServiceField.array({}, porterJson?.apps?.[name]?.config?.ingress?.annotations)
         },
         port: ServiceField.string('3000', porterJson?.apps?.[name]?.config?.container?.port),
         canDelete: porterJson?.apps?.[name] == null,
@@ -164,6 +201,7 @@ const WebService = {
                 custom_domain: service.ingress.customDomain.value ? true : false,
                 hosts: service.ingress.customDomain.value ? [service.ingress.customDomain.value] : [],
                 porter_hosts: service.ingress.porterHosts.value ? [service.ingress.porterHosts.value] : [],
+                annotations: ServiceArray.serialize(service.ingress.annotations),
             },
             service: {
                 port: service.port.value,
@@ -216,6 +254,7 @@ const WebService = {
                 customDomain: ServiceField.string(values.ingress?.hosts?.length ? values.ingress.hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
                 hosts: ServiceField.string(values.ingress?.hosts?.length ? values.ingress.hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
                 porterHosts: ServiceField.string(values.ingress?.porter_hosts?.length ? values.ingress.porter_hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.porter_hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.porter_hosts[0] : undefined),
+                annotations: ServiceField.array(values.ingress?.annotations ?? {}, porterJson?.apps?.[name]?.config?.ingress?.annotations),
             },
             port: ServiceField.string(values.container?.port ?? '', porterJson?.apps?.[name]?.config?.container?.port),
             canDelete: porterJson?.apps?.[name] == null,

+ 116 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/tabs/IngressCustomAnnotations.tsx

@@ -0,0 +1,116 @@
+import React from 'react';
+import { ServiceArray, ServiceString } from '../serviceTypes';
+import Button from 'components/porter/Button';
+import styled from 'styled-components';
+import Input from 'components/porter/Input';
+import Spacer from 'components/porter/Spacer';
+
+interface Props {
+    annotations: ServiceArray<ServiceString>;
+    onChange: (annotations: ServiceArray<ServiceString>) => void;
+}
+
+const IngressCustomAnnotations: React.FC<Props> = ({ annotations, onChange }) => {
+    const renderInputs = () => {
+        return annotations.map(({ key: annotationKey, value: annotationValue }, i) => {
+            return (
+                <>
+                    <AnnotationContainer key={i}>
+                        <Input
+                            placeholder="kubernetes.io/ingress.class"
+                            value={annotationKey}
+                            setValue={(e) => {
+                                const newAnnotations = [...annotations];
+                                newAnnotations[i].key = e;
+                                onChange(newAnnotations);
+                            }}
+                            disabled={annotationValue.readOnly}
+                            width="275px"
+                            disabledTooltip={
+                                "You may only edit this field in your porter.yaml."
+                            }
+                        />
+                        <Input
+                            placeholder="nginx"
+                            value={annotationValue.value}
+                            setValue={(e) => {
+                                const newAnnotations = [...annotations];
+                                newAnnotations[i].value = { readOnly: false, value: e };
+                                onChange(newAnnotations);
+                            }}
+                            disabled={annotationValue.readOnly}
+                            width="275px"
+                            disabledTooltip={
+                                "You may only edit this field in your porter.yaml."
+                            }
+                        />
+                        <DeleteButton
+                            onClick={() => {
+                                //remove annotation at the index
+                                const newAnnotations = [...annotations];
+                                newAnnotations.splice(i, 1);
+                                onChange(newAnnotations);
+                            }}
+                        >
+                            <i className="material-icons">cancel</i>
+                        </DeleteButton>
+                    </AnnotationContainer>
+                    <Spacer y={0.25} />
+                </>
+            );
+        });
+    };
+
+    return (
+        <IngressCustomAnnotationsContainer>
+            {annotations.length !== 0 &&
+                <>
+                    {renderInputs()}
+                    <Spacer y={0.5} />
+                </>
+            }
+            <Button
+                onClick={() => {
+                    const newAnnotations = [...annotations];
+                    newAnnotations.push({ key: "", value: { readOnly: false, value: "" } });
+                    onChange(newAnnotations);
+                }}
+            >
+                Add Annotation
+            </Button>
+        </IngressCustomAnnotationsContainer >
+    )
+};
+
+export default IngressCustomAnnotations;
+
+const IngressCustomAnnotationsContainer = styled.div`
+`;
+
+const AnnotationContainer = styled.div`
+    display: flex;
+    align-items: center;
+    gap: 5px;
+`
+
+const DeleteButton = styled.div`
+  width: 15px;
+  height: 15px;
+  display: flex;
+  align-items: center;
+  margin-left: 8px;
+  margin-top: -3px;
+  justify-content: center;
+
+  > i {
+    font-size: 17px;
+    color: #ffffff44;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    :hover {
+      color: #ffffff88;
+    }
+  }
+`;

+ 26 - 2
dashboard/src/main/home/app-dashboard/new-app-flow/tabs/WebTabs.tsx

@@ -8,6 +8,7 @@ import { Service, WebService } from "../serviceTypes";
 import AnimateHeight, { Height } from "react-animate-height";
 import { Context } from "shared/Context";
 import { DATABASE_HEIGHT_DISABLED, DATABASE_HEIGHT_ENABLED, RESOURCE_HEIGHT_WITHOUT_AUTOSCALING, RESOURCE_HEIGHT_WITH_AUTOSCALING } from "./utils";
+import IngressCustomAnnotations from "./IngressCustomAnnotations";
 
 interface Props {
   service: WebService;
@@ -17,9 +18,10 @@ interface Props {
 
 
 const NETWORKING_HEIGHT_WITHOUT_INGRESS = 204;
-const NETWORKING_HEIGHT_WITH_INGRESS = 333;
+const NETWORKING_HEIGHT_WITH_INGRESS = 425;
 const ADVANCED_BASE_HEIGHT = 215;
 const PROBE_INPUTS_HEIGHT = 230;
+const CUSTOM_ANNOTATION_HEIGHT = 53;
 
 const WebTabs: React.FC<Props> = ({
   service,
@@ -66,7 +68,7 @@ const WebTabs: React.FC<Props> = ({
   };
 
   const renderNetworking = () => {
-    setHeight(service.ingress.enabled.value ? NETWORKING_HEIGHT_WITH_INGRESS : NETWORKING_HEIGHT_WITHOUT_INGRESS)
+    setHeight(service.ingress.enabled.value ? calculateNetworkingHeight() : NETWORKING_HEIGHT_WITHOUT_INGRESS)
     return (
       <>
         <Spacer y={1} />
@@ -134,6 +136,24 @@ const WebTabs: React.FC<Props> = ({
           />
           <Spacer y={1} />
           {getApplicationURLText()}
+          <Spacer y={1} />
+          <Text color="helper">
+            Ingress Custom Annotations
+            <a
+              href="https://docs.porter.run/standard/deploying-applications/runtime-configuration-options/web-applications#ingress-custom-annotations"
+              target="_blank"
+            >
+              &nbsp;(?)
+            </a>
+          </Text>
+          <Spacer y={0.5} />
+          <IngressCustomAnnotations
+            annotations={service.ingress.annotations}
+            onChange={(annotations) => {
+              editService({ ...service, ingress: { ...service.ingress, annotations: annotations } });
+              setHeight(calculateNetworkingHeight());
+            }}
+          />
         </AnimateHeight>
       </>
     );
@@ -422,6 +442,10 @@ const WebTabs: React.FC<Props> = ({
     return height;
   };
 
+  const calculateNetworkingHeight = () => {
+    return NETWORKING_HEIGHT_WITH_INGRESS + (service.ingress.annotations.length * CUSTOM_ANNOTATION_HEIGHT);
+  }
+
   const renderAdvanced = () => {
     setHeight(calculateHealthHeight());
     return (