Răsfoiți Sursa

Merge branch 'beta.3.integration-frontend' into main

sunguroku 5 ani în urmă
părinte
comite
a0685ca42e

+ 0 - 1
dashboard/src/components/values-form/InputRow.tsx

@@ -77,7 +77,6 @@ const Input = styled.input`
   width: ${(props: { disabled: boolean, width: string }) => props.width ? props.width : '270px'};
   color: ${(props: { disabled: boolean, width: string }) => props.disabled ? '#ffffff44' : 'white'};
   padding: 5px 10px;
-  margin-right: 8px;
   height: 30px;
 `;
 

+ 6 - 2
dashboard/src/main/Login.tsx

@@ -58,8 +58,12 @@ export default class Login extends Component<PropsType, StateType> {
         if (err) {
           this.context.setCurrentError(err.response.data.errors[0])
         }
-        setUser(res?.data?.id, res?.data?.email)
-        err ? console.log(err.response.data) : authenticate();
+        if (res?.data?.redirect) {
+          window.location.href = res.data.redirect;
+        } else {
+          setUser(res?.data?.id, res?.data?.email)
+          err ? console.log(err.response.data) : authenticate();
+        }
       });
     }
   }

+ 6 - 2
dashboard/src/main/Register.tsx

@@ -60,8 +60,12 @@ export default class Register extends Component<PropsType, StateType> {
         email: email,
         password: password
       }, {}, (err: any, res: any) => {
-        setUser(res?.data?.id, res?.data?.email)
-        err ? setCurrentError(err.response.data.errors[0]) : authenticate();
+        if (res?.data?.redirect) {
+          window.location.href = res.data.redirect;
+        } else {
+          setUser(res?.data?.id, res?.data?.email)
+          err ? setCurrentError(err.response.data.errors[0]) : authenticate();
+        }
       });
     } 
   };

+ 8 - 9
dashboard/src/main/home/Home.tsx

@@ -270,12 +270,6 @@ export default class Home extends Component<PropsType, StateType> {
         );
       } else if (currentView === 'integrations') {
         return <Integrations />;
-      } else if (currentView === 'new-project') {
-        return (
-          <NewProject 
-            setCurrentView={(x: string, data: any ) => this.setState({ currentView: x })} 
-          />
-        );
       } else if (currentView === 'provisioner') {
         return (
           <ProvisionerStatus
@@ -293,8 +287,12 @@ export default class Home extends Component<PropsType, StateType> {
           setCurrentView={(x: string) => this.setState({ currentView: x })}
         />
       );
-    } else {
-
+    } else if (currentView === 'new-project') {
+      return (
+        <NewProject 
+          setCurrentView={(x: string, data: any ) => this.setState({ currentView: x })} 
+        />
+      );
     }
   }
 
@@ -337,7 +335,8 @@ export default class Home extends Component<PropsType, StateType> {
         if (res.data.length > 0) {
           this.context.setCurrentProject(res.data[0]);
         } else {
-          this.context.currentModalData.setCurrentView('new-project');
+          this.context.setCurrentProject(null);
+          this.setState({ currentView: 'new-project' });
         }
         this.context.setCurrentModal(null, null);
       }

+ 12 - 12
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -2,12 +2,14 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import { InviteType } from '../../../shared/types';
-import Loading from '../../../components/Loading';
 import api from '../../../shared/api';
-import InputRow from '../../../components/values-form/InputRow';
-
 import { Context } from '../../../shared/Context';
 
+import Loading from '../../../components/Loading';
+import InputRow from '../../../components/values-form/InputRow';
+import Helper from '../../../components/values-form/Helper';
+import Heading from '../../../components/values-form/Heading';
+
 type PropsType = {
 }
 
@@ -225,20 +227,20 @@ export default class InviteList extends Component<PropsType, StateType> {
   render() {
     return (
       <>
-        <Subtitle>Manage Access</Subtitle>
+        <Heading isAtTop={true}>Share Project</Heading>
+        <Helper>Generate a project invite for another admin user:</Helper>
         <CreateInvite>
           <InputRow
-            label='Invite Collaborators'
             value={this.state.email}
             type='text'
             setValue={(x: string) => this.setState({ email: x })}
-            width='324px'
-            placeholder='ex. mrp@getporter.dev'
+            width='calc(100%)'
+            placeholder='ex: mrp@getporter.dev'
           />
           <InviteButton
             onClick={() => this.validateEmail()}
           >
-            Invite!
+            Create Invite
           </InviteButton>
         </CreateInvite>
         {this.state.invalidEmail &&
@@ -315,11 +317,9 @@ const Rower = styled.div`
 `;
 
 const CreateInvite = styled.div`
-  display: flex;
   flex-direction: row;
-  align-items: flex-end;
-  margin-top: -20px;
-  margin-bottom: 14px;
+  align-items: center;
+  margin-top: -10px;
 `;
 
 const ShareLink = styled.input`

+ 30 - 23
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import InviteList from './InviteList';
+import TabRegion from '../../../components/TabRegion';
 
 import { Context } from '../../../shared/Context';
 
@@ -11,11 +12,18 @@ type PropsType = {
 
 type StateType = {
   projectName: string,
+  currentTab: string,
 }
 
+const tabOptions = [
+  { value: 'manage-access', label: 'Manage Access' },
+  { value: 'additional-settings', label: 'Additional Settings' }
+];
+
 export default class ProjectSettings extends Component<PropsType, StateType> {
   state = {
     projectName: '',
+    currentTab: 'manage-access',
   }
 
   componentDidMount() {
@@ -28,18 +36,16 @@ export default class ProjectSettings extends Component<PropsType, StateType> {
     if (currentProject) {
       return (
         <>
-          <TitleSection>
-            <Title>Project Settings</Title>
-          </TitleSection>
-          <LineBreak />
+
         </>
       );
     }
   }
 
-  renderDelete = () => {
-    let { currentProject } = this.context;
-    if (currentProject) {
+  renderTabContents = () => {
+    if (this.state.currentTab === 'manage-access') {
+      return <InviteList />;
+    } else {
       return (
         <>
           <Subtitle>Other Settings</Subtitle>
@@ -48,10 +54,12 @@ export default class ProjectSettings extends Component<PropsType, StateType> {
               Delete this project: 
             </BodyText>
             <DeleteButton
-              onClick={() => this.context.setCurrentModal('UpdateProjectModal', { 
-                currentProject: currentProject,
-                setCurrentView: this.props.setCurrentView,
-              })}
+              onClick={() => {
+                this.context.setCurrentModal('UpdateProjectModal', {
+                  currentProject: this.context.currentProject,
+                  setCurrentView: this.props.setCurrentView,
+                });
+              }}
             >
               Delete
             </DeleteButton>
@@ -61,20 +69,19 @@ export default class ProjectSettings extends Component<PropsType, StateType> {
     }
   }
 
-  renderContents = () => {
-    return (
-      <ContentHolder>
-          <InviteList />
-          {this.renderDelete()}
-      </ContentHolder>
-    )
-  }
-
   render () {
     return (
       <StyledProjectSettings>
-        {this.renderTitle()}
-        {this.renderContents()}
+        <TitleSection>
+          <Title>Project Settings</Title>
+        </TitleSection>
+        <TabRegion
+          currentTab={this.state.currentTab}
+          setCurrentTab={(x: string) => this.setState({ currentTab: x })}
+          options={tabOptions}
+        >
+          {this.renderTabContents()}
+        </TabRegion>
       </StyledProjectSettings>
     );
   }
@@ -93,7 +100,7 @@ const Title = styled.div`
 `;
 
 const TitleSection = styled.div`
-  margin-bottom: 20px;
+  margin-bottom: 13px;
   display: flex;
   flex-direction: row;
   align-items: center;

+ 1 - 4
dashboard/src/main/home/provisioner/ProvisionerStatus.tsx

@@ -137,18 +137,15 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
       }
 
       if (err) {
-        console.log(err)
         let e = ansiparse(err).map((el: any) => {
           return el.text;
         })
 
-        console.log(e)
-
         let index = this.state.infras.findIndex(el => el.kind === infra.kind)
         infra.status = "error"
         let infras = this.state.infras
         infras[index] = infra
-        this.setState({ logs: e, error: true, infras });
+        this.setState({ logs: [...this.state.logs, ...e], error: true, infras });
         return;
       }
 

+ 23 - 7
dashboard/src/shared/common.tsx

@@ -80,17 +80,17 @@ export const getIgnoreCase = (object: any, key: string) => {
   ];
 }
 
+const infraSets = [
+  ['ecr', 'eks'],
+  ['gcr', 'gke'],
+  ['docr', 'doks']
+];
+
 export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
   if (infras.length === 0) {
     return false;
   }
 
-  let infraSets = [
-    ['ecr', 'eks'],
-    ['gcr', 'gke'],
-    ['docr', 'doks']
-  ];
-
   let completed = [] as string[];
   infras.forEach((infra: InfraType, i: number) => {
     if (infra.status === 'created') {
@@ -115,7 +115,18 @@ export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
 
 export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
   let newestInstances = {} as any;
+  let newestId = -1;
+  let whitelistedInfras = [] as string[];
   infras.forEach((infra: InfraType, i: number) => {
+
+    // Determine the most recent set for which provisioning was attempted
+    if (infra.id > newestId) {
+      newestId = infra.id;
+      infraSets.forEach((infraSet: string[]) => {
+        infraSet.includes(infra.kind) ? whitelistedInfras = infraSet : null;
+      });
+    }
+
     if (!newestInstances[infra.kind]) {
       newestInstances[infra.kind] = infra;
     } else {
@@ -125,5 +136,10 @@ export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
       }
     }
   });
-  return Object.values(newestInstances);
+
+  let newestInfras = Object.values(newestInstances) as InfraType[];
+  let result = newestInfras.filter((x: InfraType) => {
+    return whitelistedInfras.includes(x.kind)
+  });
+  return result;
 }

+ 23 - 0
helm/.helmignore

@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/

+ 23 - 0
helm/Chart.yaml

@@ -0,0 +1,23 @@
+apiVersion: v2
+name: porter-prod
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 1.16.0

+ 21 - 0
helm/templates/NOTES.txt

@@ -0,0 +1,21 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "porter-prod.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "porter-prod.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "porter-prod.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "porter-prod.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
+{{- end }}

+ 63 - 0
helm/templates/_helpers.tpl

@@ -0,0 +1,63 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "porter-prod.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "porter-prod.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "porter-prod.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "porter-prod.labels" -}}
+helm.sh/chart: {{ include "porter-prod.chart" . }}
+{{ include "porter-prod.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "porter-prod.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "porter-prod.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "porter-prod.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "porter-prod.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}

+ 60 - 0
helm/templates/deployment.yaml

@@ -0,0 +1,60 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+{{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "porter-prod.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+    {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+      labels:
+        {{- include "porter-prod.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      volumes:
+        - name: wss-ssl-certificate
+          secret:
+            secretName: ingress-dashboard
+      serviceAccountName: {{ include "porter-prod.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+            - name: wss-ssl-certificate
+              mountPath: /etc/wss
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}

+ 28 - 0
helm/templates/hpa.yaml

@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "porter-prod.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+  {{- end }}
+  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+  {{- end }}
+{{- end }}

+ 43 - 0
helm/templates/ingress.yaml

@@ -0,0 +1,43 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "porter-prod.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  annotations:
+    kubernetes.io/ingress.global-static-ip-name: porter-hosted
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  backend:
+    serviceName: {{ $fullName }}
+    servicePort: {{ $svcPort }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ . }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}

+ 15 - 0
helm/templates/service.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "porter-prod.selectorLabels" . | nindent 4 }}

+ 12 - 0
helm/templates/serviceaccount.yaml

@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "porter-prod.serviceAccountName" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}

+ 15 - 0
helm/templates/tests/test-connection.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "porter-prod.fullname" . }}-test-connection"
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test-success
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "porter-prod.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never

+ 79 - 0
helm/values.yaml

@@ -0,0 +1,79 @@
+# Default values for porter-prod.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: porteralpha/porter-prod
+  pullPolicy: Always
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: latest
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: false
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: NodePort
+  port: 443
+
+ingress:
+  enabled: true
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: dashboard.getporter.dev
+      paths: ['/*']
+  tls:
+    - secretName: ingress-dashboard
+      hosts:
+        - dashboard.getporter.dev
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}

+ 26 - 20
server/api/user_handler.go

@@ -45,7 +45,11 @@ func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
 
 	if err == nil {
 		app.Logger.Info().Msgf("New user created: %d", user.ID)
-		redirect := session.Values["redirect"]
+		var redirect string
+
+		if valR := session.Values["redirect"]; valR != nil {
+			redirect = session.Values["redirect"].(string)
+		}
 
 		session.Values["authenticated"] = true
 		session.Values["user_id"] = user.ID
@@ -53,14 +57,9 @@ func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
 		session.Values["redirect"] = ""
 		session.Save(r, w)
 
-		if val, ok := redirect.(string); ok && val != "" {
-			http.Redirect(w, r, val, 302)
-			return
-		}
-
 		w.WriteHeader(http.StatusCreated)
 
-		if err := app.sendUser(w, user.ID, user.Email); err != nil {
+		if err := app.sendUser(w, user.ID, user.Email, redirect); err != nil {
 			app.handleErrorFormDecoding(err, ErrUserDecode, w)
 			return
 		}
@@ -80,7 +79,7 @@ func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
 	email, _ := session.Values["email"].(string)
 	w.WriteHeader(http.StatusOK)
 
-	if err := app.sendUser(w, userID, email); err != nil {
+	if err := app.sendUser(w, userID, email, ""); err != nil {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
 		return
 	}
@@ -122,7 +121,11 @@ func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	redirect := session.Values["redirect"]
+	var redirect string
+
+	if valR := session.Values["redirect"]; valR != nil {
+		redirect = session.Values["redirect"].(string)
+	}
 
 	// Set user as authenticated
 	session.Values["authenticated"] = true
@@ -134,14 +137,9 @@ func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
 		app.Logger.Warn().Err(err)
 	}
 
-	if val, ok := redirect.(string); ok && val != "" {
-		http.Redirect(w, r, val, 302)
-		return
-	}
-
-	w.WriteHeader(http.StatusCreated)
+	w.WriteHeader(http.StatusOK)
 
-	if err := app.sendUser(w, storedUser.ID, storedUser.Email); err != nil {
+	if err := app.sendUser(w, storedUser.ID, storedUser.Email, redirect); err != nil {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
 		return
 	}
@@ -342,11 +340,19 @@ func doesUserExist(repo *repository.Repository, user *models.User) *HTTPError {
 	return nil
 }
 
-func (app *App) sendUser(w http.ResponseWriter, userID uint, email string) error {
-	resUser := &models.UserExternal{
-		ID:    userID,
-		Email: email,
+type SendUserExt struct {
+	ID       uint   `json:"id"`
+	Email    string `json:"email"`
+	Redirect string `json:"redirect,omitempty"`
+}
+
+func (app *App) sendUser(w http.ResponseWriter, userID uint, email, redirect string) error {
+	resUser := &SendUserExt{
+		ID:       userID,
+		Email:    email,
+		Redirect: redirect,
 	}
+
 	if err := json.NewEncoder(w).Encode(resUser); err != nil {
 		return err
 	}