Explorar o código

Merge pull request #816 from porter-dev/master

Stage tailscale add-on changes
jusrhee %!s(int64=5) %!d(string=hai) anos
pai
achega
be85391ab4

+ 16 - 1
cli/cmd/auth.go

@@ -80,7 +80,22 @@ func login() error {
 	user, _ := client.AuthCheck(context.Background())
 
 	if user != nil {
-		color.Yellow("You are already logged in. If you'd like to log out, run \"porter auth logout\".")
+		if config.Token != "" {
+			// set the token if the user calls login with the --token flag
+			config.SetToken(config.Token)
+			color.New(color.FgGreen).Println("Successfully logged in!")
+
+			projID, err := api.GetProjectIDFromToken(config.Token)
+
+			if err != nil {
+				return err
+			}
+
+			config.SetProject(projID)
+		} else {
+			color.Yellow("You are already logged in. If you'd like to log out, run \"porter auth logout\".")
+		}
+
 		return nil
 	}
 

+ 4 - 1
dashboard/src/components/values-form/FormDebugger.tsx

@@ -281,7 +281,10 @@ tabs:
     - type: subtitle
       label: "Note: Hidden required fields aren't supported yet (global only)"
   - name: controlled-by-external
-    show_if: checkbox_a
+    show_if:
+      or:
+        - checkbox_a
+        - not_a_variable
     contents:
     - type: heading
       label: Conditional Display (A)

+ 15 - 2
dashboard/src/components/values-form/FormWrapper.tsx

@@ -65,10 +65,23 @@ export default class FormWrapper extends Component<PropsType, StateType> {
       let tabOptions = [] as { value: string; label: string }[];
       let tabs = this.props.formData?.tabs;
       let requiredFields = [] as string[];
-      let metaState: any = {};
+      let metaState: any = {
+        "currentCluster.service.is_gcp": {
+          value: this.context.currentCluster.service == "gke",
+        },
+        "currentCluster.service.is_aws": {
+          value: this.context.currentCluster.service == "eks",
+        },
+        "currentCluster.service.is_do": {
+          value: this.context.currentCluster.service == "doks",
+        },
+      };
       if (tabs) {
         tabs.forEach((tab: any, i: number) => {
-          if (tab?.name && tab.label) {
+          // Exclude value if omitFromLaunch is set
+          let omit =
+            tab.settings?.omitFromLaunch && this.props.externalValues?.isLaunch;
+          if (tab?.name && tab.label && !omit) {
             // If a tab is valid, extract state
             tab.sections?.forEach((section: Section, i: number) => {
               section?.contents?.forEach((item: FormElement, i: number) => {

+ 27 - 2
dashboard/src/components/values-form/Heading.tsx

@@ -1,9 +1,20 @@
 import React from "react";
 import styled from "styled-components";
 
-export default function Heading(props: { isAtTop?: boolean; children: any }) {
+export default function Heading(props: {
+  isAtTop?: boolean;
+  children: any;
+  docs?: string;
+}) {
   return (
-    <StyledHeading isAtTop={props.isAtTop}>{props.children}</StyledHeading>
+    <StyledHeading isAtTop={props.isAtTop}>
+      {props.children}
+      {props.docs && (
+        <a href={props.docs} target="_blank">
+          <i className="material-icons">help_outline</i>
+        </a>
+      )}
+    </StyledHeading>
   );
 }
 
@@ -15,4 +26,18 @@ const StyledHeading = styled.div<{ isAtTop: boolean }>`
   margin-bottom: 5px;
   display: flex;
   align-items: center;
+
+  > a {
+    > i {
+      display: flex;
+      align-items: center;
+      margin-bottom: -2px;
+      font-size: 16px;
+      margin-left: 12px;
+      color: #858faaaa;
+      :hover {
+        color: #aaaabb;
+      }
+    }
+  }
 `;

+ 114 - 0
dashboard/src/components/values-form/ServiceRow.tsx

@@ -0,0 +1,114 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import { Context } from "shared/Context";
+import { hardcodedNames, hardcodedIcons } from "shared/hardcodedNameDict";
+
+type PropsType = {
+  service: {
+    clusterIP: string;
+    name: string;
+    release: string;
+    app: string;
+    namespace: string;
+    type?: string;
+  };
+};
+
+type StateType = any;
+
+export default class ServiceRow extends Component<PropsType, StateType> {
+  render() {
+    let { clusterIP, name, namespace, type, app, release } = this.props.service;
+    name = name || release;
+    type = type || app;
+    return (
+      <>
+        {name &&
+          type &&
+          hardcodedNames[type] &&
+          hardcodedIcons[type] &&
+          namespace !== "kube-system" && (
+            <StyledServiceRow>
+              <Flex>
+                <Icon src={hardcodedIcons[type]} />
+                <Type>{hardcodedNames[type]}</Type>
+                <Name>{name}</Name> <Dash>-</Dash> <IP>{clusterIP}</IP>
+              </Flex>
+              <TagWrapper>
+                Namespace: <NamespaceTag>{namespace}</NamespaceTag>
+              </TagWrapper>
+            </StyledServiceRow>
+          )}
+      </>
+    );
+  }
+}
+
+ServiceRow.contextType = Context;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const TagWrapper = styled.div`
+  float: right;
+  height: 20px;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #ffffff44;
+  border-right: 0;
+  border-radius: 3px;
+  padding-left: 5px;
+`;
+
+const NamespaceTag = styled.div`
+  height: 20px;
+  margin-left: 6px;
+  color: #aaaabb;
+  border-radius: 3px;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding-left: 3px;
+  border-top-left-radius: 0px;
+  border-bottom-left-radius: 0px;
+`;
+
+const Dash = styled.div`
+  margin-right: 10px;
+`;
+
+const Icon = styled.img`
+  width: 20px;
+  margin-right: 12px;
+`;
+
+const Type = styled.div`
+  color: #aaaabb;
+  margin-right: 15px;
+`;
+
+const Name = styled.div`
+  margin-right: 10px;
+`;
+
+const IP = styled.div`
+  user-select: text;
+  font-weight: 500;
+`;
+
+const StyledServiceRow = styled.div`
+  width: 100%;
+  height: 40px;
+  background: #ffffff11;
+  margin-bottom: 15px;
+  border-radius: 5px;
+  padding: 15px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+`;

+ 70 - 17
dashboard/src/components/values-form/ValuesForm.tsx

@@ -1,7 +1,14 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 
-import { Section, FormElement } from "shared/types";
+import {
+  Section,
+  FormElement,
+  ShowIf,
+  ShowIfOr,
+  ShowIfAnd,
+  ShowIfNot,
+} from "shared/types";
 import { Context } from "shared/Context";
 
 import CheckboxRow from "./CheckboxRow";
@@ -11,6 +18,7 @@ import SelectRow from "./SelectRow";
 import Helper from "./Helper";
 import Heading from "./Heading";
 import ExpandableResource from "../ExpandableResource";
+import ServiceRow from "./ServiceRow";
 import VeleroForm from "../forms/VeleroForm";
 import InputArray from "./InputArray";
 import KeyValueArray from "./KeyValueArray";
@@ -61,22 +69,38 @@ export default class ValuesForm extends Component<PropsType, StateType> {
 
       switch (item.type) {
         case "heading":
-          return <Heading key={i}>{item.label}</Heading>;
+          return (
+            <Heading key={i} docs={item.settings?.docs}>
+              {item.label}
+            </Heading>
+          );
         case "subtitle":
           return <Helper key={i}>{item.label}</Helper>;
+        case "service-ip-list":
+          if (Array.isArray(item.value)) {
+            return (
+              <ResourceList key={key}>
+                {item.value?.map((service: any, i: number) => {
+                  return <ServiceRow service={service} key={i} />;
+                })}
+              </ResourceList>
+            );
+          }
         case "resource-list":
           if (Array.isArray(item.value)) {
             return (
               <ResourceList key={key}>
                 {item.value?.map((resource: any, i: number) => {
-                  return (
-                    <ExpandableResource
-                      key={i}
-                      resource={resource}
-                      isLast={i === item.value.length - 1}
-                      roundAllCorners={true}
-                    />
-                  );
+                  if (resource.data) {
+                    return (
+                      <ExpandableResource
+                        key={i}
+                        resource={resource}
+                        isLast={i === item.value.length - 1}
+                        roundAllCorners={true}
+                      />
+                    );
+                  }
                 })}
               </ResourceList>
             );
@@ -174,6 +198,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
             <InputRow
               key={key}
               width="100%"
+              placeholder={item.placeholder}
               isRequired={item.required}
               type="password"
               value={this.getInputValue(item)}
@@ -305,17 +330,45 @@ export default class ValuesForm extends Component<PropsType, StateType> {
     });
   };
 
+  evalShowIf = (vals: ShowIf): boolean => {
+    if (!vals) {
+      return false;
+    }
+    if (typeof vals == "string") {
+      return !!this.props.metaState[vals]?.value;
+    }
+    if ((vals as ShowIfOr).or) {
+      vals = vals as ShowIfOr;
+      for (let i = 0; i < vals.or.length; i++) {
+        if (this.evalShowIf(vals.or[i])) {
+          return true;
+        }
+      }
+      return false;
+    }
+    if ((vals as ShowIfAnd).and) {
+      vals = vals as ShowIfAnd;
+      for (let i = 0; i < vals.and.length; i++) {
+        if (!this.evalShowIf(vals.and[i])) {
+          return false;
+        }
+      }
+      return true;
+    }
+    if ((vals as ShowIfNot).not) {
+      vals = vals as ShowIfNot;
+      return !this.evalShowIf(vals.not);
+    }
+
+    return false;
+  };
+
   renderFormContents = () => {
     if (this.props.metaState) {
       return this.props.sections?.map((section: Section, i: number) => {
         // Hide collapsible section if deciding field is false
-        if (section.show_if) {
-          if (
-            !this.props.metaState[section.show_if] ||
-            this.props.metaState[section.show_if].value === false
-          ) {
-            return null;
-          }
+        if (section.show_if && !this.evalShowIf(section.show_if)) {
+          return null;
         }
 
         return <div key={i}>{this.renderSection(section)}</div>;

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

@@ -11,7 +11,7 @@ import Loading from "components/Loading";
 import LaunchFlow from "./launch-flow/LaunchFlow";
 import NoClusterPlaceholder from "../NoClusterPlaceholder";
 
-import hardcodedNames from "./hardcodedNameDict";
+import { hardcodedNames } from "shared/hardcodedNameDict";
 import semver from "semver";
 
 const tabOptions = [

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

@@ -9,7 +9,7 @@ import { PorterTemplate } from "shared/types";
 import Helper from "components/values-form/Helper";
 import Selector from "components/Selector";
 
-import hardcodedNames from "../hardcodedNameDict";
+import { hardcodedNames } from "shared/hardcodedNameDict";
 
 type PropsType = {
   currentTemplate: any;

+ 0 - 20
dashboard/src/main/home/launch/hardcodedNameDict.tsx

@@ -1,20 +0,0 @@
-const hardcodedNames: { [key: string]: string } = {
-  docker: "Docker",
-  "https-issuer": "HTTPS Issuer",
-  metabase: "Metabase",
-  mongodb: "MongoDB",
-  mysql: "MySQL",
-  postgresql: "PostgreSQL",
-  redis: "Redis",
-  ubuntu: "Ubuntu",
-  web: "Web Service",
-  worker: "Worker",
-  job: "Job",
-  "cert-manager": "Cert Manager",
-  elasticsearch: "Elasticsearch",
-  prometheus: "Prometheus",
-  rabbitmq: "RabbitMQ",
-  logdna: "LogDNA",
-};
-
-export default hardcodedNames;

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

@@ -8,7 +8,7 @@ import api from "shared/api";
 import { Context } from "shared/Context";
 import { pushFiltered } from "shared/routing";
 
-import hardcodedNames from "../hardcodedNameDict";
+import { hardcodedNames } from "shared/hardcodedNameDict";
 import SourcePage from "./SourcePage";
 import SettingsPage from "./SettingsPage";
 

+ 53 - 0
dashboard/src/shared/hardcodedNameDict.tsx

@@ -0,0 +1,53 @@
+const hardcodedNames: { [key: string]: string } = {
+  docker: "Docker",
+  "https-issuer": "HTTPS Issuer",
+  metabase: "Metabase",
+  mongodb: "MongoDB",
+  mysql: "MySQL",
+  postgresql: "PostgreSQL",
+  redis: "Redis",
+  ubuntu: "Ubuntu",
+  web: "Web Service",
+  worker: "Worker",
+  job: "Job",
+  "cert-manager": "Cert Manager",
+  elasticsearch: "Elasticsearch",
+  prometheus: "Prometheus",
+  rabbitmq: "RabbitMQ",
+  logdna: "LogDNA",
+  "tailscale-relay": "Tailscale",
+};
+
+const hardcodedIcons: { [key: string]: string } = {
+  "https-issuer":
+    "https://cdn4.iconfinder.com/data/icons/macster-2/100/https__-512.png",
+  metabase:
+    "https://pbs.twimg.com/profile_images/961380992727465985/4unoiuHt.jpg",
+  mongodb:
+    "https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png",
+  mysql: "https://www.mysql.com/common/logos/logo-mysql-170x115.png",
+  postgresql:
+    "https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-110x117.png",
+  redis:
+    "https://cdn4.iconfinder.com/data/icons/redis-2/1451/Untitled-2-512.png",
+  ubuntu: "Ubuntu",
+  web:
+    "https://user-images.githubusercontent.com/65516095/111255214-07d3da80-85ed-11eb-99e2-fddcbdb99bdb.png",
+  worker:
+    "https://user-images.githubusercontent.com/65516095/111255250-1b7f4100-85ed-11eb-8bd1-7b17be3e0e06.png",
+  job:
+    "https://user-images.githubusercontent.com/65516095/111258413-4e2c3800-85f3-11eb-8a6a-88e03460f8fe.png",
+  "cert-manager":
+    "https://raw.githubusercontent.com/jetstack/cert-manager/master/logo/logo.png",
+  elasticsearch:
+    "https://ria.gallerycdn.vsassets.io/extensions/ria/elastic/0.13.3/1530754501320/Microsoft.VisualStudio.Services.Icons.Default",
+  prometheus:
+    "https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png",
+  rabbitmq:
+    "https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png",
+  logdna:
+    "https://user-images.githubusercontent.com/65516095/118185526-a2447480-b40a-11eb-9bdb-82aa0a306f26.png",
+  "tailscale-relay": "Tailscale",
+};
+
+export { hardcodedNames, hardcodedIcons };

+ 16 - 1
dashboard/src/shared/types.tsx

@@ -104,9 +104,23 @@ export interface FormYAML {
   }[];
 }
 
+export interface ShowIfAnd {
+  and: ShowIf[];
+}
+
+export interface ShowIfOr {
+  or: ShowIf[];
+}
+
+export interface ShowIfNot {
+  not: ShowIf;
+}
+
+export type ShowIf = string | ShowIfAnd | ShowIfOr | ShowIfNot;
+
 export interface Section {
   name?: string;
-  show_if?: string;
+  show_if?: ShowIf;
   contents: FormElement[];
 }
 
@@ -121,6 +135,7 @@ export interface FormElement {
   placeholder?: string;
   value?: any;
   settings?: {
+    docs?: string;
     default?: number | string | boolean;
     options?: any[];
     omitUnitFromValue?: boolean;

+ 0 - 3
internal/kubernetes/nodes/nodes.go

@@ -2,7 +2,6 @@ package nodes
 
 import (
 	"context"
-	"fmt"
 	"sync"
 
 	v1 "k8s.io/api/core/v1"
@@ -67,8 +66,6 @@ func GetNodesUsage(clientset kubernetes.Interface) []*NodeWithUsageData {
 }
 
 func getPodsForNode(clientset kubernetes.Interface, nodeName string) *v1.PodList {
-	fmt.Printf("%s", nodeName)
-
 	podList, _ := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{
 		FieldSelector: "spec.nodeName=" + nodeName + ",status.phase=Running",
 	})

+ 5 - 1
internal/models/templates.go

@@ -30,13 +30,16 @@ type FormTab struct {
 	Name     string         `yaml:"name" json:"name"`
 	Label    string         `yaml:"label" json:"label"`
 	Sections []*FormSection `yaml:"sections" json:"sections,omitempty"`
+	Settings struct {
+		OmitFromLaunch bool `yaml:"omitFromLaunch,omitempty" json:"omitFromLaunch,omitempty"`
+	} `yaml:"settings,omitempty" json:"settings,omitempty"`
 }
 
 // FormSection is a section of a form
 type FormSection struct {
 	Context  *FormContext   `yaml:"context" json:"context"`
 	Name     string         `yaml:"name" json:"name"`
-	ShowIf   string         `yaml:"show_if" json:"show_if"`
+	ShowIf   interface{}    `yaml:"show_if" json:"show_if"`
 	Contents []*FormContent `yaml:"contents" json:"contents,omitempty"`
 }
 
@@ -51,6 +54,7 @@ type FormContent struct {
 	Placeholder string       `yaml:"placeholder,omitempty" json:"placeholder,omitempty"`
 	Value       interface{}  `yaml:"value,omitempty" json:"value,omitempty"`
 	Settings    struct {
+		Docs               string      `yaml:"docs,omitempty" json:"docs,omitempty"`
 		Default            interface{} `yaml:"default,omitempty" json:"default,omitempty"`
 		Unit               interface{} `yaml:"unit,omitempty" json:"unit,omitempty"`
 		OmitUnitFromValue  bool        `yaml:"omitUnitFromValue,omitempty" json:"omitUnitFromValue,omitempty"`

+ 2 - 2
server/api/k8s_handler.go

@@ -80,7 +80,7 @@ func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
 // HandleCreateNamespace creates a new namespace given the name.
 func (app *App) HandleCreateNamespace(w http.ResponseWriter, r *http.Request) {
 	vals, err := url.ParseQuery(r.URL.RawQuery)
-	fmt.Println(vals)
+
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
 		return
@@ -1064,7 +1064,7 @@ func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Requ
 	selectors := ""
 	if vals["selectors"] != nil {
 		selectors = vals["selectors"][0]
-	} 
+	}
 	err = agent.StreamControllerStatus(conn, kind, selectors)
 
 	if err != nil {