فهرست منبع

basic random node distribution per type and persistent graph view

sunguroku 5 سال پیش
والد
کامیت
178ecaa61e

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

@@ -41,6 +41,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
   componentDidMount() {
     let { currentCluster, setCurrentError } = this.context;
     let { currentChart } = this.props;
+
     api.getChartComponents('<token>', {
       namespace: currentChart.namespace,
       context: currentCluster,
@@ -73,11 +74,11 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
   renderTabContents = () => {
     let { currentChart, refreshChart, setSidebar} = this.props;
-
     if (this.state.currentTab === 'graph') {
       return (
         <GraphSection
           components={this.state.components}
+          currentChartName={currentChart.name}
           setSidebar={setSidebar}
         />
       );

+ 2 - 0
dashboard/src/main/home/dashboard/expanded-chart/GraphSection.tsx

@@ -9,6 +9,7 @@ import Loading from '../../../../components/Loading';
 
 type PropsType = {
   components: ResourceType[],
+  currentChartName: string,
   setSidebar: (x: boolean) => void
 };
 
@@ -28,6 +29,7 @@ export default class GraphSection extends Component<PropsType, StateType> {
           setSidebar={this.props.setSidebar}
           components={this.props.components}
           isExpanded={this.state.isExpanded}
+          currentChartName={this.props.currentChartName}
         />
       );
     }

+ 57 - 7
dashboard/src/main/home/dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -14,7 +14,8 @@ const panConstant = 0.8;
 type PropsType = {
   components: ResourceType[],
   isExpanded: boolean,
-  setSidebar: (x: boolean) => void
+  setSidebar: (x: boolean) => void,
+  currentChartName: string
 };
 
 type StateType = {
@@ -69,6 +70,12 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
 
   spaceRef: any = React.createRef();
 
+  getRandomIntBetweenRange = (min: number, max: number) => {
+    min = Math.ceil(min);
+    max = Math.floor(max);
+    return Math.floor(Math.random() * (max - min) + min);  
+  }
+
   componentDidMount() {
     let { components } = this.props;
 
@@ -83,14 +90,49 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     // Suppress trackpad gestures
     this.spaceRef.addEventListener("touchmove", (e: any) => e.preventDefault());
     this.spaceRef.addEventListener("mousewheel", (e: any) => e.preventDefault());
-    let nodes = components.map((c: ResourceType) => {
-      return { id: c.ID, name: c.Name, kind: c.Kind, x: 0, y: 0, w: 40, h: 40 };
-    });
+
+    let graph = localStorage.getItem(`charts.${this.props.currentChartName}`)
+    let nodes = [] as NodeType[]
+    let edges = [] as EdgeType[]
+
+    if (!graph) {
+      nodes = this.createNodes(components)
+      edges = this.createEdges(components)
+      this.setState({ nodes, edges });
+    } else {
+      let storedState = JSON.parse(localStorage.getItem(`charts.${this.props.currentChartName}`))
+      this.setState(storedState)
+    }
 
     document.addEventListener("keydown", this.handleKeyDown);
     document.addEventListener("keyup", this.handleKeyUp);
+  }
+
+  createNodes = (components: ResourceType[]) => {
+    return components.map((c: ResourceType) => {
+      switch(c.Kind) {
+        case "ClusterRoleBinding":
+        case "ClusterRole":
+        case "RoleBinding":
+        case "Role":
+          return { id: c.ID, name: c.Name, kind: c.Kind, x: this.getRandomIntBetweenRange(-1000, 0), y: this.getRandomIntBetweenRange(0, 500), w: 40, h: 40 };
+        case "Deployment":
+        case "StatefulSet":
+        case "Pod":
+        case "ServiceAccount":
+          return { id: c.ID, name: c.Name, kind: c.Kind, x: this.getRandomIntBetweenRange(0, 1000), y: this.getRandomIntBetweenRange(0, 500), w: 40, h: 40 };
+        case "Service":
+        case "Ingress":
+        case "ServiceAccount":
+            return { id: c.ID, name: c.Name, kind: c.Kind, x: this.getRandomIntBetweenRange(0, 1000), y: this.getRandomIntBetweenRange(-500, 0), w: 40, h: 40 };
+        default:
+          return { id: c.ID, name: c.Name, kind: c.Kind, x: this.getRandomIntBetweenRange(-700, 0), y: this.getRandomIntBetweenRange(-500, 0), w: 40, h: 40 };
+        }
+    });
+  }
 
-    let edges = [] as EdgeType[];
+  createEdges = (components: ResourceType[]) => {
+    let edges = [] as EdgeType[]
     components.map((c: ResourceType) => {
       c.Relations?.ControlRels?.map((rel: any) => {
         if (rel.Source == c.ID) {
@@ -107,12 +149,20 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
           edges.push({ type: "SpecRel", source: rel.Source, target: rel.Target });
         }
       })
-      this.setState({ edges });
     });
-    this.setState({ nodes });
+    return edges
   }
 
   componentWillUnmount() {
+    let graph = this.state;
+    console.log("unmounting...", graph)
+    // flush non-persistent data
+    graph.activeIds = [];
+    graph.currentNode = null;
+    graph.currentEdge = null;
+    graph.isExpanded = false;
+
+    localStorage.setItem(`charts.${this.props.currentChartName}`, JSON.stringify(graph))
     this.spaceRef.removeEventListener("touchmove", (e: any) => e.preventDefault());
     this.spaceRef.removeEventListener("mousewheel", (e: any) => e.preventDefault());
     document.removeEventListener("keydown", this.handleKeyDown);

+ 14 - 1
dashboard/src/main/home/dashboard/expanded-chart/graph/InfoPanel.tsx

@@ -3,6 +3,7 @@ import styled from 'styled-components';
 
 import { kindToIcon, edgeColors } from '../../../../../shared/rosettaStone';
 import { NodeType, EdgeType} from '../../../../../shared/types';
+import Edge from './Edge';
 
 type PropsType = {
   currentNode: NodeType,
@@ -50,7 +51,7 @@ export default class InfoPanel extends Component<PropsType, StateType> {
       return (
         <EdgeInfo>
           {this.renderColorBlock(currentEdge.type)}
-          {currentEdge.type}
+          {this.renderEdgeMessage(currentEdge)}
         </EdgeInfo>
       )
     }
@@ -65,6 +66,18 @@ export default class InfoPanel extends Component<PropsType, StateType> {
     )
   }
 
+  renderEdgeMessage = (edge: EdgeType) => {
+    // TODO: render more information about edges (labels, spec property field)
+    switch(edge.type) {
+      case "ControlRel":
+        return "Controller Relation"
+      case "LabelRel":
+        return "Label Relation"
+      case "SpecRel":
+        return "Spec Relation"
+    }
+  }
+
   render() {
     return (
       <StyledInfoPanel>

+ 14 - 7
internal/helm/grapher/relation.go

@@ -172,28 +172,35 @@ func (parsed *ParsedObjs) GetSpecRel() {
 			tid = parsed.findRBACTargets(o.ID, o.RawYAML)
 		case "Ingress":
 			rules := getField(o.RawYAML, "spec", "rules")
+			if rules == nil {
+				rules = []interface{}{}
+			}
 			for _, r := range rules.([]interface{}) {
-				http := getField(r.(map[string]interface{}), "http")
-				paths := getField(http.(map[string]interface{}), "paths")
+				paths := getField(r.(map[string]interface{}), "http", "paths")
+				if paths == nil {
+					paths = []interface{}{}
+				}
 				for _, p := range paths.([]interface{}) {
 					// service and resource are mutually exclusive backend types.
+					name := getField(p.(map[string]interface{}), "backend", "serviceName")
 					kind := "Service"
-					name := getField(p.(map[string]interface{}), "backend", "service", "name")
+					if name == nil {
+						name = getField(p.(map[string]interface{}), "backend", "service", "name")
+					}
 					if name == nil {
 						name = getField(p.(map[string]interface{}), "backend", "resource", "name")
 						kind = getField(p.(map[string]interface{}), "backend", "resource", "kind").(string)
 					}
-					tid = parsed.findObjectByNameAndKind(o.ID, name.(string), kind)
+					tid = parsed.findObjectByNameAndKind(o.ID, name, kind)
 				}
 			}
-
 		case "StatefulSet":
-			serviceName := getField(o.RawYAML, "spec", "serviceName").(string)
+			serviceName := getField(o.RawYAML, "spec", "serviceName")
 			tid = append(tid, parsed.findObjectByNameAndKind(o.ID, serviceName, "Service")...)
 		case "Pod":
 			volume := getField(o.RawYAML, "spec", "volumes")
 			imageSecrets := getField(o.RawYAML, "spec", "ImagePullSecrets")
-			serviceAccount := getField(o.RawYAML, "spec", "serviceaccountname")
+			serviceAccount := getField(o.RawYAML, "spec", "serviceAccountName")
 
 			if imageSecrets == nil {
 				imageSecrets = []interface{}{}