Преглед изворни кода

Merge branch 'staging' of https://github.com/porter-dev/porter into staging

sunguroku пре 5 година
родитељ
комит
aacc02187e

+ 31 - 10
README.md

@@ -1,23 +1,44 @@
 # Porter
 # Porter
 
 
-### Development
+Porter is a **dashboard for Helm** with support for the following features:
+- Visualization of all Helm releases with filtering by namespace
+- In-depth view of releases, including revision histories and component graphs
+- Rollback/update of existing releases, including editing of `values.yaml`
+
+**What's next for Porter?** View our [roadmap](https://github.com/porter-dev/porter/projects/1), or read our [mission statement](#mission-statement). 
+
+## Getting Started
+
+### Local Setup
+
+To view the dashboard locally, download our CLI and grab the latest release via:
+
+```sh
+TBD
+```
+
+Then run the dashboard (Docker engine must be running on the host machine):
 
 
 ```sh
 ```sh
-docker-compose -f docker-compose.dev.yaml up --build
+porter start
 ```
 ```
 
 
-And then visit `localhost:8080` in the browser. 
+### In-Cluster Setup
 
 
-### Testing
+TBD
 
 
-From the root directory, run `go test ./...` to run all tests and ensure the builds/tests pass. 
+## Alternative Tools
 
 
-### Building
+## Mission Statement
 
 
-From the root directory, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter`. Then you can run `docker run -p 8080:8080 porter`. 
+**`kubectl` for your fundamental operations. Porter for everything else.**
 
 
-To build the test container, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter --target porter-test`. 
+Our mission is to be the go-to tool for interacting with complex Kubernetes deployments as both a beginner and an expert. While our initial focus is on visualizing Helm components, we believe this visualization and editing can be extended to a number of other tools and concepts, including alternative templating tools (kustomize, Terraform), other deployment tools (CI/CD tools, Terraform), Kubernetes package repositories (ChartMuseum, JFrog Artifactory), and even popular Kubernetes packages (nginx-ingress, cert-manager, prometheus, velero). 
 
 
-### Running
+More specifically, we have the following long-term goals:
+- **Design a visual interface for complex deployments and operations**
+- **Make deployments and operations editable by and accessible for non-Kubernetes experts**
+- **Improve the development experience for packaging and releasing Kubernetes applications**
+- **Increase interoperability of Kubernetes tooling without compromising usability**
 
 
-`docker run -p 8080:8080 porter1/porter:latest`
+Why did we begin with Helm? Helm is the most popular auxiliary Kubernetes tool, and can function in nearly all parts of deployment lifecycle. We think of the various features of Helm in the following manner, adapted from [Brian Grant's Helm Summit talk](https://www.youtube.com/watch?v=F-TlC8nIz8s) (slides [here](https://docs.google.com/presentation/d/10dp4hKciccincnH6pAFf7t31s82iNvtt_mwhlUbeCDw/edit#slide=id.g32690131a8_0_5)): package management, dependency management, application metadata, parameterization, templating, deployment/config revision management, lifecycle management hooks, and application probes. Along with these fundamental features, an expanding number of [command plugins](https://helm.sh/docs/community/related/#helm-plugins) for more specific use-cases have started to become popular in the Helm ecosystem. If we can build a better workflow for both application developers and application operators by improving the user experience for most of these Helm features, we can generalize and expand this workflow to support alternative tooling that exists in the [Kubernetes application management ecosystem](https://docs.google.com/spreadsheets/d/1FCgqz1Ci7_VCz_wdh8vBitZ3giBtac_H8SBw4uxnrsE/edit#gid=0). 


+ 28 - 19
dashboard/src/main/home/dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -44,7 +44,7 @@ type StateType = {
   currentEdge: EdgeType | null,
   currentEdge: EdgeType | null,
   openedNode: NodeType | null,
   openedNode: NodeType | null,
   suppressCloseNode: boolean, // Still click should close opened unless on a node
   suppressCloseNode: boolean, // Still click should close opened unless on a node
-  suppressDisplayClicks: boolean, // Ignore clicks on InfoPanel or ButtonSection
+  suppressDisplay: boolean, // Ignore clicks + pan/zoom on InfoPanel or ButtonSection
 };
 };
 
 
 // TODO: region-based unselect, shift-click, multi-region
 // TODO: region-based unselect, shift-click, multi-region
@@ -75,7 +75,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     currentEdge: null as (EdgeType | null),
     currentEdge: null as (EdgeType | null),
     openedNode: null as (NodeType | null),
     openedNode: null as (NodeType | null),
     suppressCloseNode: false,
     suppressCloseNode: false,
-    suppressDisplayClicks: false
+    suppressDisplay: false
   }
   }
 
 
   spaceRef: any = React.createRef();
   spaceRef: any = React.createRef();
@@ -172,7 +172,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     graph.currentEdge = null;
     graph.currentEdge = null;
     graph.isExpanded = false;
     graph.isExpanded = false;
     graph.openedNode = null;
     graph.openedNode = null;
-    graph.suppressDisplayClicks = false;
+    graph.suppressDisplay = false;
     graph.suppressCloseNode = false;
     graph.suppressCloseNode = false;
 
 
     localStorage.setItem(`charts.${this.props.currentChartName}`, JSON.stringify(graph))
     localStorage.setItem(`charts.${this.props.currentChartName}`, JSON.stringify(graph))
@@ -312,17 +312,21 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
   // Handle pan XOR zoom (two-finger gestures count as onWheel)
   // Handle pan XOR zoom (two-finger gestures count as onWheel)
   handleWheel = (e: any) => {
   handleWheel = (e: any) => {
 
 
-    // Pinch/zoom sets e.ctrlKey to true
-    if (e.ctrlKey) {
-  
-      // Clip deltaY for extreme mousewheel values
-      let deltaY = e.deltaY >= 0 ? Math.min(40, e.deltaY) : Math.max(-40, e.deltaY);
+    // Prevent nav gestures if mouse is over InfoPanel or ButtonSection
+    if (!this.state.suppressDisplay) {
 
 
-      let scale = 1;
-      scale -= deltaY * zoomConstant;
-      this.setState({ scale, panX: 0, panY: 0 });
-    } else {
-      this.setState({ panX: e.deltaX, panY: e.deltaY, scale: 1 });
+      // Pinch/zoom sets e.ctrlKey to true
+      if (e.ctrlKey) {
+  
+        // Clip deltaY for extreme mousewheel values
+        let deltaY = e.deltaY >= 0 ? Math.min(40, e.deltaY) : Math.max(-40, e.deltaY);
+
+        let scale = 1;
+        scale -= deltaY * zoomConstant;
+        this.setState({ scale, panX: 0, panY: 0 });
+      } else {
+        this.setState({ panX: e.deltaX, panY: e.deltaY, scale: 1 });
+      }
     }
     }
   };
   };
 
 
@@ -432,8 +436,8 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
         isExpanded={this.state.isExpanded}
         isExpanded={this.state.isExpanded}
         ref={element => this.spaceRef = element}
         ref={element => this.spaceRef = element}
         onMouseMove={this.handleMouseMove}
         onMouseMove={this.handleMouseMove}
-        onMouseDown={this.state.suppressDisplayClicks ? null : this.handleMouseDown}
-        onMouseUp={this.state.suppressDisplayClicks ? null : this.handleMouseUp}
+        onMouseDown={this.state.suppressDisplay ? null : this.handleMouseDown}
+        onMouseUp={this.state.suppressDisplay ? null : this.handleMouseUp}
         onWheel={this.handleWheel}
         onWheel={this.handleWheel}
       >
       >
         {this.renderNodes()}
         {this.renderNodes()}
@@ -441,8 +445,8 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
         {this.renderSelectRegion()}
         {this.renderSelectRegion()}
 
 
         <ButtonSection
         <ButtonSection
-          onMouseEnter={() => this.setState({ suppressDisplayClicks: true })}
-          onMouseLeave={() => this.setState({ suppressDisplayClicks: false })}
+          onMouseEnter={() => this.setState({ suppressDisplay: true })}
+          onMouseLeave={() => this.setState({ suppressDisplay: false })}
         >
         >
           <ToggleLabel
           <ToggleLabel
             onClick={() => this.setState({ showKindLabels: !this.state.showKindLabels })}
             onClick={() => this.setState({ showKindLabels: !this.state.showKindLabels })}
@@ -461,11 +465,16 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
           </ExpandButton>
           </ExpandButton>
         </ButtonSection>
         </ButtonSection>
         <InfoPanel
         <InfoPanel
-          setSuppressDisplayClicks={(x: boolean) => this.setState({ suppressDisplayClicks: x })}
+          setSuppressDisplay={(x: boolean) => this.setState({ suppressDisplay: x })}
           currentNode={this.state.currentNode}
           currentNode={this.state.currentNode}
           currentEdge={this.state.currentEdge}
           currentEdge={this.state.currentEdge}
           openedNode={this.state.openedNode}
           openedNode={this.state.openedNode}
-          closeNode={() => this.setState({ openedNode: null })}
+
+          // InfoPanel won't trigger onMouseLeave for unsuppressing if close is clicked
+          closeNode={() => this.setState({ openedNode: null, suppressDisplay: false })}
+
+          // For YAML wrapper to trigger resize
+          isExpanded={this.state.isExpanded}
         />
         />
       </StyledGraphDisplay>
       </StyledGraphDisplay>
     );
     );

+ 30 - 9
dashboard/src/main/home/dashboard/expanded-chart/graph/InfoPanel.tsx

@@ -11,15 +11,18 @@ type PropsType = {
   currentNode: NodeType,
   currentNode: NodeType,
   currentEdge: EdgeType,
   currentEdge: EdgeType,
   openedNode: NodeType,
   openedNode: NodeType,
-  setSuppressDisplayClicks: (x: boolean) => void,
-  closeNode: () => void
+  setSuppressDisplay: (x: boolean) => void,
+  closeNode: () => void,
+  isExpanded: boolean
 };
 };
 
 
 type StateType = {
 type StateType = {
+  wrapperHeight: number
 };
 };
 
 
 export default class InfoPanel extends Component<PropsType, StateType> {
 export default class InfoPanel extends Component<PropsType, StateType> {
   state = {
   state = {
+    wrapperHeight: 0
   }
   }
 
 
   renderIcon = (kind: string) => {
   renderIcon = (kind: string) => {
@@ -40,6 +43,20 @@ export default class InfoPanel extends Component<PropsType, StateType> {
     return <ColorBlock color={edgeColors[type]} />;
     return <ColorBlock color={edgeColors[type]} />;
   }
   }
 
 
+  wrapperRef: any = React.createRef();
+
+  componentDidMount() {
+    this.setState({ wrapperHeight: this.wrapperRef.offsetHeight });
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if ((prevProps.openedNode !== this.props.openedNode 
+      || prevProps.isExpanded !== this.props.isExpanded) && this.wrapperRef
+    ) {
+      this.setState({ wrapperHeight: this.wrapperRef.offsetHeight });
+    }
+  }
+
   renderContents = () => {
   renderContents = () => {
     let { currentNode, currentEdge, openedNode } = this.props;
     let { currentNode, currentEdge, openedNode } = this.props;
     if (openedNode) {
     if (openedNode) {
@@ -52,11 +69,11 @@ export default class InfoPanel extends Component<PropsType, StateType> {
               {openedNode.name}
               {openedNode.name}
             </ResourceName>
             </ResourceName>
           </Div>
           </Div>
-          <YamlWrapper>
+          <YamlWrapper ref={element => this.wrapperRef = element}>
             <YamlEditor
             <YamlEditor
               value={yaml.dump(openedNode.RawYAML)}
               value={yaml.dump(openedNode.RawYAML)}
               readOnly={true}
               readOnly={true}
-              height='100vw'
+              height={this.state.wrapperHeight + 'px'}
             />
             />
           </YamlWrapper>
           </YamlWrapper>
         </Wrapped>
         </Wrapped>
@@ -103,15 +120,18 @@ export default class InfoPanel extends Component<PropsType, StateType> {
   }
   }
 
 
   render() {
   render() {
+    let { openedNode, closeNode, setSuppressDisplay } = this.props;
+
+    // Only suppress display gestures (click, pan, and zoom) if expanded
     return (
     return (
       <StyledInfoPanel
       <StyledInfoPanel
-        expanded={Boolean(this.props.openedNode)}
-        onMouseEnter={() => this.props.setSuppressDisplayClicks(true)}
-        onMouseLeave={() => this.props.setSuppressDisplayClicks(false)}
+        expanded={Boolean(openedNode)}
+        onMouseEnter={openedNode ? () => setSuppressDisplay(true) : null}
+        onMouseLeave={openedNode ? () => setSuppressDisplay(false) : null}
       >
       >
         {this.renderContents()}
         {this.renderContents()}
 
 
-        {this.props.openedNode ? <i onClick={this.props.closeNode} className="material-icons">close</i> : null}
+        {openedNode ? <i onClick={closeNode} className="material-icons">close</i> : null}
       </StyledInfoPanel>
       </StyledInfoPanel>
     );
     );
   }
   }
@@ -126,9 +146,10 @@ const YamlWrapper = styled.div`
   width: 100%;
   width: 100%;
   margin-top: 7px;
   margin-top: 7px;
   height: calc(100% - 44px);
   height: calc(100% - 44px);
-  overflow: hidden;
   border-radius: 5px;
   border-radius: 5px;
   border: 1px solid #ffffff22;
   border: 1px solid #ffffff22;
+  overflow: hidden;
+  background: #000000;
 `;
 `;
 
 
 const ColorBlock = styled.div`
 const ColorBlock = styled.div`

+ 17 - 0
docs/DEVELOPING.md

@@ -0,0 +1,17 @@
+### Development
+
+```sh
+docker-compose -f docker-compose.dev.yaml up --build
+```
+
+And then visit `localhost:8080` in the browser. 
+
+### Testing
+
+From the root directory, run `go test ./...` to run all tests and ensure the builds/tests pass. 
+
+### Building
+
+From the root directory, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter`. Then you can run `docker run -p 8080:8080 porter`. 
+
+To build the test container, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter-test --target porter-test`.