Soham Dessai преди 2 години
родител
ревизия
c9928af49a

+ 18 - 4
dashboard/src/components/SOC2Checks.tsx

@@ -15,12 +15,14 @@ import Spacer from "./porter/Spacer";
 import Text from "./porter/Text";
 import ToggleRow from "./porter/ToggleRow";
 import { type Soc2Data, type Soc2Check } from "shared/types";
+import SOC2EmailComponent from "./SOC2EmailComponent";
+
 
 type Props = RouteComponentProps & {
-  soc2Data: Soc2Check;
+  soc2Data: Soc2Data;
   error?: string;
   enableAll: boolean;
-  setSoc2Data: (x: Soc2Check) => void;
+  setSoc2Data: (x: Soc2Data) => void;
   readOnly: boolean;
 };
 type ItemProps = RouteComponentProps & {
@@ -76,8 +78,8 @@ const SOC2Checks: React.FC<Props> = ({
   }, [enableAll]);
 
   const Soc2Item: React.FC<ItemProps> = ({ checkKey, checkLabel }) => {
-    const checkData = soc2Data?.soc2_checks?.[checkKey];
-    const hasMessage = checkData?.message;
+    const checkData: Soc2Check = soc2Data?.soc2_checks?.[checkKey];
+    const hasMessage: S = checkData?.message;
     const enabled = checkData?.enabled;
     const status = checkData?.status;
 
@@ -196,6 +198,18 @@ const SOC2Checks: React.FC<Props> = ({
                     </Link>
                   )}
                 </Container>
+                {
+                  (checkData.email && (checkData.enabled || enableAll)) &&
+                  <>
+                    <Spacer y={1} />
+                    <SOC2EmailComponent
+                      emails={checkData.email}
+                      enabled={checkData.enabled}
+                      setSoc2Data={setSoc2Data}
+                      soc2CheckKey={checkKey}
+                    />
+                  </>
+                }
                 <Spacer y={0.5} />
               </>
             )}

+ 125 - 0
dashboard/src/components/SOC2EmailComponent.tsx

@@ -0,0 +1,125 @@
+import React, { useState } from "react";
+import { type Soc2Data } from "shared/types";
+import styled from "styled-components";
+import Text from "./porter/Text";
+import Input from "./porter/Input";
+import { emailRegex } from "shared/regex";
+import Spacer from "./porter/Spacer";
+import Button from "./porter/Button";
+
+type Props = {
+  enabled: boolean;
+  emails: string[];
+  setSoc2Data: (x: Soc2Data) => void;
+  soc2CheckKey: string;
+};
+
+const SOC2EmailComponent: React.FC<Props> = ({ emails, setSoc2Data, soc2CheckKey }) => {
+  const [email, setEmail] = useState("");
+  const [emailError, setEmailError] = useState(false);
+
+  const addEmail = (): void => {
+    if (emailRegex.test(email)) {
+      const updatedEmails = [...emails, email];
+
+      setSoc2Data((prev) => ({
+        ...prev,
+        soc2_checks: {
+          ...prev.soc2_checks,
+          [soc2CheckKey]: {
+            ...prev.soc2_checks[soc2CheckKey],
+            email: updatedEmails,
+          },
+        },
+      }));
+      setEmail('');
+    } else {
+      setEmailError(true);
+    }
+  };
+
+  const deleteEmail = (emailToDelete: string): void => {
+    const updatedEmails = emails.filter(e => e !== emailToDelete);
+    setSoc2Data((prev) => ({
+      ...prev,
+      soc2_checks: {
+        ...prev.soc2_checks,
+        [soc2CheckKey]: {
+          ...prev.soc2_checks[soc2CheckKey],
+          email: updatedEmails,
+        },
+      },
+    }));
+  };
+
+  return (
+    <>
+      <div>
+        <Input
+          type="email"
+          label="REQUIRED: Enter email to receive SOC2 Alerts"
+          placeholder="Email"
+          value={email}
+          setValue={(x) => {
+            setEmail(x);
+            setEmailError(false);
+          }}
+          width="60%"
+          error={emailError && "Please enter a valid email"}
+        />
+        <Spacer inline x={0.5} />
+        <Button onClick={addEmail} width="10%">
+          Add Email
+        </Button>
+      </div>
+      <Spacer y={1} />
+      <div>
+        {emails.length > 0 && <Text>Subscribers: </Text>}
+        {emails.map((email, index) => (
+          <EmailItem key={index}>
+            <Text color="helper" size={13}>{email}</Text>
+            <DeleteButton onClick={() => deleteEmail(email)}>
+              <i className="material-icons">delete</i>
+            </DeleteButton>
+          </EmailItem>
+        ))}
+      </div>
+    </>
+  );
+};
+
+export default SOC2EmailComponent;
+
+
+const DeleteButton = styled.div`
+  display: flex;
+  visibility: ${(props: { invis?: boolean }) =>
+    props.invis ? "hidden" : "visible"};
+  align-items: center;
+  justify-content: center;
+  width: 30px;
+  margin-right: 15px;
+  float: right;
+  height: 30px;
+  :hover {
+    background: #ffffff11;
+    border-radius: 20px;
+    cursor: pointer;
+  }
+
+  > i {
+    font-size: 20px;
+    color: #ffffff44;
+    border-radius: 20px;
+  }
+`;
+
+const EmailItem = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 2px;
+  margin-bottom: 5px;
+  border-radius: 4px;
+
+`;

+ 27 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Compliance.tsx

@@ -46,7 +46,9 @@ const soc2DataDefault: Soc2Data = {
       message:
         "Porter-provisioned instances do not allow remote SSH access. Users are not allowed to invoke commands directly on the host, and all commands are invoked via the EKS Control Plane.",
       enabled: true,
-      hideToggle: true,
+      locked: true,
+      disabledTooltip:
+        "Enabled by default by Porter",
       status: "ENABLED",
     },
     "Cluster Secret Encryption": {
@@ -74,6 +76,15 @@ const soc2DataDefault: Soc2Data = {
       info: "",
       status: "",
     },
+    "Enable CloudWatch Alarms": {
+      message:
+        "Enter Email List",
+      link: "https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning-enhanced.html",
+      enabled: false,
+      info: "",
+      status: "",
+      email: ["example@porter.run"], // this is a special case for email
+    },
   },
 };
 
@@ -236,11 +247,23 @@ const Compliance: React.FC<Props> = (props) => {
     return "";
   };
 
+  // function to check if any fields are missing
+  const missingFields = (): boolean => {
+    const checks = soc2Data.soc2_checks;
+    for (const key in checks) {
+      if ((checks[key].enabled || soc2Enabled) && checks[key].email?.length === 0) {
+        return true;
+      }
+    }
+    return false;
+  };
+
   const isDisabled = (): boolean | undefined => {
     return (
       isUserProvisioning ||
       isClicked ||
       (currentCluster && !currentProject?.enable_reprovision)
+      || missingFields()
     );
   };
 
@@ -366,6 +389,9 @@ const Compliance: React.FC<Props> = (props) => {
           disabled={isDisabled() ?? isLoading}
           onClick={applySettings}
           status={getStatus()}
+          disabledTooltipMessage={
+            "Missing fields. Please fill out all required fields to enable SOC 2 compliance."
+          }
         >
           Save settings
         </Button>

+ 1 - 0
dashboard/src/shared/types.tsx

@@ -739,6 +739,7 @@ export type Soc2Check = {
   locked?: boolean;
   enabledField?: string;
   info?: string;
+  email?: string[];
 };
 
 export type Soc2Data = {