Răsfoiți Sursa

update endpoint?

Justin Rhee 3 ani în urmă
părinte
comite
fc3c23837f

+ 3 - 5
api/server/handlers/stacks/create_porter_app.go

@@ -31,10 +31,9 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
 	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
 
-	request := &types.CreatePorterAppRequest{}
+	request := &types.UpdatePorterAppRequest{}
 
 	ok := c.DecodeAndValidate(w, r, request)
-
 	if !ok {
 		return
 	}
@@ -53,11 +52,10 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		ImageRepoURI: request.ImageRepoURI,
 	}
 
-	_, err := c.Repo().PorterApp().CreatePorterApp(app)
-
+	porterApp, err := c.Repo().PorterApp().UpdatePorterApp(app)
 	if err != nil {
 		return
 	}
 
-	w.WriteHeader(http.StatusCreated)
+	c.WriteResult(w, r, porterApp.ToPorterAppType())
 }

+ 1 - 1
api/server/handlers/stacks/get_porter_app.go

@@ -32,7 +32,7 @@ func (c *GetPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
 	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
 
-	app, err := c.Repo().PorterApp().ReadPorterApp(cluster.ID, name)
+	app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, name)
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return

+ 18 - 1
api/server/handlers/stacks/list_porter_app.go

@@ -5,7 +5,10 @@ import (
 
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
 )
 
 type PorterAppListHandler struct {
@@ -22,6 +25,20 @@ func NewPorterAppListHandler(
 }
 
 func (p *PorterAppListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ctx := r.Context()
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
 
-	p.WriteResult(w, r, nil)
+	porterApps, err := p.Repo().PorterApp().ListPorterAppByClusterID(cluster.ID)
+	if err != nil {
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	res := make(types.ListPorterAppResponse, 0)
+
+	for _, porterApp := range porterApps {
+		res = append(res, porterApp.ToPorterAppType())
+	}
+
+	p.WriteResult(w, r, res)
 }

+ 57 - 0
api/server/handlers/stacks/update_porter_app.go

@@ -0,0 +1,57 @@
+package stacks
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type UpdatePorterAppHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewUpdatePorterAppHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UpdatePorterAppHandler {
+	return &UpdatePorterAppHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *UpdatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	fmt.Println("so an update was attempted...")
+	ctx := r.Context()
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
+
+	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
+
+	porterApp, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, name)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	request := &types.UpdatePorterAppRequest{}
+	ok := c.DecodeAndValidate(w, r, request)
+	if !ok {
+		return
+	}
+
+	updatedPorterApp, err := c.Repo().PorterApp().UpdatePorterApp(porterApp)
+	if err != nil {
+		return
+	}
+
+	c.WriteResult(w, r, updatedPorterApp.ToPorterAppType())
+}

+ 31 - 2
api/server/router/stack.go

@@ -95,7 +95,7 @@ func getStackRoutes(
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.ProjectScope,
-				types.RegistryScope,
+				types.ClusterScope,
 			},
 		},
 	)
@@ -111,7 +111,7 @@ func getStackRoutes(
 		Router:   r,
 	})
 
-	// POST /api/projects/{project_id}/clusters/{cluster_id}/stacks/update_config -> stacks.NewCreateStackHandler
+	// POST /api/projects/{project_id}/clusters/{cluster_id}/stacks/update_config -> stacks.NewCreatePorterAppHandler
 	createPorterAppEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
 			Verb:   types.APIVerbCreate,
@@ -140,6 +140,35 @@ func getStackRoutes(
 		Router:   r,
 	})
 
+	// POST /api/projects/{project_id}/clusters/{cluster_id}/stacks/{name} -> stacks.NewCreatePorterAppHandler
+	updatePorterAppEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbUpdate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/{name}",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	updatePorterAppHandler := stacks.NewUpdatePorterAppHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: updatePorterAppEndpoint,
+		Handler:  updatePorterAppHandler,
+		Router:   r,
+	})
+
 	// POST /api/projects/{project_id}/clusters/{cluster_id}/stacks -> stacks.NewCreateStackHandler
 	createEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 27 - 0
api/types/porter_app.go

@@ -20,3 +20,30 @@ type PorterApp struct {
 	Buildpacks   string `json:"build_packs,omitempty"`
 	Dockerfile   string `json:"dockerfile,omitempty"`
 }
+
+// swagger:model
+type CreatePorterAppRequest struct {
+	Name         string `json:"name" form:"required"`
+	ClusterID    uint   `json:"cluster_id"`
+	ProjectID    uint   `json:"project_id"`
+	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"`
+}
+
+type UpdatePorterAppRequest struct {
+	Name         string `json:"name"`
+	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"`
+}
+
+type ListPorterAppResponse []*PorterApp

+ 1 - 0
api/types/request.go

@@ -43,6 +43,7 @@ const (
 	URLParamInviteID              URLParam = "invite_id"
 	URLParamNamespace             URLParam = "namespace"
 	URLParamReleaseName           URLParam = "name"
+	URLParamPorterAppID           URLParam = "porter_app_id"
 	URLParamStackID               URLParam = "stack_id"
 	URLParamReleaseVersion        URLParam = "version"
 	URLParamWildcard              URLParam = "*"

+ 0 - 14
api/types/stacks.go

@@ -22,20 +22,6 @@ type CreateStackRequest struct {
 	EnvGroups []*CreateStackEnvGroupRequest `json:"env_groups,omitempty" form:"required,dive,required"`
 }
 
-// swagger:model
-type CreatePorterAppRequest struct {
-	Name         string `json:"name" form:"required"`
-	ClusterID    uint   `json:"cluster_id"`
-	ProjectID    uint   `json:"project_id"`
-	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
 type PutStackSourceConfigRequest struct {
 	SourceConfigs []*CreateStackSourceConfigRequest `json:"source_configs,omitempty" form:"required,dive,required"`

+ 2 - 1
dashboard/src/components/porter/Toggle.tsx

@@ -16,8 +16,9 @@ const Toggle: React.FC<Props> = ({
 }) => {
   return (
     <StyledToggle>
-      {items.map((item, index) => (
+      {items.map((item, i) => (
         <Item
+          key={i}
           active={item.value === active}
           onClick={() => {
             setActive(item.value);

+ 6 - 3
dashboard/src/main/home/add-on-dashboard/AddOnDashboard.tsx

@@ -116,7 +116,10 @@ const AppDashboard: React.FC<Props> = ({
   };
 
   useEffect(() => {
-    getAddOns();
+    // currentCluster sometimes returns as -1 and passes null check
+    if (currentProject?.id >= 0 && currentCluster?.id >= 0) {
+      getAddOns();
+    }
   }, [currentCluster, currentProject]);
 
   const getExpandedChartLinkURL = useCallback((x: any) => {
@@ -181,7 +184,7 @@ const AppDashboard: React.FC<Props> = ({
         <GridList>
           {(filteredAddOns ?? []).map((app: any, i: number) => {
             return (
-              <Block to={getExpandedChartLinkURL(app)}>
+              <Block to={getExpandedChartLinkURL(app)} key={i}>
                 <Text size={14}>
                   <Icon 
                     src={
@@ -204,7 +207,7 @@ const AppDashboard: React.FC<Props> = ({
         <List>
           {(filteredAddOns ?? []).map((app: any, i: number) => {
             return (
-              <Row to={getExpandedChartLinkURL(app)}>
+              <Row to={getExpandedChartLinkURL(app)} key={i}>
                 <Text size={14}>
                   <MidIcon
                     src={

+ 18 - 9
dashboard/src/main/home/app-dashboard/AppDashboard.tsx

@@ -21,6 +21,7 @@ import Text from "components/porter/Text";
 import SearchBar from "components/porter/SearchBar";
 import Toggle from "components/porter/Toggle";
 import Link from "components/porter/Link";
+import Loading from "components/Loading";
 
 type Props = {
 };
@@ -47,6 +48,7 @@ const AppDashboard: React.FC<Props> = ({
 }) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [apps, setApps] = useState([]);
+  const [error, setError] = useState(null);
   const [searchValue, setSearchValue] = useState("");
   const [view, setView] = useState("grid");
   const [isLoading, setIsLoading] = useState(true);
@@ -65,25 +67,32 @@ const AppDashboard: React.FC<Props> = ({
   }, [apps, searchValue]);
 
   const getApps = async () => {
-    
+    setIsLoading(true);
+
     // TODO: Currently using namespaces as placeholder (replace with apps)
     try {
-      const res = await api.getNamespaces(
+      const res = await api.getPorterApps(
         "<token>",
         {},
         {
-          id: currentProject.id,
+          project_id: currentProject.id,
           cluster_id: currentCluster.id,
         }
       )
       setApps(res.data);
+      setIsLoading(false);
+    }
+    catch (err) {
+      setError(err);
+      setIsLoading(false);
     }
-    catch (err) {}
   };
 
   useEffect(() => {
-    getApps();
-  }, []);
+    if (currentProject?.id > 0 && currentCluster?.id > 0) {
+      getApps();
+    }
+  }, [currentCluster, currentProject]);
 
   return (
     <StyledAppDashboard>
@@ -117,12 +126,12 @@ const AppDashboard: React.FC<Props> = ({
         </Link>
       </Container>
       <Spacer y={1} />
-      {view === "grid" ? (
+      {isLoading ? <Loading offset="-150px" /> : view === "grid" ? (
         <GridList>
          {(filteredApps ?? []).map((app: any, i: number) => {
            if (!namespaceBlacklist.includes(app.name)) {
              return (
-               <Block>
+               <Block key={i}>
                  <Text size={14}>
                    <Icon src={icons[i % icons.length]} />
                    {app.name}
@@ -146,7 +155,7 @@ const AppDashboard: React.FC<Props> = ({
           {(filteredApps ?? []).map((app: any, i: number) => {
             if (!namespaceBlacklist.includes(app.name)) {
               return (
-                <Row>
+                <Row key={i}>
                   <Text size={14}>
                     <MidIcon src={icons[i % icons.length]} />
                     {app.name}

+ 8 - 67
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -28,6 +28,13 @@ import RevisionSection from "main/home/cluster-dashboard/expanded-chart/Revision
 type Props = RouteComponentProps & {};
 
 const ExpandedApp: React.FC<Props> = ({ ...props }) => {
+  const {
+    currentCluster,
+    currentProject,
+    setCurrentError,
+    setCurrentOverlay,
+  } = useContext(Context);
+
   const [isLoading, setIsLoading] = useState(true);
   const [appData, setAppData] = useState(null);
   const [error, setError] = useState(null);
@@ -47,12 +54,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const [isAgentInstalled, setIsAgentInstalled] = useState<boolean>(false);
   const [showRevisions, setShowRevisions] = useState<boolean>(false);
   const [newestImage, setNewestImage] = useState<string>(null);
-  const {
-    currentCluster,
-    currentProject,
-    setCurrentError,
-    setCurrentOverlay,
-  } = useContext(Context);
+  
   const getPorterApp = async () => {
     setIsLoading(true);
     const { appName } = props.match.params as any;
@@ -66,7 +68,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           name: appName,
         }
       );
-      console.log(resPorterApp);
       const resChartData = await api.getChart(
         "<token>",
         {},
@@ -82,8 +83,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         app: resPorterApp?.data,
         chart: resChartData?.data,
       });
-      console.log(resChartData?.data);
-      console.log(resPorterApp?.data);
       setIsLoading(false);
     } catch (err) {
       setError(err);
@@ -230,64 +229,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     [appData?.chart]
   );
 
-  // const updateTabs = () => {
-  //   // Collate non-form tabs
-  //   let rightTabOptions = [] as any[];
-  //   let leftTabOptions = [] as any[];
-  //   if (
-  //     appData.chart.chart.metadata.home === "https://getporter.dev/" &&
-  //     (appData.chart.chart.metadata.name === "web" ||
-  //       appData.chart.chart.metadata.name === "worker" ||
-  //       appData.chart.chart.metadata.name === "job") &&
-  //     currentCluster.agent_integration_enabled
-  //   ) {
-  //     leftTabOptions.push({ label: "Events", value: "events" });
-
-  //     if (isAgentInstalled) {
-  //       leftTabOptions.push({ label: "Logs", value: "logs" });
-  //     }
-  //   }
-  //   leftTabOptions.push({ label: "Status", value: "status" });
-  //   leftTabOptions.push({ label: "Metrics", value: "metrics" });
-  //   // if (props.isMetricsInstalled) {
-  //   //   leftTabOptions.push({ label: "Metrics", value: "metrics" });
-  //   // }
-
-  //   rightTabOptions.push({ label: "Chart Overview", value: "graph" });
-
-  //   // if (devOpsMode) {
-  //   //   rightTabOptions.push(
-  //   //     { label: "Manifests", value: "list" },
-  //   //     { label: "Helm Values", value: "values" }
-  //   //   );
-  //   // }
-
-  //   if (appData.chart?.git_action_config?.git_repo) {
-  //     rightTabOptions.push({
-  //       label: "Build Settings",
-  //       value: "build-settings",
-  //     });
-  //   }
-
-  //   // Settings tab is always last
-  //   if (isAuthorized("application", "", ["get", "delete"])) {
-  //     rightTabOptions.push({ label: "Settings", value: "settings" });
-  //   }
-
-  //   // Filter tabs if previewing an old revision or updating the chart version
-  //   if (isPreview) {
-  //     const liveTabs = ["status", "events", "settings", "deploy", "metrics"];
-  //     rightTabOptions = rightTabOptions.filter(
-  //       (tab: any) => !liveTabs.includes(tab.value)
-  //     );
-  //     leftTabOptions = leftTabOptions.filter(
-  //       (tab: any) => !liveTabs.includes(tab.value)
-  //     );
-  //   }
-
-  //   setLeftTabOptions(leftTabOptions);
-  //   setRightTabOptions(rightTabOptions);
-  // };
   useEffect(() => {
     const { appName } = props.match.params as any;
     if (currentCluster && appName && currentProject) {

+ 3 - 0
dashboard/src/main/home/sidebar/Clusters.tsx

@@ -39,6 +39,9 @@ class Clusters extends Component<PropsType, StateType> {
   };
 
   updateClusters = () => {
+    if (!this.context.currentProject) {
+      return
+    }
     let {
       user,
       currentProject,

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

@@ -164,6 +164,17 @@ const createEmailVerification = baseApi<{}, {}>("POST", (pathParams) => {
   return `/api/email/verify/initiate`;
 });
 
+const getPorterApps = baseApi<
+{},
+{
+  project_id: number;
+  cluster_id: number;
+}
+>("GET", (pathParams) => {
+let { project_id, cluster_id } = pathParams;
+return `/api/projects/${project_id}/clusters/${cluster_id}/stacks`;
+});
+
 const getPorterApp = baseApi<
   {},
   {
@@ -196,6 +207,27 @@ const createPorterApp = baseApi<
   return `/api/projects/${project_id}/clusters/${cluster_id}/stacks/update_config`;
 });
 
+const updatePorterApp = baseApi<
+  {
+    name: string;
+    repo_name: string;
+    git_branch: string;
+    build_context: string;
+    builder: string;
+    buildpacks: string;
+    dockerfile: string;
+    image_repo_uri: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    name: string;
+  }
+>("POST", (pathParams) => {
+  let { project_id, cluster_id, name } = pathParams;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/stacks/${name}`;
+});
+
 const updatePorterStack = baseApi<
   {
     stack_name: string;
@@ -2530,8 +2562,10 @@ export default {
   createPasswordResetVerify,
   createPasswordResetFinalize,
   createProject,
+  getPorterApps,
   getPorterApp,
   createPorterApp,
+  updatePorterApp,
   updatePorterStack,
   createConfigMap,
   deleteCluster,

+ 1 - 1
internal/repository/gorm/porter_app.go

@@ -34,7 +34,7 @@ func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*mo
 	return apps, nil
 }
 
-func (repo *PorterAppRepository) ReadPorterApp(clusterID uint, name string) (*models.PorterApp, error) {
+func (repo *PorterAppRepository) ReadPorterAppByName(clusterID uint, name string) (*models.PorterApp, error) {
 	app := &models.PorterApp{}
 
 	if err := repo.db.Where("cluster_id = ? AND name = ?", clusterID, name).First(&app).Error; err != nil {

+ 2 - 2
internal/repository/porter_app.go

@@ -6,8 +6,8 @@ import (
 
 // PorterAppRepository represents the set of queries on the PorterApp model
 type PorterAppRepository interface {
+	ReadPorterAppByName(clusterID uint, name string) (*models.PorterApp, error)
 	CreatePorterApp(app *models.PorterApp) (*models.PorterApp, error)
-	// ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error)
-	ReadPorterApp(clusterID uint, name string) (*models.PorterApp, error)
+	ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error)
 	UpdatePorterApp(app *models.PorterApp) (*models.PorterApp, error)
 }

+ 6 - 2
internal/repository/test/porter_app.go

@@ -18,14 +18,18 @@ func NewPorterAppRepository(canQuery bool, failingMethods ...string) repository.
 
 }
 
-func (repo *PorterAppRepository) CreatePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
+func (repo *PorterAppRepository) ReadPorterAppByName(clusterID uint, name string) (*models.PorterApp, error) {
 	return nil, errors.New("cannot write database")
 }
 
-func (repo *PorterAppRepository) ReadPorterApp(clusterID uint, name string) (*models.PorterApp, error) {
+func (repo *PorterAppRepository) CreatePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
 	return nil, errors.New("cannot write database")
 }
 
 func (repo *PorterAppRepository) UpdatePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
 	return nil, errors.New("cannot write database")
 }
+
+func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error) {
+	return nil, errors.New("cannot write database")
+}