Explorar o código

adding image url

Feroze Mohideen %!s(int64=3) %!d(string=hai) anos
pai
achega
fbd885a218

+ 1 - 0
api/server/handlers/stacks/create_porter_app.go

@@ -50,6 +50,7 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		Builder:      request.Builder,
 		Buildpacks:   request.Buildpacks,
 		Dockerfile:   request.Dockerfile,
+		ImageRepoURI: request.ImageRepoURI,
 	}
 
 	_, err := c.Repo().PorterApp().CreatePorterApp(app)

+ 0 - 3
api/server/handlers/stacks/get_porter_app.go

@@ -1,7 +1,6 @@
 package stacks
 
 import (
-	"fmt"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/authz"
@@ -39,7 +38,5 @@ func (c *GetPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	fmt.Println("got here", app)
-
 	c.WriteResult(w, r, app.ToPorterAppType())
 }

+ 27 - 0
api/server/handlers/stacks/list_porter_app.go

@@ -0,0 +1,27 @@
+package stacks
+
+import (
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/config"
+)
+
+type PorterAppListHandler struct {
+	handlers.PorterHandlerWriter
+}
+
+func NewPorterAppListHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *PorterAppListHandler {
+	return &PorterAppListHandler{
+		PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+	}
+}
+
+func (p *PorterAppListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+
+	p.WriteResult(w, r, nil)
+}

+ 28 - 0
api/server/router/stack.go

@@ -83,6 +83,34 @@ func getStackRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/stacks -> stacks.NewPorterAppListHandler
+	listPorterAppEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbList,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath,
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.RegistryScope,
+			},
+		},
+	)
+
+	listPorterAppHandler := stacks.NewPorterAppListHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: listPorterAppEndpoint,
+		Handler:  listPorterAppHandler,
+		Router:   r,
+	})
+
 	// POST /api/projects/{project_id}/clusters/{cluster_id}/stacks/update_config -> stacks.NewCreateStackHandler
 	createPorterAppEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 4 - 3
api/types/stacks.go

@@ -27,12 +27,13 @@ type CreatePorterAppRequest struct {
 	Name         string `json:"name" form:"required"`
 	ClusterID    uint   `json:"cluster_id"`
 	ProjectID    uint   `json:"project_id"`
-	RepoName     string `json:"repo_name" form:"required"`
-	GitBranch    string `json:"git_branch" form:"required"`
-	BuildContext string `json:"build_context" form:"required"`
+	RepoName     string `json:"repo_name"`
+	GitBranch    string `json:"git_branch"`
+	BuildContext string `json:"build_context"`
 	Builder      string `json:"builder"`
 	Buildpacks   string `json:"buildpacks"`
 	Dockerfile   string `json:"dockerfile"`
+	ImageRepoURI string `json:"image_repo_uri"`
 }
 
 // swagger:model

+ 5 - 8
dashboard/src/components/TitleSection.tsx

@@ -41,10 +41,7 @@ const TitleSection: React.FC<Props> = ({
           <Icon width={iconWidth} src={icon} />
         ))}
 
-      <StyledTitle
-        capitalize={capitalize}
-        onClick={onClick}
-      >
+      <StyledTitle capitalize={capitalize} onClick={onClick}>
         {children}
       </StyledTitle>
     </StyledTitleSection>
@@ -85,19 +82,19 @@ const MaterialIcon = styled.span<{ width: string }>`
   margin-right: 16px;
 `;
 
-const StyledTitle = styled.div<{ 
+const StyledTitle = styled.div<{
   capitalize: boolean;
   onClick?: any;
 }>`
   font-size: 21px;
   user-select: text;
-  color: ${props => props.theme.text.primary};
+  color: ${(props) => props.theme.text.primary};
   text-transform: ${(props) => (props.capitalize ? "capitalize" : "")};
   display: flex;
   align-items: center;
-  cursor: ${props => props.onClick ? "pointer" : ""};
+  cursor: ${(props) => (props.onClick ? "pointer" : "")};
   :hover {
-    text-decoration: ${props => props.onClick ? "underline" : ""};
+    text-decoration: ${(props) => (props.onClick ? "underline" : "")};
   }
 
   > i {

+ 117 - 0
dashboard/src/components/TitleSectionStacks.tsx

@@ -0,0 +1,117 @@
+import React from "react";
+import styled from "styled-components";
+
+interface Props {
+  children: React.ReactNode;
+  icon?: any;
+  iconWidth?: string;
+  capitalize?: boolean;
+  className?: string;
+  materialIconClass?: string;
+  handleNavBack?: () => void;
+  onClick?: any;
+}
+
+const TitleSectionStacks: React.FC<Props> = ({
+  children,
+  icon,
+  iconWidth,
+  capitalize,
+  handleNavBack,
+  className,
+  materialIconClass,
+  onClick,
+}) => {
+  return (
+    <StyledTitleSection className={className}>
+      {handleNavBack && (
+        <BackButton>
+          <i className="material-icons" onClick={handleNavBack}>
+            keyboard_backspace
+          </i>
+        </BackButton>
+      )}
+
+      {icon && <Icon disableMarginRight={true} className={icon} />}
+
+      <StyledTitle capitalize={capitalize} onClick={onClick}>
+        {children}
+      </StyledTitle>
+    </StyledTitleSection>
+  );
+};
+
+export default TitleSectionStacks;
+
+const BackButton = styled.div`
+  > i {
+    cursor: pointer;
+    font-size: 24px;
+    color: #aaaabb;
+    margin-right: 10px;
+    padding: 3px;
+    margin-left: 0px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const StyledTitleSection = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const Icon = styled.span<{ disableMarginRight: boolean }>`
+  font-size: 24px;
+  margin-right: 10px;
+  ${(props) => {
+    if (!props.disableMarginRight) {
+      return "margin-right: 20px";
+    }
+  }}
+`;
+
+const StyledTitle = styled.div<{
+  capitalize: boolean;
+  onClick?: any;
+}>`
+  font-size: 21px;
+  user-select: text;
+  color: ${(props) => props.theme.text.primary};
+  text-transform: ${(props) => (props.capitalize ? "capitalize" : "")};
+  display: flex;
+  align-items: center;
+  cursor: ${(props) => (props.onClick ? "pointer" : "")};
+  :hover {
+    text-decoration: ${(props) => (props.onClick ? "underline" : "")};
+  }
+
+  > i {
+    margin-left: 10px;
+    cursor: pointer;
+    font-size: 18px;
+    color: #858faaaa;
+    padding: 5px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+    margin-bottom: -3px;
+  }
+
+  > a {
+    > i {
+      display: flex;
+      align-items: center;
+      margin-bottom: -2px;
+      font-size: 18px;
+      margin-left: 15px;
+      color: #858faaaa;
+      :hover {
+        color: #aaaabb;
+      }
+    }
+  }
+`;

+ 113 - 41
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -1,6 +1,7 @@
 import React, { useEffect, useState, useContext } from "react";
 import { RouteComponentProps, withRouter } from "react-router";
 import styled from "styled-components";
+import { DeviconsNameList } from "assets/devicons-name-list";
 
 import api from "shared/api";
 import { Context } from "shared/Context";
@@ -16,13 +17,14 @@ import Link from "components/porter/Link";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
 import Back from "components/porter/Back";
 import TabSelector from "components/TabSelector";
+import TitleSectionStacks from "components/TitleSectionStacks";
+import DeploymentTypeStacks from "main/home/cluster-dashboard/expanded-chart/DeploymentTypeStacks";
+import DeployStatusSection from "main/home/cluster-dashboard/expanded-chart/deploy-status-section/DeployStatusSection";
+import { integrationList } from "shared/common";
 
-type Props = RouteComponentProps & {
-};
+type Props = RouteComponentProps & {};
 
-const ExpandedApp: React.FC<Props> = ({
-  ...props
-}) => {
+const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const { currentCluster, currentProject } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [appData, setAppData] = useState(null);
@@ -43,6 +45,7 @@ const ExpandedApp: React.FC<Props> = ({
           name: appName,
         }
       );
+      console.log(resPorterApp);
       const resChartData = await api.getChart(
         "<token>",
         {},
@@ -58,78 +61,126 @@ const ExpandedApp: React.FC<Props> = ({
         app: resPorterApp?.data,
         chart: resChartData?.data,
       });
+      console.log(resChartData?.data);
+      console.log(resPorterApp?.data);
       setIsLoading(false);
     } catch (err) {
       setError(err);
       setIsLoading(false);
     }
-  }
+  };
+  const renderIcon = (str: string) => {
+    let value = str.split(",");
+    let buildpack = value[0];
+    // console.log(value);
+    // value.forEach((buildpack) => {
+    //   console.log(buildpack);
+    const [languageName] = buildpack.split("/").reverse();
+    const devicon = DeviconsNameList.find(
+      (devicon) => languageName.toLowerCase() === devicon.name
+    );
+    if (devicon) {
+      const icon = `devicon-${devicon?.name}-plain colored`;
+      return icon;
+    }
+    // });
+    return "";
+  };
 
   useEffect(() => {
-    if (currentCluster) {
+    const { appName } = props.match.params as any;
+    if (currentCluster && appName && currentProject) {
       getPorterApp();
     }
   }, [currentCluster]);
 
+  const getReadableDate = (s: string) => {
+    const ts = new Date(s);
+    const date = ts.toLocaleDateString();
+    const time = ts.toLocaleTimeString([], {
+      hour: "numeric",
+      minute: "2-digit",
+    });
+    return `${time} on ${date}`;
+  };
   const renderTabContents = () => {
     switch (tab) {
       case "overview":
-        return (
-          <div>TODO: service list</div>
-        );
+        return <div>TODO: service list</div>;
       case "build-settings":
-        return (
-          <div>TODO: build settings</div>
-        );
+        return <div>TODO: build settings</div>;
       case "settings":
-        return (
-          <div>TODO: stack deletion</div>
-        )
+        return <div>TODO: stack deletion</div>;
       default:
-        return (
-          <div>dream on</div>
-        )
+        return <div>dream on</div>;
     }
   };
 
   return (
     <StyledExpandedApp>
-      {isLoading && (
-        <Loading />
-      )}
+      {isLoading && <Loading />}
       {!appData && !isLoading && (
         <Placeholder>
           <Container row>
             <PlaceholderIcon src={notFound} />
             <Text color="helper">
-              No application matching "{(props.match.params as any).appName}" was found.
+              No application matching "{(props.match.params as any).appName}"
+              was found.
             </Text>
           </Container>
           <Spacer y={1} />
-          <Link to="/apps">Return to dashboard</Link> 
+          <Link to="/apps">Return to dashboard</Link>
         </Placeholder>
       )}
       {appData && (
         <>
-          <Back to="/apps" />
-          <Container row>
+          {/* <Container row>
             <Icon src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-plain.svg" />
-            <Text size={21}>
-              {appData.name}
-            </Text>
+            <Text size={21}>{appData.name}</Text>
             <Spacer inline x={1} />
-            <Text size={13}>
-              repo: porter-dev/porter
-            </Text>
+            <Text size={13}>repo: porter-dev/porter</Text>
             <Spacer inline x={1} />
-            <Text size={13}>
-              branch: main
-            </Text>
-          </Container>
-          <Spacer y={1} />
-          <Text color="helper">
-            Last updated 2 days ago
-          </Text>
+            <Text size={13}>branch: main</Text>
+          </Container> */}
+          <HeaderWrapper>
+            <TitleSectionStacks
+              icon={
+                appData.app.build_packs &&
+                renderIcon(appData.app.build_packs) != ""
+                  ? renderIcon(appData.app.build_packs)
+                  : integrationList.registry.icon
+              }
+              iconWidth="33px"
+            >
+              {appData.chart.canonical_name === ""
+                ? appData.chart.name
+                : appData.chart.canonical_name}
+              <DeploymentTypeStacks appData={appData} />
+            </TitleSectionStacks>
+
+            {/* {currentChart.chart.metadata.name != "worker" &&
+              currentChart.chart.metadata.name != "job" &&
+              renderUrl()} */}
+
+            {/* //{currentChart.canonical_name !== "" && renderHelmReleaseName()} */}
+            <InfoWrapper>
+              {/*
+                  <StatusIndicator
+                    controllers={controllers}
+                    status={currentChart.info.status}
+                    margin_left={"0px"}
+                  />
+                  */}
+              {/* <DeployStatusSection
+                chart={appData.chart}
+                setLogData={test}//renderLogsAtTimestamp}
+              /> */}
+              <LastDeployed>
+                <Dot>•</Dot>Last deployed
+                {" " + getReadableDate(appData.chart.info.last_deployed)}
+              </LastDeployed>
+            </InfoWrapper>
+          </HeaderWrapper>
           <Spacer y={1} />
           <TabSelector
             options={[
@@ -177,4 +228,25 @@ const Placeholder = styled.div`
 const StyledExpandedApp = styled.div`
   width: 100%;
   height: 100%;
-`;
+`;
+
+const HeaderWrapper = styled.div`
+  position: relative;
+`;
+const LastDeployed = styled.div`
+  font-size: 13px;
+  margin-left: 8px;
+  margin-top: -1px;
+  display: flex;
+  align-items: center;
+  color: #aaaabb66;
+`;
+const Dot = styled.div`
+  margin-right: 16px;
+`;
+const InfoWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  margin-left: 3px;
+  margin-top: 22px;
+`;

+ 9 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -186,6 +186,12 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       const finalPorterYaml = createFinalPorterYaml();
       const yamlString = yaml.dump(finalPorterYaml);
       const base64Encoded = btoa(yamlString);
+      const imageInfo = imageUrl ? {
+        image_info: {
+          repository: imageUrl,
+          tag: imageTag,
+        }
+      } : {}
 
       // only deploy + write to DB if we can create a final porter yaml
       await Promise.all([
@@ -197,8 +203,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
             git_branch: branch,
             build_context: folderPath,
             builder: (buildConfig as any)?.builder,
-            buildpacks: (buildConfig as any)?.buildpacks,
+            buildpacks: (buildConfig as any)?.buildpacks.join(",") ?? "",
             dockerfile: dockerfilePath,
+            image_repo_uri: imageUrl,
           },
           {
             cluster_id: currentCluster.id,
@@ -210,6 +217,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           {
             stack_name: formState.applicationName,
             porter_yaml: base64Encoded,
+            ...imageInfo,
           },
           {
             cluster_id: currentCluster.id,

+ 104 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/DeploymentTypeStacks.tsx

@@ -0,0 +1,104 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+
+import { integrationList } from "shared/common";
+import { ChartType } from "shared/types";
+
+type Props = {
+  appData: any;
+};
+
+const DeploymentTypeStacks: React.FC<Props> = ({ appData }) => {
+  const [showRepoTooltip, setShowRepoTooltip] = useState(false);
+
+  const githubRepository = appData?.app.repo_name;
+  const icon = githubRepository
+    ? integrationList.repo.icon
+    : integrationList.registry.icon;
+
+  const repository =
+    githubRepository ||
+    appData.cluster.image_repo_uri ||
+    appData.cluster.config?.image?.repository;
+
+  if (repository?.includes("hello-porter")) {
+    return null;
+  }
+
+  return (
+    <DeploymentImageContainer>
+      <DeploymentTypeIcon src={icon} />
+      <RepositoryName
+        onMouseOver={() => {
+          setShowRepoTooltip(true);
+        }}
+        onMouseOut={() => {
+          setShowRepoTooltip(false);
+        }}
+      >
+        {repository}
+      </RepositoryName>
+      {showRepoTooltip && <Tooltip>{repository}</Tooltip>}
+    </DeploymentImageContainer>
+  );
+};
+
+export default DeploymentTypeStacks;
+
+const DeploymentImageContainer = styled.div`
+  height: 20px;
+  font-size: 13px;
+  position: relative;
+  display: flex;
+  margin-left: 15px;
+  margin-bottom: -3px;
+  align-items: center;
+  font-weight: 400;
+  justify-content: center;
+  color: #ffffff66;
+  padding-left: 5px;
+`;
+
+const Icon = styled.img`
+  width: 100%;
+`;
+
+const DeploymentTypeIcon = styled(Icon)`
+  width: 20px;
+  margin-right: 10px;
+`;
+
+const RepositoryName = styled.div`
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 390px;
+  position: relative;
+  margin-right: 3px;
+`;
+
+const Tooltip = styled.div`
+  position: absolute;
+  left: -40px;
+  top: 28px;
+  min-height: 18px;
+  max-width: calc(700px);
+  padding: 5px 7px;
+  background: #272731;
+  z-index: 999;
+  color: white;
+  font-size: 12px;
+  font-family: "Work Sans", sans-serif;
+  outline: 1px solid #ffffff55;
+  opacity: 0;
+  animation: faded-in 0.2s 0.15s;
+  animation-fill-mode: forwards;
+  @keyframes faded-in {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;

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

@@ -185,6 +185,7 @@ const createPorterApp = baseApi<
     builder: string;
     buildpacks: string;
     dockerfile: string;
+    image_repo_uri: string;
   },
   {
     project_id: number;

+ 3 - 5
internal/repository/gorm/porter_app.go

@@ -27,11 +27,9 @@ func (repo *PorterAppRepository) CreatePorterApp(a *models.PorterApp) (*models.P
 func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error) {
 	apps := []*models.PorterApp{}
 
-	/*
-		if err := repo.db.Where("project_id = ? AND NOT revoked", projectID).Find(&tokens).Error; err != nil {
-			return nil, err
-		}
-	*/
+	if err := repo.db.Where("cluster_id = ?", clusterID).Find(&apps).Error; err != nil {
+		return nil, err
+	}
 
 	return apps, nil
 }