Просмотр исходного кода

Merge branch 'belanger/por-83-usage-enforcement' of github.com:porter-dev/porter into belanger/por-83-usage-enforcement

jnfrati 4 лет назад
Родитель
Сommit
8d5b7bfa42
40 измененных файлов с 400 добавлено и 782 удалено
  1. 57 0
      api/server/handlers/user/welcome_webhook.go
  2. 25 0
      api/server/router/user.go
  3. 7 0
      api/types/user.go
  4. 6 3
      dashboard/src/components/porter-form/field-components/KeyValueArray.tsx
  5. 2 2
      dashboard/src/components/repo-selector/ContentsList.tsx
  6. 1 3
      dashboard/src/main/auth/Login.tsx
  7. 1 3
      dashboard/src/main/auth/ResetPasswordInit.tsx
  8. 1 3
      dashboard/src/main/auth/VerifyEmail.tsx
  9. 11 5
      dashboard/src/main/home/Home.tsx
  10. 1 1
      dashboard/src/main/home/WelcomeForm.tsx
  11. 19 14
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  12. 4 8
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx
  13. 5 4
      dashboard/src/main/home/cluster-dashboard/expanded-chart/NotificationSettingsSection.tsx
  14. 7 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx
  15. 11 12
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  16. 1 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx
  17. 21 26
      dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx
  18. 2 4
      dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobList.tsx
  19. 2 4
      dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx
  20. 1 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx
  21. 1 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx
  22. 1 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx
  23. 1 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  24. 2 2
      dashboard/src/main/home/launch/Launch.tsx
  25. 1 1
      dashboard/src/main/home/launch/launch-flow/SourcePage.tsx
  26. 0 72
      dashboard/src/main/home/modals/AccountSettingsModal.tsx
  27. 5 75
      dashboard/src/main/home/modals/ClusterInstructionsModal.tsx
  28. 3 61
      dashboard/src/main/home/modals/DeleteNamespaceModal.tsx
  29. 5 52
      dashboard/src/main/home/modals/EditInviteOrCollaboratorModal.tsx
  30. 3 109
      dashboard/src/main/home/modals/IntegrationsInstructionsModal.tsx
  31. 7 63
      dashboard/src/main/home/modals/IntegrationsModal.tsx
  32. 44 3
      dashboard/src/main/home/modals/Modal.tsx
  33. 3 61
      dashboard/src/main/home/modals/NamespaceModal.tsx
  34. 16 71
      dashboard/src/main/home/modals/UpdateClusterModal.tsx
  35. 89 89
      dashboard/src/main/home/modals/UsageWarningModal.tsx
  36. 23 3
      dashboard/src/main/home/project-settings/BillingPage.tsx
  37. 1 0
      dashboard/src/main/home/provisioner/ProvisionerSettings.tsx
  38. 7 10
      dashboard/src/shared/api.tsx
  39. 2 4
      dashboard/src/shared/baseApi.tsx
  40. 1 1
      docs/guides/using-env-groups.md

+ 57 - 0
api/server/handlers/user/welcome_webhook.go

@@ -0,0 +1,57 @@
+package user
+
+import (
+	"net/http"
+	"net/url"
+
+	"github.com/gorilla/schema"
+	"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"
+)
+
+type UserWelcomeHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewUserWelcomeHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *UserWelcomeHandler {
+	return &UserWelcomeHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (u *UserWelcomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	reqVals := &types.WelcomeWebhookRequest{}
+
+	if ok := u.DecodeAndValidate(w, r, reqVals); !ok {
+		return
+	}
+
+	req, err := http.NewRequest("GET", u.Config().ServerConf.WelcomeFormWebhook, nil)
+
+	if err != nil {
+		return
+	}
+
+	encoder := schema.NewEncoder()
+	dst := make(url.Values)
+
+	if err := encoder.Encode(reqVals, dst); err != nil {
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	req.URL.RawQuery = dst.Encode()
+	_, err = http.Get(req.URL.String())
+
+	if err != nil {
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+}

+ 25 - 0
api/server/router/user.go

@@ -48,6 +48,31 @@ func getUserRoutes(
 ) []*Route {
 	routes := make([]*Route, 0)
 
+	// POST /api/welcome -> user.NewUserWelcomeHandler
+	welcomeEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/welcome",
+			},
+			Scopes: []types.PermissionScope{types.UserScope},
+		},
+	)
+
+	welcomeHandler := user.NewUserWelcomeHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: welcomeEndpoint,
+		Handler:  welcomeHandler,
+		Router:   r,
+	})
+
 	// GET /api/cli/login -> user.user.NewCLILoginHandler
 	cliLoginUserEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 7 - 0
api/types/user.go

@@ -60,3 +60,10 @@ type FinalizeResetUserPasswordRequest struct {
 }
 
 type ListUserProjectsResponse []*Project
+
+type WelcomeWebhookRequest struct {
+	Email     string `json:"email" schema:"email"`
+	IsCompany bool   `json:"isCompany" schema:"isCompany"`
+	Company   string `json:"company" schema:"company"`
+	Role      string `json:"role" schema:"role"`
+}

+ 6 - 3
dashboard/src/components/porter-form/field-components/KeyValueArray.tsx

@@ -171,11 +171,14 @@ const KeyValueArray: React.FC<Props> = (props) => {
                 const prevValues = prev.values.reduce((acc, currentValue) => {
                   acc[currentValue.key] = currentValue.value;
                   return acc;
-                }, {} as Record<string, string>)
+                }, {} as Record<string, string>);
 
                 // Deconstruct the two records/objects inside one to merge their values (this will override the old duped vars too)
                 // and convert the new object back to an array usable for the component
-                const newValues = Object.entries({...prevValues, ...values})?.map(([k, v]) => {
+                const newValues = Object.entries({
+                  ...prevValues,
+                  ...values,
+                })?.map(([k, v]) => {
                   return {
                     key: k,
                     value: v,
@@ -183,7 +186,7 @@ const KeyValueArray: React.FC<Props> = (props) => {
                 });
 
                 return {
-                  values: [...newValues]
+                  values: [...newValues],
                 };
               });
             }}

+ 2 - 2
dashboard/src/components/repo-selector/ContentsList.tsx

@@ -425,7 +425,7 @@ export default class ContentsList extends Component<PropsType, StateType> {
             <p>
               <b>{this.state.autoBuildpack.name}</b> buildpack was{" "}
               <a
-                href="https://docs.getporter.dev/docs/auto-deploy-requirements#auto-build-with-cloud-native-buildpacks"
+                href="https://docs.porter.run/docs/auto-deploy-requirements#auto-build-with-cloud-native-buildpacks"
                 target="_blank"
               >
                 detected automatically
@@ -440,7 +440,7 @@ export default class ContentsList extends Component<PropsType, StateType> {
           <FlexWrapper>
             <UseButton onClick={this.handleContinue}>Continue</UseButton>
             <StatusWrapper
-              href="https://docs.getporter.dev/docs/auto-deploy-requirements#auto-build-with-cloud-native-buildpacks"
+              href="https://docs.porter.run/docs/auto-deploy-requirements#auto-build-with-cloud-native-buildpacks"
               target="_blank"
             >
               <i className="material-icons">help_outline</i>

+ 1 - 3
dashboard/src/main/auth/Login.tsx

@@ -92,9 +92,7 @@ export default class Login extends Component<PropsType, StateType> {
             authenticate();
           }
         })
-        .catch((err) =>
-          this.context.setCurrentError(err.response.data.error)
-        );
+        .catch((err) => this.context.setCurrentError(err.response.data.error));
     }
   };
 

+ 1 - 3
dashboard/src/main/auth/ResetPasswordInit.tsx

@@ -64,9 +64,7 @@ export default class ResetPasswordInit extends Component<PropsType, StateType> {
         .then((res) => {
           this.setState({ submitted: true });
         })
-        .catch((err) =>
-          this.context.setCurrentError(err.response.data.error)
-        );
+        .catch((err) => this.context.setCurrentError(err.response.data.error));
     }
   };
 

+ 1 - 3
dashboard/src/main/auth/VerifyEmail.tsx

@@ -24,9 +24,7 @@ export default class VerifyEmail extends Component<PropsType, StateType> {
       .then((res) => {
         this.setState({ submitted: true });
       })
-      .catch((err) =>
-        this.context.setCurrentError(err.response.data.error)
-      );
+      .catch((err) => this.context.setCurrentError(err.response.data.error));
   };
 
   render() {

+ 11 - 5
dashboard/src/main/home/Home.tsx

@@ -407,9 +407,7 @@ class Home extends Component<PropsType, StateType> {
             <Icon src={discordLogo} />
             Join Our Discord
           </DiscordButton>
-          {(this.context?.capabilities?.version === "production" ||
-            this.context?.capabilities?.version === "staging") &&
-            this.state.showWelcomeForm &&
+          {this.state.showWelcomeForm &&
             localStorage.getItem("welcomed") != "true" && (
               <>
                 <WelcomeForm
@@ -500,6 +498,7 @@ class Home extends Component<PropsType, StateType> {
             onRequestClose={() => setCurrentModal(null, null)}
             width="760px"
             height="650px"
+            title="Connecting to an Existing Cluster"
           >
             <ClusterInstructionsModal />
           </Modal>
@@ -512,6 +511,7 @@ class Home extends Component<PropsType, StateType> {
               onRequestClose={() => setCurrentModal(null, null)}
               width="565px"
               height="275px"
+              title="Cluster Settings"
             >
               <UpdateClusterModal
                 setRefreshClusters={(x: boolean) =>
@@ -524,7 +524,8 @@ class Home extends Component<PropsType, StateType> {
           <Modal
             onRequestClose={() => setCurrentModal(null, null)}
             width="760px"
-            height="725px"
+            height="380px"
+            title="Add a New Integration"
           >
             <IntegrationsModal />
           </Modal>
@@ -534,6 +535,7 @@ class Home extends Component<PropsType, StateType> {
             onRequestClose={() => setCurrentModal(null, null)}
             width="760px"
             height="650px"
+            title="Connecting to an Image Registry"
           >
             <IntegrationsInstructionsModal />
           </Modal>
@@ -544,6 +546,7 @@ class Home extends Component<PropsType, StateType> {
               onRequestClose={() => setCurrentModal(null, null)}
               width="600px"
               height="220px"
+              title="Add Namespace"
             >
               <NamespaceModal />
             </Modal>
@@ -554,6 +557,7 @@ class Home extends Component<PropsType, StateType> {
               onRequestClose={() => setCurrentModal(null, null)}
               width="700px"
               height="280px"
+              title="Delete Namespace"
             >
               <DeleteNamespaceModal />
             </Modal>
@@ -573,6 +577,7 @@ class Home extends Component<PropsType, StateType> {
             onRequestClose={() => setCurrentModal(null, null)}
             width="760px"
             height="440px"
+            title="Account Settings"
           >
             <AccountSettingsModal />
           </Modal>
@@ -582,7 +587,8 @@ class Home extends Component<PropsType, StateType> {
           <Modal
             onRequestClose={() => setCurrentModal(null, null)}
             width="760px"
-            height="440px"
+            height="530px"
+            title="Usage Warning"
           >
             <UsageWarningModal />
           </Modal>

+ 1 - 1
dashboard/src/main/home/WelcomeForm.tsx

@@ -22,7 +22,7 @@ const WelcomeForm: React.FunctionComponent<Props> = ({}) => {
 
   const submitForm = () => {
     api
-      .getWelcome(
+      .postWelcome(
         "<token>",
         {
           email: context.user && context.user.email,

+ 19 - 14
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -69,8 +69,9 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const [rightTabOptions, setRightTabOptions] = useState<any[]>([]);
   const [leftTabOptions, setLeftTabOptions] = useState<any[]>([]);
   const [saveValuesStatus, setSaveValueStatus] = useState<string>(null);
-  const [forceRefreshRevisions, setForceRefreshRevisions] =
-    useState<boolean>(false);
+  const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
+    false
+  );
   const [controllers, setControllers] = useState<
     Record<string, Record<string, any>>
   >({});
@@ -82,11 +83,19 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const [showRepoTooltip, setShowRepoTooltip] = useState(false);
   const [isAuthorized] = useAuth();
 
-  const { newWebsocket, openWebsocket, closeAllWebsockets, closeWebsocket } =
-    useWebsockets();
+  const {
+    newWebsocket,
+    openWebsocket,
+    closeAllWebsockets,
+    closeWebsocket,
+  } = useWebsockets();
 
-  const { currentCluster, currentProject, setCurrentError, setCurrentOverlay } =
-    useContext(Context);
+  const {
+    currentCluster,
+    currentProject,
+    setCurrentError,
+    setCurrentOverlay,
+  } = useContext(Context);
 
   // Retrieve full chart data (includes form and values)
   const getChartData = async (chart: ChartType) => {
@@ -196,8 +205,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
     try {
       const res = await api.getChartComponents(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           name: currentChart.name,
@@ -262,8 +270,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
         values: valuesYaml,
       });
     } catch (err) {
-      const parsedErr =
-        err?.response?.data?.error;
+      const parsedErr = err?.response?.data?.error;
 
       if (parsedErr) {
         err = parsedErr;
@@ -317,8 +324,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
         cb && cb();
       } catch (err) {
-        let parsedErr =
-          err?.response?.data?.error;
+        let parsedErr = err?.response?.data?.error;
 
         if (parsedErr) {
           err = parsedErr;
@@ -626,8 +632,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
     api
       .getIngress(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           name: ingressName,

+ 4 - 8
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -78,8 +78,7 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     api
       .getChart(
         "<token>",
-        {
-        },
+        {},
         {
           name: chart.name,
           revision: revision,
@@ -380,8 +379,7 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
         this.refreshChart(0);
       })
       .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.error;
+        let parsedErr = err?.response?.data?.error;
 
         if (parsedErr) {
           err = parsedErr;
@@ -407,8 +405,7 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     api
       .getJobs(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           cluster_id: currentCluster.id,
@@ -631,8 +628,7 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
 
       cb && cb();
     } catch (err) {
-      let parsedErr =
-        err?.response?.data?.error;
+      let parsedErr = err?.response?.data?.error;
 
       if (parsedErr) {
         err = parsedErr;

+ 5 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/NotificationSettingsSection.tsx

@@ -39,8 +39,7 @@ const NotificationSettingsSection: React.FC<Props> = (props) => {
     api
       .getNotificationConfig(
         "<token>",
-        {
-        },
+        {},
         {
           project_id: currentProject.id,
           namespace: props.currentChart.namespace,
@@ -103,7 +102,7 @@ const NotificationSettingsSection: React.FC<Props> = (props) => {
         setSaveLoading(false);
       });
   };
-  
+
   return (
     <>
       <Heading>Notification Settings</Heading>
@@ -120,7 +119,9 @@ const NotificationSettingsSection: React.FC<Props> = (props) => {
           {hasNotifications != null && !hasNotifications ? (
             <Banner type="warning">
               No integration has been set up for notifications.{" "}
-              <A href={`${window.location.protocol}//${window.location.host}/integrations/slack`} >
+              <A
+                href={`${window.location.protocol}//${window.location.host}/integrations/slack`}
+              >
                 Connect to Slack
               </A>
             </Banner>

+ 7 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx

@@ -47,13 +47,17 @@ class RevisionSection extends Component<PropsType, StateType> {
   refreshHistory = () => {
     let { chart } = this.props;
     let { currentCluster, currentProject } = this.context;
-    
+
     return api
       .getRevisions(
         "<token>",
+        {},
         {
-        },
-        { id: currentProject.id, namespace: chart.namespace, cluster_id: currentCluster.id, name: chart.name }
+          id: currentProject.id,
+          namespace: chart.namespace,
+          cluster_id: currentCluster.id,
+          name: chart.name,
+        }
       )
       .then((res) => {
         res.data.sort((a: ChartType, b: ChartType) => {

+ 11 - 12
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -65,10 +65,9 @@ const SettingsSection: React.FC<PropsType> = ({
     api
       .getReleaseToken(
         "<token>",
+        {},
         {
-        },
-        { 
-          id: currentProject.id, 
+          id: currentProject.id,
           name: currentChart?.name,
           namespace: currentChart?.namespace,
           cluster_id: currentCluster.id,
@@ -129,8 +128,7 @@ const SettingsSection: React.FC<PropsType> = ({
       setSaveValuesStatus("successful");
       refreshChart();
     } catch (err) {
-      let parsedErr =
-        err?.response?.data?.error;
+      let parsedErr = err?.response?.data?.error;
 
       if (parsedErr) {
         err = parsedErr;
@@ -163,8 +161,7 @@ const SettingsSection: React.FC<PropsType> = ({
         setWebhookToken(res.data.webhook_token);
       }, 500);
     } catch (err) {
-      let parsedErr =
-        err?.response?.data?.error;
+      let parsedErr = err?.response?.data?.error;
 
       if (parsedErr) {
         err = parsedErr;
@@ -288,17 +285,19 @@ const SettingsSection: React.FC<PropsType> = ({
   };
 
   const canBeCloned = () => {
-    if(chartWasDeployedWithGithub()) {
+    if (chartWasDeployedWithGithub()) {
       return false;
     }
 
     // If its not web worker or job it means is an addon, and for now it's not supported
-    if (!["web", "worker", "job"].includes(currentChart?.chart?.metadata?.name)) {
-      return false
+    if (
+      !["web", "worker", "job"].includes(currentChart?.chart?.metadata?.name)
+    ) {
+      return false;
     }
 
-    return true
-  }
+    return true;
+  };
 
   return (
     <Wrapper>

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx

@@ -77,8 +77,7 @@ export default class ValuesYaml extends Component<PropsType, StateType> {
         this.props.refreshChart();
       })
       .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.error;
+        let parsedErr = err?.response?.data?.error;
 
         if (parsedErr) {
           err = parsedErr;

+ 21 - 26
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx

@@ -88,27 +88,26 @@ const EventsTab: React.FunctionComponent<Props> = (props) => {
       if (!shouldRequest) return;
       setShouldRequest(false);
       api
-          .getReleaseSteps(
-              "<token>",
-              {
-              },
-              {
-                cluster_id: currentCluster.id,
-                namespace: props.currentChart.namespace,
-                id: currentProject.id,
-                name: props.currentChart.name,
-              }
-          )
-          .then((data) => {
-            setIsLoading(false);
-            filterData(data.data);
-          })
-          .catch((err) => {
-            setIsError(true);
-          })
-          .finally(() => {
-            setShouldRequest(true);
-          });
+        .getReleaseSteps(
+          "<token>",
+          {},
+          {
+            cluster_id: currentCluster.id,
+            namespace: props.currentChart.namespace,
+            id: currentProject.id,
+            name: props.currentChart.name,
+          }
+        )
+        .then((data) => {
+          setIsLoading(false);
+          filterData(data.data);
+        })
+        .catch((err) => {
+          setIsError(true);
+        })
+        .finally(() => {
+          setShouldRequest(true);
+        });
     };
 
     getData();
@@ -121,11 +120,7 @@ const EventsTab: React.FunctionComponent<Props> = (props) => {
   }, [currentProject, currentCluster, props.currentChart]);
 
   if (isError) {
-    return (
-        <Placeholder>
-          Error loading events.
-        </Placeholder>
-    )
+    return <Placeholder>Error loading events.</Placeholder>;
   }
 
   if (isLoading) {

+ 2 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobList.tsx

@@ -76,8 +76,7 @@ class JobList extends Component<PropsType, StateType> {
     api
       .deleteJob(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           name: job.metadata?.name,
@@ -92,8 +91,7 @@ class JobList extends Component<PropsType, StateType> {
         });
       })
       .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.error;
+        let parsedErr = err?.response?.data?.error;
         if (parsedErr) {
           err = parsedErr;
         }

+ 2 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx

@@ -59,8 +59,7 @@ export default class JobResource extends Component<PropsType, StateType> {
       )
       .then((res) => {})
       .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.error;
+        let parsedErr = err?.response?.data?.error;
         if (parsedErr) {
           err = parsedErr;
         }
@@ -74,8 +73,7 @@ export default class JobResource extends Component<PropsType, StateType> {
     api
       .getJobPods(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           name: this.props.job.metadata?.name,

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx

@@ -136,8 +136,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
     api
       .getChartControllers(
         "<token>",
-        {
-        },
+        {},
         {
           id: currentProject.id,
           name: currentChart.name,

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -230,8 +230,7 @@ const ControllerTabFC: React.FunctionComponent<Props> = ({
     api
       .deletePod(
         "<token>",
-        {
-        },
+        {},
         {
           cluster_id: currentCluster.id,
           name: pod.metadata?.name,

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -180,8 +180,7 @@ export default class Logs extends Component<PropsType, StateType> {
     api
       .getPodEvents(
         "<token>",
-        {
-        },
+        {},
         {
           name: selectedPod?.metadata?.name,
           namespace: selectedPod?.metadata?.namespace,

+ 1 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -32,8 +32,7 @@ const StatusSectionFC: React.FunctionComponent<Props> = ({
     api
       .getChartControllers(
         "<token>",
-        {
-        },
+        {},
         {
           namespace: currentChart.namespace,
           cluster_id: currentCluster.id,

+ 2 - 2
dashboard/src/main/home/launch/Launch.tsx

@@ -168,7 +168,7 @@ class Templates extends Component<PropsType, StateType> {
 
   areCloneQueryParamsValid = () => {
     const qp = getQueryParams(this.props);
-    
+
     const requiredParams = [
       "release_namespace",
       "release_template_version",
@@ -325,7 +325,7 @@ class Templates extends Component<PropsType, StateType> {
         <TemplatesWrapper>
           <TitleSection>
             Launch
-            <a href="https://docs.getporter.dev/docs/add-ons" target="_blank">
+            <a href="https://docs.porter.run/docs/addons" target="_blank">
               <i className="material-icons">help_outline</i>
             </a>
           </TitleSection>

+ 1 - 1
dashboard/src/main/home/launch/launch-flow/SourcePage.tsx

@@ -275,7 +275,7 @@ class SourcePage extends Component<PropsType, StateType> {
         <Helper>
           Learn more about
           <Highlight
-            href="https://docs.getporter.dev/docs/add-ons"
+            href="https://docs.porter.run/docs/addons"
             target="_blank"
           >
             deploying services to Porter

+ 0 - 72
dashboard/src/main/home/modals/AccountSettingsModal.tsx

@@ -1,7 +1,6 @@
 import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import close from "assets/close.png";
 import github from "assets/github.png";
 
 import { Context } from "../../../shared/Context";
@@ -42,15 +41,6 @@ const AccountSettingsModal = () => {
 
   return (
     <>
-      <CloseButton
-        onClick={() => {
-          setCurrentModal(null, null);
-        }}
-      >
-        <CloseButtonImg src={close} />
-      </CloseButton>
-      <ModalTitle>Account Settings</ModalTitle>
-
       <TabSelector
         options={tabOptions}
         currentTab={currentTab}
@@ -181,68 +171,6 @@ const GitIcon = styled.img`
   margin-left: 1px;
 `;
 
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-
-  > i {
-    background: none;
-    border-radius: 3px;
-    display: flex;
-    font-size: 18px;
-    margin-top: 1px;
-    margin-right: 10px;
-    padding: 1px;
-    align-items: center;
-    justify-content: center;
-    color: #ffffffaa;
-    border: 0;
-  }
-`;
-
-const Subtitle = styled.div`
-  margin-top: 23px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  margin-bottom: -10px;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
 const A = styled.a`
   color: #8590ff;
   text-decoration: underline;

+ 5 - 75
dashboard/src/main/home/modals/ClusterInstructionsModal.tsx

@@ -34,8 +34,8 @@ export default class ClusterInstructionsModal extends Component<
               <br />
               name=$(curl -s
               https://api.github.com/repos/porter-dev/porter/releases/latest |
-              grep "browser_download_url.*/porter_.*_Darwin_x86_64\.zip" | cut -d
-              ":" -f 2,3 | tr -d \")
+              grep "browser_download_url.*/porter_.*_Darwin_x86_64\.zip" | cut
+              -d ":" -f 2,3 | tr -d \")
               <br />
               name=$(basename $name)
               <br />
@@ -96,17 +96,7 @@ export default class ClusterInstructionsModal extends Component<
   render() {
     let { currentPage, currentTab } = this.state;
     return (
-      <StyledClusterInstructionsModal>
-        <CloseButton
-          onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}
-        >
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Connecting to an Existing Cluster</ModalTitle>
-
+      <>
         <TabSelector
           options={tabOptions}
           currentTab={currentTab}
@@ -139,7 +129,7 @@ export default class ClusterInstructionsModal extends Component<
             arrow_forward
           </i>
         </PageSection>
-      </StyledClusterInstructionsModal>
+      </>
     );
   }
 }
@@ -207,64 +197,4 @@ const Placeholder = styled.div`
 const Bold = styled.div`
   font-weight: 600;
   margin-bottom: 7px;
-`;
-
-const Subtitle = styled.div`
-  padding: 17px 0px 25px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  margin-top: 3px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledClusterInstructionsModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 32px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 3 - 61
dashboard/src/main/home/modals/DeleteNamespaceModal.tsx

@@ -20,7 +20,7 @@ const DeleteNamespaceModal = () => {
   const [status, setStatus] = useState<string>(null as string);
   const deleteNamespace = () => {
     if (namespaceNameForDelition !== currentModalData.metadata.name) {
-      setStatus("Please insert the name of the namespace to confirm deletion");
+      setStatus("Please enter the name of this namespace to confirm deletion");
       return;
     }
 
@@ -47,16 +47,7 @@ const DeleteNamespaceModal = () => {
   };
 
   return (
-    <StyledUpdateProjectModal>
-      <CloseButton
-        onClick={() => {
-          setCurrentModal(null, null);
-        }}
-      >
-        <CloseButtonImg src={close} />
-      </CloseButton>
-
-      <ModalTitle>Delete Namespace</ModalTitle>
+    <>
       <Subtitle>
         Please insert the name of the namespace to delete it:
         <DangerText>{" " + currentModalData.metadata.name}</DangerText>
@@ -84,7 +75,7 @@ const DeleteNamespaceModal = () => {
         onClick={() => deleteNamespace()}
         status={status}
       />
-    </StyledUpdateProjectModal>
+    </>
   );
 };
 
@@ -133,55 +124,6 @@ const Subtitle = styled.div`
   margin-bottom: -10px;
 `;
 
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledUpdateProjectModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
-
 const Warning = styled.div`
   font-size: 13px;
   display: flex;

+ 5 - 52
dashboard/src/main/home/modals/EditInviteOrCollaboratorModal.tsx

@@ -80,15 +80,7 @@ const EditCollaboratorModal = () => {
   };
 
   return (
-    <StyledUpdateProjectModal>
-      <CloseButton
-        onClick={() => {
-          setCurrentModal(null, null);
-        }}
-      >
-        <CloseButtonImg src={close} />
-      </CloseButton>
-
+    <>
       <ModalTitle>
         Update {isInvite ? "Invite for" : "Collaborator"} {user?.email}
       </ModalTitle>
@@ -107,7 +99,7 @@ const EditCollaboratorModal = () => {
         onClick={() => handleUpdate()}
         status={status}
       />
-    </StyledUpdateProjectModal>
+    </>
   );
 };
 
@@ -130,50 +122,11 @@ const Subtitle = styled.div`
 `;
 
 const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: "Work Sans", sans-serif;
   font-size: 18px;
-  color: #ffffff;
+  font-weight: 500;
+  margin-bottom: 10px;
   user-select: none;
-  font-weight: 700;
-  align-items: center;
   position: relative;
   white-space: nowrap;
   text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledUpdateProjectModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 3 - 109
dashboard/src/main/home/modals/IntegrationsInstructionsModal.tsx

@@ -47,17 +47,7 @@ export default class ClusterInstructionsModal extends Component<
   render() {
     let { currentPage, currentTab } = this.state;
     return (
-      <StyledClusterInstructionsModal>
-        <CloseButton
-          onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}
-        >
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Connecting to an Image Registry</ModalTitle>
-
+      <>
         <TabSelector
           options={tabOptions}
           currentTab={currentTab}
@@ -67,42 +57,13 @@ export default class ClusterInstructionsModal extends Component<
         />
 
         {this.renderPage()}
-      </StyledClusterInstructionsModal>
+      </>
     );
   }
 }
 
 ClusterInstructionsModal.contextType = Context;
 
-const PageCount = styled.div`
-  margin-right: 9px;
-  user-select: none;
-  letter-spacing: 2px;
-`;
-
-const PageSection = styled.div`
-  position: absolute;
-  bottom: 22px;
-  right: 20px;
-  display: flex;
-  align-items: center;
-  font-size: 13px;
-  color: #ffffff;
-  justify-content: flex-end;
-  user-select: none;
-
-  > i {
-    font-size: 18px;
-    margin-left: 2px;
-    cursor: pointer;
-    border-radius: 20px;
-    padding: 5px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-`;
-
 const Code = styled.div`
   background: #181b21;
   padding: 10px 15px;
@@ -116,13 +77,6 @@ const Code = styled.div`
   font-family: monospace;
 `;
 
-const A = styled.a`
-  color: #ffffff;
-  text-decoration: underline;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-`;
-
 const Placeholder = styled.div`
   color: #aaaabb;
   font-size: 13px;
@@ -135,64 +89,4 @@ const Placeholder = styled.div`
 const Bold = styled.div`
   font-weight: 600;
   margin-bottom: 7px;
-`;
-
-const Subtitle = styled.div`
-  padding: 10px 0px 20px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  margin-top: 3px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledClusterInstructionsModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 32px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 7 - 63
dashboard/src/main/home/modals/IntegrationsModal.tsx

@@ -46,7 +46,9 @@ export default class IntegrationsModal extends Component<PropsType, StateType> {
           integrationList[integration.service].icon;
         let disabled =
           integration.service === "kube" || integration.service === "dockerhub";
-        return (
+
+        if (!disabled) {
+          return (
           <IntegrationOption
             key={i}
             disabled={disabled}
@@ -61,28 +63,19 @@ export default class IntegrationsModal extends Component<PropsType, StateType> {
             <Label>{integrationList[integration.service].label}</Label>
           </IntegrationOption>
         );
+        }
       });
     }
   };
 
   render() {
     return (
-      <StyledIntegrationsModal>
-        <CloseButton
-          onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}
-        >
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Add a New Integration</ModalTitle>
+      <>
         <Subtitle>Select the service you would like to connect to.</Subtitle>
-
         <IntegrationsCatalog>
           {this.renderIntegrationsCatalog()}
         </IntegrationsCatalog>
-      </StyledIntegrationsModal>
+      </>
     );
   }
 }
@@ -134,53 +127,4 @@ const Subtitle = styled.div`
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledIntegrationsModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 32px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 44 - 3
dashboard/src/main/home/modals/Modal.tsx

@@ -5,6 +5,7 @@ type PropsType = {
   onRequestClose: () => void;
   width?: string;
   height?: string;
+  title?: string;
 };
 
 type StateType = {};
@@ -38,6 +39,14 @@ export default class Modal extends Component<PropsType, StateType> {
     return (
       <Overlay>
         <StyledModal ref={this.wrapperRef} width={width} height={height}>
+          <CloseButton onClick={this.props.onRequestClose}>
+            <i className="material-icons">close</i>
+          </CloseButton>
+          { 
+            this.props.title && (
+              <ModalTitle>{this.props.title}</ModalTitle>
+            )
+          }
           {this.props.children}
         </StyledModal>
       </Overlay>
@@ -45,6 +54,36 @@ export default class Modal extends Component<PropsType, StateType> {
   }
 }
 
+const ModalTitle = styled.div`
+  font-size: 18px;
+  font-weight: 500;
+  margin-bottom: 10px;
+  user-select: none;
+`;
+
+const CloseButton = styled.div`
+  position: absolute;
+  display: block;
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+  border-radius: 50%;
+  right: 15px;
+  top: 12px;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+
+  > i {
+    font-size: 20px;
+    color: #aaaabb;
+  }
+`;
+
 const Overlay = styled.div`
   position: fixed;
   margin: 0;
@@ -67,11 +106,13 @@ const StyledModal = styled.div`
   max-width: 80vw;
   height: ${(props: { width?: string; height?: string }) =>
     props.height ? props.height : "425px"};
-  border-radius: 7px;
-  border: 0;
-  background-color: #202227;
   overflow: visible;
   padding: 25px 32px;
+  font-size: 13px;
+  border-radius: 10px;
+  background: #202227;
+  border: 1px solid #ffffff55;
+  color: #ffffff;
   animation: floatInModal 0.5s 0s;
   @keyframes floatInModal {
     from {

+ 3 - 61
dashboard/src/main/home/modals/NamespaceModal.tsx

@@ -80,16 +80,7 @@ export default class NamespaceModal extends Component<PropsType, StateType> {
 
   render() {
     return (
-      <StyledUpdateProjectModal>
-        <CloseButton
-          onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}
-        >
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Add Namespace</ModalTitle>
+      <>
         <Subtitle>Name</Subtitle>
 
         <InputWrapper>
@@ -113,7 +104,7 @@ export default class NamespaceModal extends Component<PropsType, StateType> {
           onClick={() => this.createNamespace()}
           status={this.state.status}
         />
-      </StyledUpdateProjectModal>
+      </>
     );
   }
 }
@@ -156,53 +147,4 @@ const Subtitle = styled.div`
   white-space: nowrap;
   text-overflow: ellipsis;
   margin-bottom: -10px;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledUpdateProjectModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 16 - 71
dashboard/src/main/home/modals/UpdateClusterModal.tsx

@@ -59,16 +59,19 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
         }
 
         // Handle destroying infra we've provisioned
-        api.destroyInfra(
-          "<token>",
-                { name: currentCluster.name },
-                {
-                  project_id: currentProject.id,
-                  infra_id: currentCluster.infra_id,
-                }
-        ).then(() =>
-          console.log("destroyed provisioned infra:", currentCluster.infra_id)
-        ).catch(console.log);
+        api
+          .destroyInfra(
+            "<token>",
+            { name: currentCluster.name },
+            {
+              project_id: currentProject.id,
+              infra_id: currentCluster.infra_id,
+            }
+          )
+          .then(() =>
+            console.log("destroyed provisioned infra:", currentCluster.infra_id)
+          )
+          .catch(console.log);
 
         this.props.setRefreshClusters(true);
         this.setState({ status: "successful", showDeleteOverlay: false });
@@ -99,16 +102,7 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
 
   render() {
     return (
-      <StyledUpdateProjectModal>
-        <CloseButton
-          onClick={() => {
-            this.context.setCurrentModal(null, null);
-          }}
-        >
-          <CloseButtonImg src={close} />
-        </CloseButton>
-
-        <ModalTitle>Cluster Settings</ModalTitle>
+      <>
         <Subtitle>Cluster name</Subtitle>
 
         <InputWrapper>
@@ -147,7 +141,7 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
           onYes={this.handleDelete}
           onNo={() => this.setState({ showDeleteOverlay: false })}
         />
-      </StyledUpdateProjectModal>
+      </>
     );
   }
 }
@@ -229,53 +223,4 @@ const Subtitle = styled.div`
   white-space: nowrap;
   text-overflow: ellipsis;
   margin-bottom: -10px;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 18px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledUpdateProjectModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-`;
+`;

+ 89 - 89
dashboard/src/main/home/modals/UsageWarningModal.tsx

@@ -1,6 +1,7 @@
 import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
-import close from "assets/close.png";
+
+import Banner from "components/Banner";
 
 import { Context } from "shared/Context";
 import { UsageData } from "shared/types";
@@ -16,7 +17,6 @@ const ReadableNameMap: {
 };
 
 const filterExceeded = (usage: UsageData) => {
-  console.log(usage);
   const current = usage.current;
   const limits = usage.limit;
   return Object.keys(usage.current).reduce((acc, key) => {
@@ -48,7 +48,7 @@ const UpgradeChartModal: React.FC<{}> = () => {
 
   useEffect(() => {
     if (usage) {
-      setFilteredUsage(filterExceeded(usage));
+      setFilteredUsage(usage);
     }
   }, [usage]);
 
@@ -57,36 +57,56 @@ const UpgradeChartModal: React.FC<{}> = () => {
   }
   console.log({ usage, filteredUsage });
   return (
-    <StyledUpgradeChartModal>
-      <CloseButton onClick={() => setCurrentModal(null, null)}>
-        <CloseButtonImg src={close} />
-      </CloseButton>
-      <ModalTitle>Usage warning</ModalTitle>
-      You're current project is currently exceeding its usage limits. Your usage
-      limits are:
-      <DescriptionSection>
-        {filteredUsage !== null &&
-          Object.entries(filteredUsage.limit).map(([key, value]) => {
-            return (
-              <div key={key}>
-                <b>{ReadableNameMap[key]}:</b> {value}
-              </div>
-            );
-          })}
-      </DescriptionSection>
-      Your project is currently using:
-      <DescriptionSection>
-        {filteredUsage !== null &&
-          Object.entries(filteredUsage.current).map(([key, value]) => {
-            return (
-              <div key={key}>
-                <b>{ReadableNameMap[key]}:</b> {value}
-              </div>
-            );
-          })}
-      </DescriptionSection>
-      You have currently <b>7 days</b> to resolve this issue before you loose
-      access to the Porter dashboard.
+    <>
+      <Br />
+        <Banner type="warning">
+          Your project is currently exceeding its resource usage limit.
+        </Banner>
+      <Br />
+      {
+        filteredUsage !== null && (
+          <UsageSection>
+            <UsageBlock isRed={filteredUsage.current["resource_cpu"] > filteredUsage.limit["resource_cpu"]}>
+              <Label isRed={filteredUsage.current["resource_cpu"] > filteredUsage.limit["resource_cpu"]}>
+                CPU Usage
+              </Label>
+              <Stat isRed={filteredUsage.current["resource_cpu"] > filteredUsage.limit["resource_cpu"]}>
+                {filteredUsage.current["resource_cpu"]} / {filteredUsage.limit["resource_cpu"]} vCPU
+              </Stat>
+            </UsageBlock>
+            <UsageBlock isRed={filteredUsage.current["resource_memory"] > filteredUsage.limit["resource_memory"]}>
+              <Label isRed={filteredUsage.current["resource_memory"] > filteredUsage.limit["resource_memory"]}>
+                Memory Usage
+              </Label>
+              <Stat isRed={filteredUsage.current["resource_memory"] > filteredUsage.limit["resource_memory"]}>
+                {filteredUsage.current["resource_memory"]/1000} / {filteredUsage.limit["resource_memory"]/1000} GB
+              </Stat>
+            </UsageBlock>
+            <UsageBlock isRed={filteredUsage.current["users"] > filteredUsage.limit["users"]}>
+              <Label isRed={filteredUsage.current["users"] > filteredUsage.limit["users"]}>
+                Users
+              </Label>
+              <Stat isRed={filteredUsage.current["users"] > filteredUsage.limit["users"]}>
+                {filteredUsage.current["users"]} / {filteredUsage.limit["users"]} seats
+              </Stat>
+            </UsageBlock>
+            <UsageBlock isRed={filteredUsage.current["clusters"] > filteredUsage.limit["clusters"]}>
+              <Label isRed={filteredUsage.current["clusters"] > filteredUsage.limit["clusters"]}>
+                Clusters
+              </Label> 
+              <Stat isRed={filteredUsage.current["clusters"] > filteredUsage.limit["clusters"]}>
+                {filteredUsage.current["clusters"]} / {filteredUsage.limit["clusters"]} clusters
+              </Stat>
+            </UsageBlock>
+          </UsageSection>
+        )
+      }
+      <Helper>
+        You have <b>7 days</b> to resolve this issue before your access to the dashboard is restricted.
+      </Helper>
+      <Helper>
+        Have a question about billing? Email us at <a target="_blank" href="mailto:contact@porter.run">contact@porter.run</a>.
+      </Helper>
       <Button
         as={Link}
         to={{
@@ -97,12 +117,40 @@ const UpgradeChartModal: React.FC<{}> = () => {
       >
         Take me to billing
       </Button>
-    </StyledUpgradeChartModal>
+    </>
   );
 };
 
 export default UpgradeChartModal;
 
+const UsageBlock = styled.div<{ isRed?: boolean }>`
+  border: 1px solid ${props => props.isRed ? "#ff385d" : "#ffffff55"};
+  border-radius: 5px;
+  padding: 18px;
+`;
+
+const Helper = styled.div`
+  color: #aaaabb;
+  margin-top: 12px;
+`;
+
+const Label = styled.div<{ isRed?: boolean }>`
+  margin-bottom: 10px;
+  font-weight: 500;
+  color: ${props => props.isRed ? "#ff385d" : "#ffffff55"};
+`;
+
+const Stat = styled.div<{ isRed?: boolean }>`
+  font-size: 20px;
+  margin-bottom: 25px;
+  color: ${props => props.isRed ? "#ff385d" : "#ffffff55"};
+`;
+
+const Br = styled.div`
+  width: 100%;
+  height: 5px;
+`;
+
 const Button = styled.button`
   height: 35px;
   background: #616feecc;
@@ -127,59 +175,11 @@ const Button = styled.button`
   bottom: 20px;
 `;
 
-const DescriptionSection = styled.div`
+const UsageSection = styled.div`
   margin-top: 10px;
-  margin-bottom: 10px;
-`;
-
-const ModalTitle = styled.div`
-  margin: 0px 0px 13px;
-  display: flex;
-  flex: 1;
-  font-family: Work Sans, sans-serif;
-  font-size: 24px;
-  color: #ffffff;
-  user-select: none;
-  font-weight: 700;
-  align-items: center;
-  position: relative;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  z-index: 1;
-  text-align: center;
-  border-radius: 50%;
-  right: 15px;
-  top: 12px;
-  cursor: pointer;
-  :hover {
-    background-color: #ffffff11;
-  }
-`;
-
-const CloseButtonImg = styled.img`
-  width: 14px;
-  margin: 0 auto;
-`;
-
-const StyledUpgradeChartModal = styled.div`
-  width: 100%;
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  padding: 25px 30px;
-  overflow: hidden;
-  border-radius: 6px;
-  background: #202227;
-  font-size: 13px;
-  line-height: 1.8em;
-  font-family: Work Sans, sans-serif;
-`;
+  margin-bottom: 35px;
+  display: grid;
+  grid-column-gap: 25px;
+  grid-row-gap: 25px;
+  grid-template-columns: repeat(2, minmax(200px, 1fr));
+`;

+ 23 - 3
dashboard/src/main/home/project-settings/BillingPage.tsx

@@ -31,9 +31,29 @@ function BillingPage() {
         <PlanSelect
           theme={{
             base: {
-              darkMode: "on",
-              primaryColor: "white",
-              fontFamily: "sans-serif",
+              customFont: 'Work Sans',
+              fontFamily: '"Work Sans", sans-serif',
+              darkMode: 'on',
+              colors: {
+                primary: 'rgba(97, 111, 238, 0.8)',
+                secondary: 'rgb(103, 108, 124)',
+                danger: 'rgb(227, 54, 109)',
+                success: 'rgb(56, 168, 138)',
+              },
+            },
+            card: {
+              backgroundColor: 'rgb(38, 40, 47)',
+              boxShadow: 'rgb(0 0 0 / 33%) 0px 4px 15px 0px',
+              borderRadius: '8px',
+              border: '2px solid rgba(158, 180, 255, 0)',
+            },
+            button: {
+              base: {
+                boxShadow: 'rgb(0 0 0 / 19%) 0px 2px 5px 0px',
+                borderRadius: '5px',
+                fontSize: '14px',
+                fontWeight: '500',
+              },
             },
           }}
         ></PlanSelect>

+ 1 - 0
dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

@@ -271,6 +271,7 @@ const Br = styled.div`
 
 const StyledProvisionerSettings = styled.div`
   position: relative;
+  z-index: 0;
 `;
 
 const PositionWrapper = styled.div<{ selectedProvider: string | null }>``;

+ 7 - 10
dashboard/src/shared/api.tsx

@@ -763,15 +763,12 @@ const getMetadata = baseApi<{}, {}>("GET", () => {
   return `/api/metadata`;
 });
 
-const getWelcome = baseApi<
-  {
-    email: string;
-    isCompany: boolean;
-    company: string;
-    role: string;
-  },
-  {}
->("GET", () => {
+const postWelcome = baseApi<{
+  email: string;
+  isCompany: boolean;
+  company: string;
+  role: string;
+}>("POST", () => {
   return `/api/welcome`;
 });
 
@@ -1096,7 +1093,7 @@ export default {
   getBranchContents,
   getBranches,
   getMetadata,
-  getWelcome,
+  postWelcome,
   getChart,
   getCharts,
   getChartComponents,

+ 2 - 4
dashboard/src/shared/baseApi.tsx

@@ -19,11 +19,9 @@ export const baseApi = <T extends {}, S = {}>(
 
     // Handle request type (can refactor)
     if (requestType === "POST") {
-      return axios.post(endpointString, params, {
-      });
+      return axios.post(endpointString, params, {});
     } else if (requestType === "PUT") {
-      return axios.put(endpointString, params, {
-      });
+      return axios.put(endpointString, params, {});
     } else if (requestType === "DELETE") {
       return axios.delete(
         endpointString + "?" + qs.stringify(params, { arrayFormat: "repeat" })

+ 1 - 1
docs/guides/using-env-groups.md

@@ -20,7 +20,7 @@ You will be redirected to the list of environment groups, and your new environme
 
 ![Load env group](https://files.readme.io/c909d6a-env-groups-4.png "env-groups-4.png")
 
-You can then select your environment group and click "Load Selected Env Group", which will automatically populate the environment group variables that you previously set. You can modify these environment variables in this tab, for example if you'd like to add environment variables that aren't currently in the environment group. To view all deployment options, head over to our [application deployment docs](https://docs.getporter.dev/docs/add-ons). 
+You can then select your environment group and click "Load Selected Env Group", which will automatically populate the environment group variables that you previously set. You can modify these environment variables in this tab, for example if you'd like to add environment variables that aren't currently in the environment group. To view all deployment options, head over to our [application deployment docs](https://docs.porter.run/docs/addons). 
 
 # 🔒 Creating secret environment variables