Jelajahi Sumber

handle 0, 1, >1 registry integrations for repo CI

jusrhee 5 tahun lalu
induk
melakukan
0a597ca0ad

+ 12 - 56
dashboard/src/components/repo-selector/ActionConfEditor.tsx

@@ -20,6 +20,7 @@ type PropsType = {
   folderPath: string;
   setFolderPath: (x: string) => void;
   setSelectedRegistryId: (x: number) => void;
+  selectedRegistryId: number;
 };
 
 type StateType = {
@@ -93,31 +94,17 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
       );
     }
     return (
-      <>
-        <ActionDetails
-          branch={branch}
-          actionConfig={actionConfig}
-          setActionConfig={setActionConfig}
-          dockerfilePath={this.props.dockerfilePath}
-          folderPath={this.props.folderPath}
-          setSelectedRegistryId={this.props.setSelectedRegistryId}
-        />
-        <Flex>
-          <BackButton
-            width="140px"
-            onClick={() => {
-              this.props.setDockerfilePath(null);
-              this.props.setFolderPath(null);
-            }}
-          >
-            <i className="material-icons">keyboard_backspace</i>
-            Select Folder
-          </BackButton>
-          <StatusWrapper>
-            <i className="material-icons">done</i> Source selected.
-          </StatusWrapper>
-        </Flex>
-      </>
+      <ActionDetails
+        branch={branch}
+        setDockerfilePath={this.props.setDockerfilePath}
+        setFolderPath={this.props.setFolderPath}
+        actionConfig={actionConfig}
+        setActionConfig={setActionConfig}
+        dockerfilePath={this.props.dockerfilePath}
+        folderPath={this.props.folderPath}
+        setSelectedRegistryId={this.props.setSelectedRegistryId}
+        selectedRegistryId={this.props.selectedRegistryId}
+      />
     );
   };
 
@@ -128,37 +115,6 @@ export default class ActionConfEditor extends Component<PropsType, StateType> {
 
 ActionConfEditor.contextType = Context;
 
-const StatusWrapper = styled.div`
-  display: flex;
-  align-items: center;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #ffffff55;
-  margin-right: 25px;
-  margin-left: 20px;
-  margin-top: 26px;
-
-  > i {
-    font-size: 18px;
-    margin-right: 10px;
-    color: #4797ff;
-  }
-
-  animation: statusFloatIn 0.5s;
-  animation-fill-mode: forwards;
-
-  @keyframes statusFloatIn {
-    from {
-      opacity: 0;
-      transform: translateY(10px);
-    }
-    to {
-      opacity: 1;
-      transform: translateY(0px);
-    }
-  }
-`;
-
 const Br = styled.div`
   width: 100%;
   height: 8px;

+ 216 - 1
dashboard/src/components/repo-selector/ActionDetails.tsx

@@ -2,7 +2,10 @@ import ImageSelector from "components/image-selector/ImageSelector";
 import React, { Component } from "react";
 import styled from "styled-components";
 
+import { integrationList } from "shared/common";
 import { Context } from "../../shared/Context";
+import api from "../../shared/api";
+import Loading from "components/Loading";
 import { ActionConfigType } from "../../shared/types";
 import InputRow from "../values-form/InputRow";
 
@@ -13,19 +16,88 @@ type PropsType = {
   dockerfilePath: string;
   folderPath: string;
   setSelectedRegistryId: (x: number) => void;
+  selectedRegistryId: number;
+  setDockerfilePath: (x: string) => void;
+  setFolderPath: (x: string) => void;
 };
 
 type StateType = {
   dockerRepo: string;
   error: boolean;
+  registries: any[] | null;
+  loading: boolean;
 };
 
+const dummyRegistries = [
+  { id: 1, service: 'ecr', url: 'https://idfkasdfasdf' },
+  { id: 12, service: 'ecr', url: 'https://dfasdfidfkasdfasdf' },
+  { id: 11, service: 'gcr', url: 'https://idfkasdfasdf' },
+] as any[];
+
 export default class ActionDetails extends Component<PropsType, StateType> {
   state = {
     dockerRepo: "",
     error: false,
+    registries: null as any[] | null,
+    loading: true,
+  };
+
+  componentDidMount() {
+    api
+      .getProjectRegistries("<token>", {}, { id: this.context.currentProject.id })
+      .then((res: any) => {
+        this.setState({ registries: res.data, loading: false });
+        if (res.data.length === 1) {
+          this.props.setSelectedRegistryId(res.data[0].id);
+        }
+      })
+      .catch((err: any) => console.log(err));
+  }
+
+  renderIntegrationList = () => {
+    let { loading, registries } = this.state;
+    if (loading) {
+      return (
+        <LoadingWrapper>
+          <Loading />
+        </LoadingWrapper>
+      );
+    }
+
+    return registries.map((registry: any, i: number) => {
+      let icon =
+        integrationList[registry.service] && integrationList[registry.service].icon;
+      if (!icon) {
+        icon = integrationList["docker"].icon;
+      }
+      return (
+        <RegistryItem
+          key={i}
+          isSelected={registry.id === this.props.selectedRegistryId}
+          lastItem={i === registries.length - 1}
+          onClick={() => this.props.setSelectedRegistryId(registry.id)}
+        >
+          <img src={icon && icon} />
+          {registry.url}
+        </RegistryItem>
+      );
+    });
   };
 
+  renderRegistrySection = () => {
+    let { registries } = this.state;
+    if (!registries || registries.length === 0 || registries.length === 1) {
+      return;
+    } else {
+      return (
+        <>
+          <Subtitle>Container Registry</Subtitle>
+          <ExpandedWrapper>{this.renderIntegrationList()}</ExpandedWrapper>
+        </>
+      );
+    }
+  }
+
   render() {
     return (
       <>
@@ -54,8 +126,32 @@ export default class ActionDetails extends Component<PropsType, StateType> {
             value={this.props.folderPath}
           />
         )}
-        <AdvancedHeader>Advanced Settings</AdvancedHeader>
+        {this.renderRegistrySection()}
         <Br />
+
+        <Flex>
+          <BackButton
+            width="140px"
+            onClick={() => {
+              this.props.setDockerfilePath(null);
+              this.props.setFolderPath(null);
+            }}
+          >
+            <i className="material-icons">keyboard_backspace</i>
+            Select Folder
+          </BackButton>
+          { 
+            this.props.selectedRegistryId ? (
+              <StatusWrapper successful={true}>
+                <i className="material-icons">done</i> Source selected.
+              </StatusWrapper>
+            ) : (
+              <StatusWrapper>
+                <i className="material-icons">error_outline</i> A connected container registry is required
+              </StatusWrapper>
+            )
+          }
+        </Flex>
       </>
     );
   }
@@ -63,6 +159,125 @@ export default class ActionDetails extends Component<PropsType, StateType> {
 
 ActionDetails.contextType = Context;
 
+const Subtitle = styled.div`
+  margin-top: 21px;
+`;
+
+const RegistryItem = styled.div`
+  display: flex;
+  width: 100%;
+  font-size: 13px;
+  border-bottom: 1px solid
+    ${(props: { lastItem: boolean; isSelected: boolean }) =>
+      props.lastItem ? "#00000000" : "#606166"};
+  color: #ffffff;
+  user-select: none;
+  align-items: center;
+  padding: 10px 0px;
+  cursor: pointer;
+  background: ${(props: { isSelected: boolean; lastItem: boolean }) =>
+    props.isSelected ? "#ffffff11" : ""};
+  :hover {
+    background: #ffffff22;
+
+    > i {
+      background: #ffffff22;
+    }
+  }
+
+  > img {
+    width: 18px;
+    height: 18px;
+    margin-left: 12px;
+    margin-right: 12px;
+    filter: grayscale(100%);
+  }
+`;
+
+const LoadingWrapper = styled.div`
+  padding: 30px 0px;
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  justify-content: center;
+  color: #ffffff44;
+`;
+
+const ExpandedWrapper = styled.div`
+  margin-top: 10px;
+  width: 100%;
+  border-radius: 3px;
+  border: 1px solid #ffffff44;
+  max-height: 275px;
+  background: #ffffff11;
+  overflow-y: auto;
+  margin-bottom: 15px;
+`;
+
+const StatusWrapper = styled.div<{ successful?: boolean }>`
+  display: flex;
+  align-items: center;
+  font-family: "Work Sans", sans-serif;
+  font-size: 13px;
+  color: #ffffff55;
+  margin-right: 25px;
+  margin-left: 20px;
+  margin-top: 26px;
+
+  > i {
+    font-size: 18px;
+    margin-right: 10px;
+    color: ${props => props.successful ? "#4797ff" : "#fcba03"};
+  }
+
+  animation: statusFloatIn 0.5s;
+  animation-fill-mode: forwards;
+
+  @keyframes statusFloatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 22px;
+  cursor: pointer;
+  font-size: 13px;
+  height: 35px;
+  padding: 5px 13px;
+  margin-bottom: -7px;
+  padding-right: 15px;
+  border: 1px solid #ffffff55;
+  border-radius: 3px;
+  width: ${(props: { width: string }) => props.width};
+  color: white;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: white;
+    font-size: 16px;
+    margin-right: 6px;
+  }
+`;
+
 const AdvancedHeader = styled.div`
   margin-top: 15px;
 `;

+ 12 - 1
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -294,7 +294,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
 
       // Allow if source type is repo and dockerfile or folder path is set
       if (sourceType === "repo" && (dockerfilePath || folderPath)) {
-        return false;
+        return !this.state.selectedRegistryId;
       }
 
       return true;
@@ -304,7 +304,17 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
   };
 
   getStatus = () => {
+    let {
+      selectedRegistryId,
+      sourceType,
+      dockerfilePath,
+      folderPath,
+    } = this.state;
+
     if (this.submitIsDisabled()) {
+      if (sourceType === "repo" && (dockerfilePath || folderPath) && !selectedRegistryId) {
+        return "A connected container registry is required"
+      }
       let { templateName } = this.state;
       if (templateName.length > 0 && !isAlphanumeric(templateName)) {
         return "Template name contains illegal characters";
@@ -580,6 +590,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
             setSelectedRegistryId={(x: number) => {
               this.setState({ selectedRegistryId: x });
             }}
+            selectedRegistryId={this.state.selectedRegistryId}
           />
           <br />
         </StyledSourceBox>