Jelajahi Sumber

Merge pull request #957 from porter-dev/0.7.0-hoist-expanded-chart-out-of-modal

all resource modals -> full view (+ updated inner tabs)
jusrhee 4 tahun lalu
induk
melakukan
2213a3200d
35 mengubah file dengan 514 tambahan dan 2581 penghapusan
  1. 5 733
      dashboard/package-lock.json
  2. TEMPAT SAMPAH
      dashboard/src/assets/back_arrow.png
  3. 2 5
      dashboard/src/components/ResourceTab.tsx
  4. 1 0
      dashboard/src/components/TabRegion.tsx
  5. 96 0
      dashboard/src/components/TitleSection.tsx
  6. 4 3
      dashboard/src/components/YamlEditor.tsx
  7. 1 1
      dashboard/src/components/values-form/FormWrapper.tsx
  8. 4 5
      dashboard/src/index.html
  9. 2 4
      dashboard/src/main/home/Home.tsx
  10. 11 59
      dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx
  11. 9 38
      dashboard/src/main/home/cluster-dashboard/DashboardHeader.tsx
  12. 3 35
      dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx
  13. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/node-view/ExpandedNodeView.tsx
  14. 2 2
      dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx
  15. 57 122
      dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx
  16. 83 126
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  17. 57 104
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx
  18. 17 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx
  19. 20 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx
  20. 1 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  21. 20 3
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx
  22. 2 0
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx
  23. 20 6
      dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx
  24. 2 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx
  25. 19 5
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx
  26. 2 2
      dashboard/src/main/home/dashboard/ClusterList.tsx
  27. 9 35
      dashboard/src/main/home/dashboard/Dashboard.tsx
  28. 29 61
      dashboard/src/main/home/integrations/IntegrationCategories.tsx
  29. 19 40
      dashboard/src/main/home/integrations/Integrations.tsx
  30. 3 34
      dashboard/src/main/home/launch/Launch.tsx
  31. 0 1040
      dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx
  32. 2 2
      dashboard/src/main/home/launch/expanded-template/TemplateInfo.tsx
  33. 4 54
      dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx
  34. 4 28
      dashboard/src/main/home/new-project/NewProject.tsx
  35. 3 23
      dashboard/src/main/home/project-settings/ProjectSettings.tsx

+ 5 - 733
dashboard/package-lock.json

@@ -274,99 +274,6 @@
         "react-is": "^16.8.0 || ^17.0.0"
       }
     },
-    "@react-spring/animated": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.2.4.tgz",
-      "integrity": "sha512-AfV6ZM8pCCAT29GY5C8/1bOPjZrv/7kD0vedjiE/tEYvNDwg9GlscrvsTViWR2XykJoYrDfdkYArrldWpsCJ5g==",
-      "requires": {
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/core": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.2.4.tgz",
-      "integrity": "sha512-R+PwyfsjiuYCWqaTTfCpYpRmsP0h87RNm7uxC1Uxy7QAHUfHEm2sAHn+AdHPwq/MbVwDssVT8C5yf2WGcqiXGg==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/konva": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/konva/-/konva-9.2.4.tgz",
-      "integrity": "sha512-19anDOIkfjcydDTfGgVIuZ3lruZxKubYGs9oHCswaP8SRLj7c1kkopJHUr/S4LXGxiIdqdF0XucWm0iTEPEq4w==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/native": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/native/-/native-9.2.4.tgz",
-      "integrity": "sha512-xKJWKh5qOhSclpL3iuGwJRLoZzTNvlBEnIrMs8yh8xvX6z9Lmnu4uGu5DpfrnM1GzBvRoktoCoLEx/VcEYFSng==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/rafz": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.2.4.tgz",
-      "integrity": "sha512-SOKf9eue+vAX+DGo7kWYNl9i9J3gPUlQjifIcV9Bzw9h3i30wPOOP0TjS7iMG/kLp2cdHQYDNFte6nt23VAZkQ=="
-    },
-    "@react-spring/shared": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.2.4.tgz",
-      "integrity": "sha512-ZEr4l2BxmyFRUvRA2VCkPfCJii4E7cGkwbjmTBx1EmcGrOnde/V2eF5dxqCTY3k35QuCegkrWe0coRJVkh8q2Q==",
-      "requires": {
-        "@react-spring/rafz": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/three": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.2.4.tgz",
-      "integrity": "sha512-ljFig7XW099VWwRPKPUf+4yYLivp/sSWXN3oO5SJOF/9BSoV1quS/9chZ5Myl5J14od3CsHf89Tv4FdlX5kHlA==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/types": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.2.4.tgz",
-      "integrity": "sha512-zHUXrWO8nweUN/ISjrjqU7GgXXvoEbFca1CgiE0TY0H/dqJb3l+Rhx8ecPVNYimzFg3ZZ1/T0egpLop8SOv4aA=="
-    },
-    "@react-spring/web": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.2.4.tgz",
-      "integrity": "sha512-vtPvOalLFvuju/MDBtoSnCyt0xXSL6Amyv82fljOuWPl1yGd4M1WteijnYL9Zlriljl0a3oXcPunAVYTD9dbDQ==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
-    "@react-spring/zdog": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/@react-spring/zdog/-/zdog-9.2.4.tgz",
-      "integrity": "sha512-rv7ptedS37SHr6yuCbRkUErAzAhebdgt8f4KUtZWzseC+7qLNkaZWf+uujgsb881qAuX9b9yz8rre9UKeYepgw==",
-      "requires": {
-        "@react-spring/animated": "~9.2.0",
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/shared": "~9.2.0",
-        "@react-spring/types": "~9.2.0"
-      }
-    },
     "@sheerun/mutationobserver-shim": {
       "version": "0.3.3",
       "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
@@ -577,11 +484,6 @@
       "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.0.tgz",
       "integrity": "sha512-UpLg1mn/8PLyjr+J/JwdQJM/GzysMvv2CS8y+WYAL5K0+wbvXv/pPSLEfdNaprCZsGcXTxPsFMy8QtkYv9ueew=="
     },
-    "@types/d3-voronoi": {
-      "version": "1.1.9",
-      "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.9.tgz",
-      "integrity": "sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ=="
-    },
     "@types/glob": {
       "version": "7.1.3",
       "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
@@ -654,6 +556,11 @@
       "integrity": "sha512-BnEyOcDE4H6bkg8m84xhdbkYoAoCg8sYERmAvE4Ff50U8jTfbmOinRdJpauBn1P9XsCCQgCLuSiyz3PM4WHYOA==",
       "dev": true
     },
+    "@types/js-yaml": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.2.tgz",
+      "integrity": "sha512-KbeHS/Y4R+k+5sWXEYzAZKuB1yQlZtEghuhRxrVRLaqhtoG5+26JwQsa4HyS3AWX8v1Uwukma5HheduUDskasA=="
+    },
     "@types/json-schema": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
@@ -1004,139 +911,6 @@
       "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
       "dev": true
     },
-    "@visx/annotation": {
-      "version": "1.18.1",
-      "resolved": "https://registry.npmjs.org/@visx/annotation/-/annotation-1.18.1.tgz",
-      "integrity": "sha512-z6zCk6PKmmeFziKKMBhJqJG4utg3dDWOCBZdR70HWSt9rl5cTMwIfATCuhJNbE2KuW6+QvTLiMCONySFwuVR+g==",
-      "requires": {
-        "@types/react": "*",
-        "@visx/drag": "1.18.1",
-        "@visx/group": "1.17.1",
-        "@visx/point": "1.7.0",
-        "@visx/shape": "1.17.1",
-        "@visx/text": "1.17.1",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.5.10",
-        "react-use-measure": "^2.0.4"
-      },
-      "dependencies": {
-        "@types/d3-scale": {
-          "version": "3.3.2",
-          "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
-          "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==",
-          "requires": {
-            "@types/d3-time": "^2"
-          }
-        },
-        "@types/d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg=="
-        },
-        "@visx/curve": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-1.7.0.tgz",
-          "integrity": "sha512-n0/SHM4YXjke+aEinhHFZPLMxWu3jbqtvqzfGJyibX8OmbDjavk9P+MHfGokUcw0xHy6Ch3YTuwbYuvVw5ny9A==",
-          "requires": {
-            "@types/d3-shape": "^1.3.1",
-            "d3-shape": "^1.0.6"
-          }
-        },
-        "@visx/group": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz",
-          "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==",
-          "requires": {
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "@visx/point": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz",
-          "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg=="
-        },
-        "@visx/scale": {
-          "version": "1.14.0",
-          "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-1.14.0.tgz",
-          "integrity": "sha512-ovbtEOF/d76uGMJ5UZlxdS3t2T8I6md+aIwOXBaq0HdjaCLbe7HLlMyHJKjak/sqBxLAiCGVnechTUpSkfgSQw==",
-          "requires": {
-            "@types/d3-interpolate": "^1.3.1",
-            "@types/d3-scale": "^3.3.0",
-            "@types/d3-time": "^2.0.0",
-            "d3-interpolate": "^1.4.0",
-            "d3-scale": "^3.3.0",
-            "d3-time": "^2.1.1"
-          }
-        },
-        "@visx/shape": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.17.1.tgz",
-          "integrity": "sha512-rVYFpytPCnV4s5U0za+jQ2jqFzKnmB3c8RP6fuOfF6kKosFPJcOYg9ikvewojARyMBTr1u3XvWV960Da+xyUdQ==",
-          "requires": {
-            "@types/d3-path": "^1.0.8",
-            "@types/d3-shape": "^1.3.1",
-            "@types/lodash": "^4.14.146",
-            "@types/react": "*",
-            "@visx/curve": "1.7.0",
-            "@visx/group": "1.17.1",
-            "@visx/scale": "1.14.0",
-            "classnames": "^2.3.1",
-            "d3-path": "^1.0.5",
-            "d3-shape": "^1.2.0",
-            "lodash": "^4.17.15",
-            "prop-types": "^15.5.10"
-          }
-        },
-        "@visx/text": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/text/-/text-1.17.1.tgz",
-          "integrity": "sha512-Cx6iH0kVq3YqCfFj7U6bMiKwa/bz4Z3q0vPdxmnVGcPjGZM1ac/y61KFH263e164LJ5jFaTYpPrrFmbZoy8+Vg==",
-          "requires": {
-            "@types/lodash": "^4.14.160",
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "lodash": "^4.17.20",
-            "prop-types": "^15.7.2",
-            "reduce-css-calc": "^1.3.0"
-          }
-        },
-        "classnames": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
-          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
-        },
-        "d3-scale": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
-          "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
-          "requires": {
-            "d3-array": "^2.3.0",
-            "d3-format": "1 - 2",
-            "d3-interpolate": "1.2.0 - 2",
-            "d3-time": "^2.1.1",
-            "d3-time-format": "2 - 3"
-          }
-        },
-        "d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
-          "requires": {
-            "d3-array": "2"
-          }
-        },
-        "react-use-measure": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz",
-          "integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==",
-          "requires": {
-            "debounce": "^1.2.0"
-          }
-        }
-      }
-    },
     "@visx/axis": {
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.6.1.tgz",
@@ -1232,32 +1006,6 @@
         "d3-shape": "^1.0.6"
       }
     },
-    "@visx/drag": {
-      "version": "1.18.1",
-      "resolved": "https://registry.npmjs.org/@visx/drag/-/drag-1.18.1.tgz",
-      "integrity": "sha512-5xsgUUthG/0Nq51HFWYIe3NaHT5csxuVqx/+VfNsjkGgCHntWkcS2soPlEMh4wUT2iPKRs9z9VtRAyrpN4TtKw==",
-      "requires": {
-        "@types/react": "*",
-        "@visx/event": "1.7.0",
-        "prop-types": "^15.5.10"
-      },
-      "dependencies": {
-        "@visx/event": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/event/-/event-1.7.0.tgz",
-          "integrity": "sha512-RbAoKxvy+ildX2dVXC9/ZX94lQXPwjKgtO9jy7COc15knG4zmzsMCDYDC3uLd0+jE2o/+gSaZ/9r52p6zG5+IQ==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/point": "1.7.0"
-          }
-        },
-        "@visx/point": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz",
-          "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg=="
-        }
-      }
-    },
     "@visx/event": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/@visx/event/-/event-1.3.0.tgz",
@@ -1267,36 +1015,6 @@
         "@visx/point": "1.0.0"
       }
     },
-    "@visx/glyph": {
-      "version": "1.17.1",
-      "resolved": "https://registry.npmjs.org/@visx/glyph/-/glyph-1.17.1.tgz",
-      "integrity": "sha512-9KAPmO7DsH1Iq+2kZs8oTgirgYWRq7EacNyEtsq78uuHqw0gFqmOuyYV6+iHelLbulNCAzHKVAZ7Aebfy7G8ZA==",
-      "requires": {
-        "@types/d3-shape": "^1.3.1",
-        "@types/react": "*",
-        "@visx/group": "1.17.1",
-        "classnames": "^2.3.1",
-        "d3-shape": "^1.2.0",
-        "prop-types": "^15.6.2"
-      },
-      "dependencies": {
-        "@visx/group": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz",
-          "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==",
-          "requires": {
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "classnames": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
-          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
-        }
-      }
-    },
     "@visx/gradient": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@visx/gradient/-/gradient-1.0.0.tgz",
@@ -1346,159 +1064,6 @@
       "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.0.0.tgz",
       "integrity": "sha512-0L3ILwv6ro0DsQVbA1lo8fo6q3wvIeSTt9C8NarUUkoTNSFZaJtlmvwg2238r8fwwmSv0v9QFBj1hBz4o0bHrg=="
     },
-    "@visx/react-spring": {
-      "version": "1.17.1",
-      "resolved": "https://registry.npmjs.org/@visx/react-spring/-/react-spring-1.17.1.tgz",
-      "integrity": "sha512-U2+cXYpmuwN8/TNNJAiqAcXHPewKbb2vT+YmDmkIk9G20INO95EKJbHE5+homf+Jg7mRr5En31Orxjt6eIKgzA==",
-      "requires": {
-        "@types/react": "*",
-        "@visx/axis": "1.17.1",
-        "@visx/grid": "1.17.1",
-        "@visx/scale": "1.14.0",
-        "@visx/text": "1.17.1",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.6.2"
-      },
-      "dependencies": {
-        "@types/d3-scale": {
-          "version": "3.3.2",
-          "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
-          "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==",
-          "requires": {
-            "@types/d3-time": "^2"
-          }
-        },
-        "@types/d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg=="
-        },
-        "@visx/axis": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.17.1.tgz",
-          "integrity": "sha512-3JdAY8xwA4xVnzkbXdIzCOWYCknCgw3L185lOJTXWNGO7kIgzbQ2YrLXnet37BFgD83MfxmlP6LhiHLkKVI6OQ==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/group": "1.17.1",
-            "@visx/point": "1.7.0",
-            "@visx/scale": "1.14.0",
-            "@visx/shape": "1.17.1",
-            "@visx/text": "1.17.1",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.0"
-          }
-        },
-        "@visx/curve": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-1.7.0.tgz",
-          "integrity": "sha512-n0/SHM4YXjke+aEinhHFZPLMxWu3jbqtvqzfGJyibX8OmbDjavk9P+MHfGokUcw0xHy6Ch3YTuwbYuvVw5ny9A==",
-          "requires": {
-            "@types/d3-shape": "^1.3.1",
-            "d3-shape": "^1.0.6"
-          }
-        },
-        "@visx/grid": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-1.17.1.tgz",
-          "integrity": "sha512-dse9q3weDqPNmeXK0lGKKPRgGiDuUjJ7Mt7NNonPUyXPctNmv6lJEWZu9HJrXEGiCAVNa8PHJ7Qkns/z+mH88Q==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/curve": "1.7.0",
-            "@visx/group": "1.17.1",
-            "@visx/point": "1.7.0",
-            "@visx/scale": "1.14.0",
-            "@visx/shape": "1.17.1",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "@visx/group": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz",
-          "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==",
-          "requires": {
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "@visx/point": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz",
-          "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg=="
-        },
-        "@visx/scale": {
-          "version": "1.14.0",
-          "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-1.14.0.tgz",
-          "integrity": "sha512-ovbtEOF/d76uGMJ5UZlxdS3t2T8I6md+aIwOXBaq0HdjaCLbe7HLlMyHJKjak/sqBxLAiCGVnechTUpSkfgSQw==",
-          "requires": {
-            "@types/d3-interpolate": "^1.3.1",
-            "@types/d3-scale": "^3.3.0",
-            "@types/d3-time": "^2.0.0",
-            "d3-interpolate": "^1.4.0",
-            "d3-scale": "^3.3.0",
-            "d3-time": "^2.1.1"
-          }
-        },
-        "@visx/shape": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.17.1.tgz",
-          "integrity": "sha512-rVYFpytPCnV4s5U0za+jQ2jqFzKnmB3c8RP6fuOfF6kKosFPJcOYg9ikvewojARyMBTr1u3XvWV960Da+xyUdQ==",
-          "requires": {
-            "@types/d3-path": "^1.0.8",
-            "@types/d3-shape": "^1.3.1",
-            "@types/lodash": "^4.14.146",
-            "@types/react": "*",
-            "@visx/curve": "1.7.0",
-            "@visx/group": "1.17.1",
-            "@visx/scale": "1.14.0",
-            "classnames": "^2.3.1",
-            "d3-path": "^1.0.5",
-            "d3-shape": "^1.2.0",
-            "lodash": "^4.17.15",
-            "prop-types": "^15.5.10"
-          }
-        },
-        "@visx/text": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/text/-/text-1.17.1.tgz",
-          "integrity": "sha512-Cx6iH0kVq3YqCfFj7U6bMiKwa/bz4Z3q0vPdxmnVGcPjGZM1ac/y61KFH263e164LJ5jFaTYpPrrFmbZoy8+Vg==",
-          "requires": {
-            "@types/lodash": "^4.14.160",
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "lodash": "^4.17.20",
-            "prop-types": "^15.7.2",
-            "reduce-css-calc": "^1.3.0"
-          }
-        },
-        "classnames": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
-          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
-        },
-        "d3-scale": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
-          "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
-          "requires": {
-            "d3-array": "^2.3.0",
-            "d3-format": "1 - 2",
-            "d3-interpolate": "1.2.0 - 2",
-            "d3-time": "^2.1.1",
-            "d3-time-format": "2 - 3"
-          }
-        },
-        "d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
-          "requires": {
-            "d3-array": "2"
-          }
-        }
-      }
-    },
     "@visx/responsive": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-1.3.0.tgz",
@@ -1571,271 +1136,6 @@
         "react-use-measure": "2.0.1"
       }
     },
-    "@visx/voronoi": {
-      "version": "1.17.1",
-      "resolved": "https://registry.npmjs.org/@visx/voronoi/-/voronoi-1.17.1.tgz",
-      "integrity": "sha512-XpgQ5siRYI9Vvw+Q82avIntDzfkSrIT3EmN2J/L/6ZnT3nTCjWksTEgQQ3G9GqoX510srbX8wL+mRqkYP+3O4Q==",
-      "requires": {
-        "@types/d3-voronoi": "^1.1.9",
-        "@types/react": "*",
-        "classnames": "^2.3.1",
-        "d3-voronoi": "^1.1.2",
-        "prop-types": "^15.6.1"
-      },
-      "dependencies": {
-        "classnames": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
-          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
-        }
-      }
-    },
-    "@visx/xychart": {
-      "version": "1.18.1",
-      "resolved": "https://registry.npmjs.org/@visx/xychart/-/xychart-1.18.1.tgz",
-      "integrity": "sha512-VyC7yBpLUSgzLZIWWRtf1pCP0xuYLjHEwAXl2LdCSrnyUs2pMTms7xMUNbq8uwR4e8bbBY5dbsRg/7X95tB8Yg==",
-      "requires": {
-        "@types/lodash": "^4.14.146",
-        "@types/react": "*",
-        "@visx/annotation": "1.18.1",
-        "@visx/axis": "1.17.1",
-        "@visx/event": "1.7.0",
-        "@visx/glyph": "1.17.1",
-        "@visx/grid": "1.17.1",
-        "@visx/react-spring": "1.17.1",
-        "@visx/responsive": "1.10.1",
-        "@visx/scale": "1.14.0",
-        "@visx/shape": "1.17.1",
-        "@visx/text": "1.17.1",
-        "@visx/tooltip": "1.17.1",
-        "@visx/voronoi": "1.17.1",
-        "classnames": "^2.3.1",
-        "d3-array": "^2.6.0",
-        "d3-interpolate-path": "2.2.1",
-        "d3-shape": "^2.0.0",
-        "lodash": "^4.17.10",
-        "mitt": "^2.1.0",
-        "prop-types": "^15.6.2"
-      },
-      "dependencies": {
-        "@types/d3-scale": {
-          "version": "3.3.2",
-          "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
-          "integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==",
-          "requires": {
-            "@types/d3-time": "^2"
-          }
-        },
-        "@types/d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg=="
-        },
-        "@visx/axis": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-1.17.1.tgz",
-          "integrity": "sha512-3JdAY8xwA4xVnzkbXdIzCOWYCknCgw3L185lOJTXWNGO7kIgzbQ2YrLXnet37BFgD83MfxmlP6LhiHLkKVI6OQ==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/group": "1.17.1",
-            "@visx/point": "1.7.0",
-            "@visx/scale": "1.14.0",
-            "@visx/shape": "1.17.1",
-            "@visx/text": "1.17.1",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.0"
-          }
-        },
-        "@visx/bounds": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-1.7.0.tgz",
-          "integrity": "sha512-ajF6PTgDoZTfwv5J0ZTx1miXY8lk3sGhMVqE3UsMubdTZBlOgeZMT4OmtTPtbCJTBTgw0FD0gd7X3gZ+3X9HgQ==",
-          "requires": {
-            "@types/react": "*",
-            "@types/react-dom": "*",
-            "prop-types": "^15.5.10"
-          }
-        },
-        "@visx/curve": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-1.7.0.tgz",
-          "integrity": "sha512-n0/SHM4YXjke+aEinhHFZPLMxWu3jbqtvqzfGJyibX8OmbDjavk9P+MHfGokUcw0xHy6Ch3YTuwbYuvVw5ny9A==",
-          "requires": {
-            "@types/d3-shape": "^1.3.1",
-            "d3-shape": "^1.0.6"
-          },
-          "dependencies": {
-            "d3-shape": {
-              "version": "1.3.7",
-              "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
-              "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
-              "requires": {
-                "d3-path": "1"
-              }
-            }
-          }
-        },
-        "@visx/event": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/event/-/event-1.7.0.tgz",
-          "integrity": "sha512-RbAoKxvy+ildX2dVXC9/ZX94lQXPwjKgtO9jy7COc15knG4zmzsMCDYDC3uLd0+jE2o/+gSaZ/9r52p6zG5+IQ==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/point": "1.7.0"
-          }
-        },
-        "@visx/grid": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-1.17.1.tgz",
-          "integrity": "sha512-dse9q3weDqPNmeXK0lGKKPRgGiDuUjJ7Mt7NNonPUyXPctNmv6lJEWZu9HJrXEGiCAVNa8PHJ7Qkns/z+mH88Q==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/curve": "1.7.0",
-            "@visx/group": "1.17.1",
-            "@visx/point": "1.7.0",
-            "@visx/scale": "1.14.0",
-            "@visx/shape": "1.17.1",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "@visx/group": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/group/-/group-1.17.1.tgz",
-          "integrity": "sha512-g8pSqy8TXAisiOzypnVycDynEGlBhfxtVlwDmsbYB+XSFGEjnOheQSDohDI+ia7ek54Mw9uYe05tx5kP1hRMYw==",
-          "requires": {
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.6.2"
-          }
-        },
-        "@visx/point": {
-          "version": "1.7.0",
-          "resolved": "https://registry.npmjs.org/@visx/point/-/point-1.7.0.tgz",
-          "integrity": "sha512-oaoY/HXYHhmpkkeKI4rBPmFtjHWtxSrIhZCVm1ipPoyQp3voJ8L6JD5eUIVmmaUCdUGUGwL1lFLnJiQ2p1Vlwg=="
-        },
-        "@visx/responsive": {
-          "version": "1.10.1",
-          "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-1.10.1.tgz",
-          "integrity": "sha512-7FT2BBmWFkFFqynI9C1NYfVOKT1FsNOm6MwWMqXKA7TMomdBW0wdtQNB1bHvwJvWurM/sNqxcQ/CBED6t9xujQ==",
-          "requires": {
-            "@types/lodash": "^4.14.146",
-            "@types/react": "*",
-            "lodash": "^4.17.10",
-            "prop-types": "^15.6.1",
-            "resize-observer-polyfill": "1.5.1"
-          }
-        },
-        "@visx/scale": {
-          "version": "1.14.0",
-          "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-1.14.0.tgz",
-          "integrity": "sha512-ovbtEOF/d76uGMJ5UZlxdS3t2T8I6md+aIwOXBaq0HdjaCLbe7HLlMyHJKjak/sqBxLAiCGVnechTUpSkfgSQw==",
-          "requires": {
-            "@types/d3-interpolate": "^1.3.1",
-            "@types/d3-scale": "^3.3.0",
-            "@types/d3-time": "^2.0.0",
-            "d3-interpolate": "^1.4.0",
-            "d3-scale": "^3.3.0",
-            "d3-time": "^2.1.1"
-          }
-        },
-        "@visx/shape": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-1.17.1.tgz",
-          "integrity": "sha512-rVYFpytPCnV4s5U0za+jQ2jqFzKnmB3c8RP6fuOfF6kKosFPJcOYg9ikvewojARyMBTr1u3XvWV960Da+xyUdQ==",
-          "requires": {
-            "@types/d3-path": "^1.0.8",
-            "@types/d3-shape": "^1.3.1",
-            "@types/lodash": "^4.14.146",
-            "@types/react": "*",
-            "@visx/curve": "1.7.0",
-            "@visx/group": "1.17.1",
-            "@visx/scale": "1.14.0",
-            "classnames": "^2.3.1",
-            "d3-path": "^1.0.5",
-            "d3-shape": "^1.2.0",
-            "lodash": "^4.17.15",
-            "prop-types": "^15.5.10"
-          },
-          "dependencies": {
-            "d3-shape": {
-              "version": "1.3.7",
-              "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
-              "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
-              "requires": {
-                "d3-path": "1"
-              }
-            }
-          }
-        },
-        "@visx/text": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/text/-/text-1.17.1.tgz",
-          "integrity": "sha512-Cx6iH0kVq3YqCfFj7U6bMiKwa/bz4Z3q0vPdxmnVGcPjGZM1ac/y61KFH263e164LJ5jFaTYpPrrFmbZoy8+Vg==",
-          "requires": {
-            "@types/lodash": "^4.14.160",
-            "@types/react": "*",
-            "classnames": "^2.3.1",
-            "lodash": "^4.17.20",
-            "prop-types": "^15.7.2",
-            "reduce-css-calc": "^1.3.0"
-          }
-        },
-        "@visx/tooltip": {
-          "version": "1.17.1",
-          "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-1.17.1.tgz",
-          "integrity": "sha512-YfRgVtKSLTn3iW8CT5+CfTWhSXGeAp01SaPDThtdaUTx89rKv5wb4oyVgeQ5g2ScRYVC8mYj5RzY/pj3RrezFQ==",
-          "requires": {
-            "@types/react": "*",
-            "@visx/bounds": "1.7.0",
-            "classnames": "^2.3.1",
-            "prop-types": "^15.5.10",
-            "react-use-measure": "^2.0.4"
-          }
-        },
-        "classnames": {
-          "version": "2.3.1",
-          "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
-          "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
-        },
-        "d3-scale": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
-          "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
-          "requires": {
-            "d3-array": "^2.3.0",
-            "d3-format": "1 - 2",
-            "d3-interpolate": "1.2.0 - 2",
-            "d3-time": "^2.1.1",
-            "d3-time-format": "2 - 3"
-          }
-        },
-        "d3-shape": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
-          "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
-          "requires": {
-            "d3-path": "1 - 2"
-          }
-        },
-        "d3-time": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
-          "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
-          "requires": {
-            "d3-array": "2"
-          }
-        },
-        "react-use-measure": {
-          "version": "2.0.4",
-          "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz",
-          "integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==",
-          "requires": {
-            "debounce": "^1.2.0"
-          }
-        }
-      }
-    },
     "@webassemblyjs/ast": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@@ -3287,11 +2587,6 @@
         "d3-color": "1"
       }
     },
-    "d3-interpolate-path": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.2.1.tgz",
-      "integrity": "sha512-6qLLh/KJVzls0XtMsMpcxhqMhgVEN7VIbR/6YGZe2qlS8KDgyyVB20XcmGnDyB051HcefQXM/Tppa9vcANEA4Q=="
-    },
     "d3-path": {
       "version": "1.0.9",
       "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
@@ -3335,11 +2630,6 @@
         "d3-time": "1 - 2"
       }
     },
-    "d3-voronoi": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
-      "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
-    },
     "debounce": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
@@ -5975,11 +5265,6 @@
         "through2": "^2.0.0"
       }
     },
-    "mitt": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
-      "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg=="
-    },
     "mixin-deep": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -7011,19 +6296,6 @@
         "tiny-warning": "^1.0.0"
       }
     },
-    "react-spring": {
-      "version": "9.2.4",
-      "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-9.2.4.tgz",
-      "integrity": "sha512-bMjbyTW0ZGd+/h9cjtohLqCwOGqX2OuaTvalOVfLCGmhzEg/u3GgopI3LAm4UD2Br3MNdVdGgNVoESg4MGqKFQ==",
-      "requires": {
-        "@react-spring/core": "~9.2.0",
-        "@react-spring/konva": "~9.2.0",
-        "@react-spring/native": "~9.2.0",
-        "@react-spring/three": "~9.2.0",
-        "@react-spring/web": "~9.2.0",
-        "@react-spring/zdog": "~9.2.0"
-      }
-    },
     "react-table": {
       "version": "7.7.0",
       "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz",

TEMPAT SAMPAH
dashboard/src/assets/back_arrow.png


+ 2 - 5
dashboard/src/components/ResourceTab.tsx

@@ -142,15 +142,12 @@ export default class ResourceTab extends Component<PropsType, StateType> {
 const StyledResourceTab = styled.div`
   width: 100%;
   margin-bottom: 2px;
+  overflow: hidden;
   background: #ffffff11;
   border-bottom-left-radius: ${(props: {
     isLast: boolean;
     roundAllCorners: boolean;
-  }) => (props.isLast ? "5px" : "")};
-  border-bottom-right-radius: ${(props: {
-    isLast: boolean;
-    roundAllCorners: boolean;
-  }) => (props.roundAllCorners && props.isLast ? "5px" : "")};
+  }) => (props.isLast ? "10px" : "")};
 `;
 
 const Tooltip = styled.div`

+ 1 - 0
dashboard/src/components/TabRegion.tsx

@@ -91,4 +91,5 @@ const StyledTabRegion = styled.div`
   height: 100%;
   position: relative;
   overflow-y: auto;
+  overflow: visible;
 `;

+ 96 - 0
dashboard/src/components/TitleSection.tsx

@@ -0,0 +1,96 @@
+import React from "react";
+import styled from "styled-components";
+
+interface Props {
+  children: React.ReactNode;
+  icon?: any;
+  iconWidth?: string;
+  capitalize?: boolean;
+  handleNavBack?: () => void;
+}
+
+const TitleSection: React.FC<Props> = ({
+  children,
+  icon,
+  iconWidth,
+  capitalize,
+  handleNavBack,
+}) => {
+  return (
+    <StyledTitleSection>
+      {handleNavBack && (
+        <BackButton>
+          <i className="material-icons" onClick={handleNavBack}>
+            keyboard_backspace
+          </i>
+        </BackButton>
+      )}
+      {icon && <Icon width={iconWidth} src={icon} />}
+      <StyledTitle capitalize={capitalize}>{children}</StyledTitle>
+    </StyledTitleSection>
+  );
+};
+
+export default TitleSection;
+
+const BackButton = styled.div`
+  > i {
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    margin-right: 10px;
+    padding: 3px;
+    margin-left: 0px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;
+
+const StyledTitleSection = styled.div`
+  margin-bottom: 15px;
+  display: flex;
+  align-items: center;
+`;
+
+const Icon = styled.img<{ width: string }>`
+  width: ${(props) => props.width || "28px"};
+  margin-right: 16px;
+`;
+
+const StyledTitle = styled.div<{ capitalize: boolean }>`
+  font-size: 24px;
+  font-weight: 600;
+  user-select: text;
+  text-transform: ${(props) => (props.capitalize ? "capitalize" : "")};
+  display: flex;
+  align-items: center;
+
+  > i {
+    margin-left: 10px;
+    cursor: pointer;
+    font-size: 18px;
+    color: #858faaaa;
+    padding: 5px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+    margin-bottom: -3px;
+  }
+
+  > a {
+    > i {
+      display: flex;
+      align-items: center;
+      margin-bottom: -2px;
+      font-size: 18px;
+      margin-left: 15px;
+      color: #858faaaa;
+      :hover {
+        color: #aaaabb;
+      }
+    }
+  }
+`;

+ 4 - 3
dashboard/src/components/YamlEditor.tsx

@@ -52,7 +52,7 @@ class YamlEditor extends Component<PropsType, StateType> {
             editorProps={{ $blockScrolling: true }}
             height={this.props.height}
             width="100%"
-            style={{ borderRadius: "5px" }}
+            style={{ borderRadius: "10px" }}
             showPrintMargin={false}
             showGutter={true}
             highlightActiveLine={true}
@@ -67,9 +67,10 @@ class YamlEditor extends Component<PropsType, StateType> {
 export default YamlEditor;
 
 const Editor = styled.form`
-  border-radius: ${(props: { border: boolean }) => (props.border ? "5px" : "")};
+  border-radius: ${(props: { border: boolean }) =>
+    props.border ? "10px" : ""};
   border: ${(props: { border: boolean }) =>
-    props.border ? "1px solid #ffffff22" : ""};
+    props.border ? "1px solid #ffffff33" : ""};
 `;
 
 const Holder = styled.div`

+ 1 - 1
dashboard/src/components/values-form/FormWrapper.tsx

@@ -445,7 +445,7 @@ export default class FormWrapper extends Component<PropsType, StateType> {
     let showSave = this.showSaveButton();
     return (
       <>
-        {this.props.isInModal ? (
+        {this.props.isInModal || !showSave ? (
           <StyledValuesWrapper showSave={showSave}>
             {this.renderContents(showSave)}
           </StyledValuesWrapper>

+ 4 - 5
dashboard/src/index.html

@@ -67,7 +67,7 @@
       })();
     </script>
 
-    <link rel="icon" href="https://i.ibb.co/Xy0QK6P/dsquare.png" />
+    <link rel="icon" href="https://i.ibb.co/HnSk02f/ptr.png" />
     <meta
       name="description"
       content="Kubernetes powered PaaS that runs in your own cloud."
@@ -75,15 +75,14 @@
     <meta property="og:title" content="Porter" />
     <meta
       property="og:image"
-      content="https://i.ibb.co/DL4695L/logo-wide.png"
+      content="https://i.ibb.co/52g2g7C/porter-wide.png"
     />
     <meta
       property="og:description"
-      content="Fully-managed remote dev environments for any team."
+      content="Kubernetes powered PaaS that runs in your own cloud."
     />
-    <meta property="og:url" content="https://getporter.dev" />
+    <meta property="og:url" content="https://porter.run" />
 
-    <link rel="icon" href="https://i.ibb.co/Xy0QK6P/dsquare.png" />
     <link
       href="https://fonts.googleapis.com/icon?family=Material+Icons"
       rel="stylesheet"

+ 2 - 4
dashboard/src/main/home/Home.tsx

@@ -605,7 +605,7 @@ export default withRouter(withAuth(Home));
 const ViewWrapper = styled.div`
   height: 100%;
   width: 100vw;
-  padding-top: 30px;
+  padding-top: 10vh;
   overflow-y: auto;
   display: flex;
   flex: 1;
@@ -615,10 +615,8 @@ const ViewWrapper = styled.div`
 `;
 
 const DashboardWrapper = styled.div`
-  width: 80%;
-  padding-top: 50px;
+  width: 83%;
   min-width: 300px;
-  padding-bottom: 120px;
 `;
 
 const StyledHome = styled.div`

+ 11 - 59
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -13,6 +13,7 @@ import {
   pushQueryParams,
 } from "shared/routing";
 
+import DashboardHeader from "./DashboardHeader";
 import ChartList from "./chart/ChartList";
 import EnvGroupDashboard from "./env-groups/EnvGroupDashboard";
 import NamespaceSelector from "./NamespaceSelector";
@@ -112,14 +113,6 @@ class ClusterDashboard extends Component<PropsType, StateType> {
     }
   }
 
-  renderDashboardIcon = () => {
-    if (this.props.currentView === "jobs") {
-      return <Img src={monojob} />;
-    } else {
-      return <Img src={monoweb} />;
-    }
-  };
-
   getDescription = (currentView: string): string => {
     if (currentView === "jobs") {
       return "Scripts and tasks that run once or on a repeating interval.";
@@ -183,22 +176,11 @@ class ClusterDashboard extends Component<PropsType, StateType> {
 
     return (
       <>
-        <TitleSection>
-          {this.renderDashboardIcon()}
-          <Title>{currentView}</Title>
-        </TitleSection>
-
-        <InfoSection>
-          <TopRow>
-            <InfoLabel>
-              <i className="material-icons">info</i> Info
-            </InfoLabel>
-          </TopRow>
-          <Description>{this.getDescription(currentView)}</Description>
-        </InfoSection>
-
-        <LineBreak />
-
+        <DashboardHeader
+          image={currentView === "jobs" ? monojob : monoweb}
+          title={currentView}
+          description={this.getDescription(currentView)}
+        />
         {this.renderBody()}
       </>
     );
@@ -250,6 +232,11 @@ ClusterDashboard.contextType = Context;
 
 export default withRouter(withAuth(ClusterDashboard));
 
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
+`;
+
 const ControlRow = styled.div`
   display: flex;
   justify-content: ${(props: { hasMultipleChilds: boolean }) => {
@@ -401,41 +388,6 @@ const Img = styled.img`
   width: 30px;
 `;
 
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  text-transform: capitalize;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;
-
 const SortFilterWrapper = styled.div`
   width: 468px;
   display: flex;

+ 9 - 38
dashboard/src/main/home/cluster-dashboard/DashboardHeader.tsx

@@ -3,6 +3,8 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 
+import TitleSection from "components/TitleSection";
+
 type PropsType = {
   image: any;
   title: string;
@@ -15,11 +17,12 @@ export default class DashboardHeader extends Component<PropsType, StateType> {
   render() {
     return (
       <>
-        <TitleSection>
-          <Img src={this.props.image} />
-          <Title>{this.props.title}</Title>
+        <TitleSection capitalize={true} icon={this.props.image}>
+          {this.props.title}
         </TitleSection>
 
+        <Br />
+
         <InfoSection>
           <TopRow>
             <InfoLabel>
@@ -37,8 +40,9 @@ export default class DashboardHeader extends Component<PropsType, StateType> {
 
 DashboardHeader.contextType = Context;
 
-const Img = styled.img`
-  width: 30px;
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
 `;
 
 const LineBreak = styled.div`
@@ -82,16 +86,6 @@ const InfoSection = styled.div`
   margin-bottom: 35px;
 `;
 
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  text-transform: capitalize;
-  white-space: nowrap;
-`;
-
 const ClusterLabel = styled.div`
   color: #ffffff22;
   font-size: 14px;
@@ -101,26 +95,3 @@ const ClusterLabel = styled.div`
   overflow: hidden;
   text-overflow: ellipsis;
 `;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size 18px;
-    color: #858FAAaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;

+ 3 - 35
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -3,6 +3,7 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 import TabSelector from "components/TabSelector";
+import TitleSection from "components/TitleSection";
 
 import NodeList from "./NodeList";
 
@@ -56,7 +57,7 @@ export const Dashboard: React.FunctionComponent = () => {
         <DashboardIcon>
           <i className="material-icons">device_hub</i>
         </DashboardIcon>
-        <Title>{context.currentCluster.name}</Title>
+        {context.currentCluster.name}
       </TitleSection>
 
       <InfoSection>
@@ -86,6 +87,7 @@ const DashboardIcon = styled.div`
   min-width: 45px;
   width: 45px;
   border-radius: 5px;
+  margin-right: 17px;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -128,37 +130,3 @@ const InfoSection = styled.div`
   margin-left: 0px;
   margin-bottom: 35px;
 `;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/node-view/ExpandedNodeView.tsx

@@ -185,7 +185,7 @@ const IconWrapper = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 18px;
+  font-size: 20px;
   font-weight: 500;
   display: flex;
   align-items: center;

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx

@@ -325,8 +325,8 @@ const Subtitle = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   margin-left: 15px;
   border-radius: 2px;

+ 57 - 122
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -1,6 +1,7 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import close from "assets/close.png";
+import backArrow from "assets/back_arrow.png";
 import key from "assets/key.svg";
 import _ from "lodash";
 
@@ -9,6 +10,7 @@ import { Context } from "shared/Context";
 import { isAlphanumeric } from "shared/common";
 import api from "shared/api";
 
+import TitleSection from "components/TitleSection";
 import SaveButton from "components/SaveButton";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
@@ -373,8 +375,11 @@ class ExpandedEnvGroup extends Component<PropsType, StateType> {
 
     return (
       <>
-        <CloseOverlay onClick={closeExpanded} />
         <StyledExpandedChart>
+          <BackButton onClick={closeExpanded}>
+            <BackButtonImg src={backArrow} />
+          </BackButton>
+
           <ConfirmOverlay
             show={this.state.showDeleteOverlay}
             message={`Are you sure you want to delete ${name}?`}
@@ -383,29 +388,18 @@ class ExpandedEnvGroup extends Component<PropsType, StateType> {
           />
           {this.renderDeleteOverlay()}
 
-          <HeaderWrapper>
-            <TitleSection>
-              <Title>
-                <IconWrapper>
-                  <Icon src={key} />
-                </IconWrapper>
-                {name}
-              </Title>
-              <InfoWrapper>
-                <LastDeployed>
-                  Last updated {this.readableDate(timestamp)}
-                </LastDeployed>
-              </InfoWrapper>
-
-              <TagWrapper>
-                Namespace <NamespaceTag>{namespace}</NamespaceTag>
-              </TagWrapper>
-            </TitleSection>
-
-            <CloseButton onClick={closeExpanded}>
-              <CloseButtonImg src={close} />
-            </CloseButton>
-          </HeaderWrapper>
+          <TitleSection icon={key} iconWidth="33px">
+            {name}
+            <TagWrapper>
+              Namespace <NamespaceTag>{namespace}</NamespaceTag>
+            </TagWrapper>
+          </TitleSection>
+
+          <InfoWrapper>
+            <LastDeployed>
+              Last updated {this.readableDate(timestamp)}
+            </LastDeployed>
+          </InfoWrapper>
 
           <TabRegion
             currentTab={this.state.currentTab}
@@ -425,6 +419,33 @@ ExpandedEnvGroup.contextType = Context;
 
 export default withAuth(ExpandedEnvGroup);
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const Button = styled.button`
   height: 35px;
   font-size: 13px;
@@ -464,6 +485,7 @@ const InnerWrapper = styled.div<{ full?: boolean }>`
 const TabWrapper = styled.div`
   height: 100%;
   width: 100%;
+  padding-bottom: 65px;
   overflow: hidden;
 `;
 
@@ -499,37 +521,10 @@ const DeleteOverlay = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const HeaderWrapper = styled.div``;
-
-const Dot = styled.div`
-  margin-right: 9px;
-  margin-left: 9px;
-`;
-
 const InfoWrapper = styled.div`
   display: flex;
   align-items: center;
-  margin: 24px 0px 17px 0px;
+  margin: 10px 0px 17px 0px;
   height: 20px;
 `;
 
@@ -543,13 +538,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  right: 0px;
-  bottom: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -574,85 +569,25 @@ const NamespaceTag = styled.div`
   border-bottom-left-radius: 0px;
 `;
 
-const Icon = styled.img`
-  width: 100%;
-`;
-
-const IconWrapper = styled.div`
-  color: #efefef;
-  font-size: 16px;
-  height: 20px;
-  width: 20px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  border-radius: 3px;
-  margin-right: 12px;
-
-  > i {
-    font-size: 20px;
-  }
-`;
-
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  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 StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  overflow: hidden;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 83 - 126
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -8,7 +8,7 @@ import React, {
 } from "react";
 import styled from "styled-components";
 import yaml from "js-yaml";
-import close from "assets/close.png";
+import backArrow from "assets/back_arrow.png";
 import _ from "lodash";
 import loadingSrc from "assets/loading.gif";
 
@@ -34,6 +34,7 @@ import StatusSection from "./status/StatusSection";
 import SettingsSection from "./SettingsSection";
 import { useWebsockets } from "shared/hooks/useWebsockets";
 import useAuth from "shared/auth/useAuth";
+import TitleSection from "components/TitleSection";
 
 type Props = {
   namespace: string;
@@ -646,8 +647,11 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
   return (
     <>
-      <CloseOverlay onClick={props.closeChart} />
       <StyledExpandedChart>
+        <BackButton onClick={props.closeChart}>
+          <BackButtonImg src={backArrow} />
+        </BackButton>
+
         <ConfirmOverlay
           show={showDeleteOverlay}
           message={`Are you sure you want to delete ${currentChart.name}?`}
@@ -660,34 +664,30 @@ const ExpandedChart: React.FC<Props> = (props) => {
           </DeleteOverlay>
         )}
         <HeaderWrapper>
-          <TitleSection>
-            <Title>
-              <IconWrapper>{renderIcon()}</IconWrapper>
-              {currentChart.name}
-            </Title>
-            {currentChart.chart.metadata.name != "worker" &&
-              currentChart.chart.metadata.name != "job" &&
-              renderUrl()}
-            <InfoWrapper>
-              <StatusIndicator
-                controllers={controllers}
-                status={currentChart.info.status}
-                margin_left={"0px"}
-              />
-              <LastDeployed>
-                <Dot>•</Dot>Last deployed
-                {" " + getReadableDate(currentChart.info.last_deployed)}
-              </LastDeployed>
-            </InfoWrapper>
-
+          <TitleSection
+            icon={currentChart.chart.metadata.icon}
+            iconWidth="33px"
+          >
+            {currentChart.name}
             <TagWrapper>
               Namespace <NamespaceTag>{currentChart.namespace}</NamespaceTag>
             </TagWrapper>
           </TitleSection>
 
-          <CloseButton onClick={props.closeChart}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
+          {currentChart.chart.metadata.name != "worker" &&
+            currentChart.chart.metadata.name != "job" &&
+            renderUrl()}
+          <InfoWrapper>
+            <StatusIndicator
+              controllers={controllers}
+              status={currentChart.info.status}
+              margin_left={"0px"}
+            />
+            <LastDeployed>
+              <Dot>•</Dot>Last deployed
+              {" " + getReadableDate(currentChart.info.last_deployed)}
+            </LastDeployed>
+          </InfoWrapper>
 
           <RevisionSection
             showRevisions={showRevisions}
@@ -709,30 +709,27 @@ const ExpandedChart: React.FC<Props> = (props) => {
             upgradeVersion={handleUpgradeVersion}
           />
         </HeaderWrapper>
-        <BodyWrapper>
-          <FormWrapper
-            isReadOnly={
-              imageIsPlaceholder ||
-              !isAuthorized("application", "", ["get", "update"])
-            }
-            formData={currentChart.form}
-            tabOptions={tabOptions}
-            isInModal={true}
-            renderTabContents={renderTabContents}
-            onSubmit={onSubmit}
-            saveValuesStatus={saveValuesStatus}
-            externalValues={{
-              namespace: props.namespace,
-              clusterId: currentCluster.id,
-            }}
-            color={isPreview ? "#f5cb42" : null}
-            addendum={
-              <TabButton onClick={toggleDevOpsMode} devOpsMode={devOpsMode}>
-                <i className="material-icons">offline_bolt</i> DevOps Mode
-              </TabButton>
-            }
-          />
-        </BodyWrapper>
+        <FormWrapper
+          isReadOnly={
+            imageIsPlaceholder ||
+            !isAuthorized("application", "", ["get", "update"])
+          }
+          formData={currentChart.form}
+          tabOptions={tabOptions}
+          renderTabContents={renderTabContents}
+          onSubmit={onSubmit}
+          saveValuesStatus={saveValuesStatus}
+          externalValues={{
+            namespace: props.namespace,
+            clusterId: currentCluster.id,
+          }}
+          color={isPreview ? "#f5cb42" : null}
+          addendum={
+            <TabButton onClick={toggleDevOpsMode} devOpsMode={devOpsMode}>
+              <i className="material-icons">offline_bolt</i> DevOps Mode
+            </TabButton>
+          }
+        />
       </StyledExpandedChart>
     </>
   );
@@ -742,6 +739,33 @@ export default ExpandedChart;
 
 const TextWrap = styled.div``;
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const Header = styled.div`
   font-weight: 500;
   color: #aaaabb;
@@ -768,12 +792,6 @@ const Spinner = styled.img`
   margin-bottom: -2px;
 `;
 
-const BodyWrapper = styled.div`
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-`;
-
 const DeleteOverlay = styled.div`
   position: absolute;
   top: 0px;
@@ -858,26 +876,6 @@ const TabButton = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
 const HeaderWrapper = styled.div``;
 
 const Dot = styled.div`
@@ -901,13 +899,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  bottom: 0px;
-  right: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -952,66 +950,25 @@ const IconWrapper = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-  user-select: text;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  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 StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
-  overflow: hidden;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 57 - 104
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -1,7 +1,8 @@
 import React, { Component } from "react";
 import styled from "styled-components";
 import yaml from "js-yaml";
-import close from "assets/close.png";
+
+import backArrow from "assets/back_arrow.png";
 import _ from "lodash";
 import loading from "assets/loading.gif";
 
@@ -12,11 +13,10 @@ import api from "shared/api";
 import SaveButton from "components/SaveButton";
 import ConfirmOverlay from "components/ConfirmOverlay";
 import Loading from "components/Loading";
-import TabRegion from "components/TabRegion";
+import TitleSection from "components/TitleSection";
 import JobList from "./jobs/JobList";
 import SettingsSection from "./SettingsSection";
 import FormWrapper from "components/values-form/FormWrapper";
-import { PlaceHolder } from "brace";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 
 type PropsType = WithAuthProps & {
@@ -509,19 +509,6 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
     this.setState({ tabOptions });
   }
 
-  renderIcon = () => {
-    let { currentChart } = this.state;
-
-    if (
-      currentChart.chart.metadata.icon &&
-      currentChart.chart.metadata.icon !== ""
-    ) {
-      return <Icon src={currentChart.chart.metadata.icon} />;
-    } else {
-      return <i className="material-icons">tonality</i>;
-    }
-  };
-
   readableDate = (s: string) => {
     let ts = new Date(s);
     let date = ts.toLocaleDateString();
@@ -585,8 +572,11 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
 
     return (
       <>
-        <CloseOverlay onClick={closeChart} />
         <StyledExpandedChart>
+          <BackButton onClick={closeChart}>
+            <BackButtonImg src={backArrow} />
+          </BackButton>
+
           <ConfirmOverlay
             show={this.state.showDeleteOverlay}
             message={`Are you sure you want to delete ${currentChart.name}?`}
@@ -596,27 +586,23 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
           {this.renderDeleteOverlay()}
 
           <HeaderWrapper>
-            <TitleSection>
-              <Title>
-                <IconWrapper>{this.renderIcon()}</IconWrapper>
-                {chart.name}
-              </Title>
-              <InfoWrapper>
-                <LastDeployed>
-                  Run {this.state.jobs.length} times <Dot>•</Dot>Last template
-                  update at
-                  {" " + this.readableDate(chart.info.last_deployed)}
-                </LastDeployed>
-              </InfoWrapper>
-
+            <TitleSection
+              icon={currentChart.chart.metadata.icon}
+              iconWidth="33px"
+            >
+              {chart.name}
               <TagWrapper>
                 Namespace <NamespaceTag>{chart.namespace}</NamespaceTag>
               </TagWrapper>
             </TitleSection>
 
-            <CloseButton onClick={closeChart}>
-              <CloseButtonImg src={close} />
-            </CloseButton>
+            <InfoWrapper>
+              <LastDeployed>
+                Run {this.state.jobs.length} times <Dot>•</Dot>Last template
+                update at
+                {" " + this.readableDate(chart.info.last_deployed)}
+              </LastDeployed>
+            </InfoWrapper>
           </HeaderWrapper>
 
           <BodyWrapper>
@@ -631,7 +617,6 @@ class ExpandedJobChart extends Component<PropsType, StateType> {
               }
               formData={this.state.formData}
               tabOptions={this.state.tabOptions}
-              isInModal={true}
               renderTabContents={this.renderTabContents}
               tabOptionsOnly={true}
               onSubmit={(formValues) =>
@@ -651,6 +636,33 @@ ExpandedJobChart.contextType = Context;
 
 export default withAuth(ExpandedJobChart);
 
+const BackButton = styled.div`
+  position: absolute;
+  top: 0px;
+  right: 0px;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;
+
 const TextWrap = styled.div``;
 
 const Header = styled.div`
@@ -688,6 +700,7 @@ const BodyWrapper = styled.div`
 const TabWrapper = styled.div`
   height: 100%;
   width: 100%;
+  padding-bottom: 47px;
   overflow: hidden;
 `;
 
@@ -723,26 +736,6 @@ const DeleteOverlay = styled.div`
   }
 `;
 
-const CloseOverlay = styled.div`
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: #202227;
-  animation: fadeIn 0.2s 0s;
-  opacity: 0;
-  animation-fill-mode: forwards;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
 const HeaderWrapper = styled.div``;
 
 const Dot = styled.div`
@@ -767,13 +760,13 @@ const LastDeployed = styled.div`
 `;
 
 const TagWrapper = styled.div`
-  position: absolute;
-  right: 0px;
-  bottom: 0px;
   height: 20px;
   font-size: 12px;
   display: flex;
+  margin-left: 20px;
+  margin-bottom: -3px;
   align-items: center;
+  font-weight: 400;
   justify-content: center;
   color: #ffffff44;
   border: 1px solid #ffffff44;
@@ -818,65 +811,25 @@ const IconWrapper = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 18px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-`;
-
-const TitleSection = styled.div`
-  width: 100%;
-  position: relative;
-`;
-
-const CloseButton = styled.div`
-  position: absolute;
-  display: block;
-  width: 40px;
-  height: 40px;
-  padding: 13px 0 12px 0;
-  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 StyledExpandedChart = styled.div`
-  width: calc(100% - 50px);
-  height: calc(100% - 50px);
+  width: 100%;
   z-index: 0;
-  position: absolute;
-  top: 25px;
-  left: 25px;
-  border-radius: 10px;
-  background: #26272f;
-  box-shadow: 0 5px 12px 4px #00000033;
-  animation: floatIn 0.3s;
+  position: relative;
+  animation: fadeIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
-  padding: 25px;
   display: flex;
-  overflow: hidden;
+  overflow-y: auto;
+  padding-bottom: 120px;
   flex-direction: column;
+  overflow: visible;
 
-  @keyframes floatIn {
+  @keyframes fadeIn {
     from {
       opacity: 0;
-      transform: translateY(30px);
     }
     to {
       opacity: 1;
-      transform: translateY(0px);
     }
   }
 `;

+ 17 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/GraphSection.tsx

@@ -48,9 +48,23 @@ GraphSection.contextType = Context;
 
 const StyledGraphSection = styled.div`
   width: 100%;
-  height: 100%;
-  background: #ffffff11;
+  min-height: 450px;
+  height: 50vh;
   font-size: 13px;
-  border-radius: 5px;
   overflow: hidden;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 20 - 5
dashboard/src/main/home/cluster-dashboard/expanded-chart/ListSection.tsx

@@ -120,13 +120,13 @@ ListSection.contextType = Context;
 const YamlWrapper = styled.div`
   width: 100%;
   height: 100%;
+  overflow: visible;
 `;
 
 const TabWrapper = styled.div`
   min-width: 200px;
   width: 35%;
   margin-right: 10px;
-  border-radius: 5px;
   overflow: hidden;
   overflow-y: auto;
 `;
@@ -135,14 +135,29 @@ const FlexWrapper = styled.div`
   display: flex;
   flex: 1;
   height: 100%;
+  overflow: visible;
 `;
 
 const StyledListSection = styled.div`
-  width: 100%;
-  height: 100%;
   display: flex;
-  position: relative;
   font-size: 13px;
-  border-radius: 5px;
+  width: 100%;
+  min-height: 450px;
+  height: 50vh;
+  font-size: 13px;
   overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -371,6 +371,7 @@ const A = styled.a`
 
 const Wrapper = styled.div`
   width: 100%;
+  padding-bottom: 65px;
   height: 100%;
 `;
 

+ 20 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/ValuesYaml.tsx

@@ -111,13 +111,30 @@ ValuesYaml.contextType = Context;
 const Wrapper = styled.div`
   overflow: auto;
   height: calc(100% - 60px);
-  border-radius: 5px;
-  border: 1px solid #ffffff22;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
 `;
 
 const StyledValuesYaml = styled.div`
   display: flex;
   flex-direction: column;
   width: 100%;
-  height: 100%;
+  min-height: 450px;
+  height: 50vh;
+  font-size: 13px;
+  overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

+ 2 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -634,11 +634,13 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
             </Checkbox>
             Show Type
           </ToggleLabel>
+          {/*
           <ExpandButton onClick={this.toggleExpanded}>
             <i className="material-icons">
               {this.state.isExpanded ? "close_fullscreen" : "open_in_full"}
             </i>
           </ExpandButton>
+          */}
         </ButtonSection>
         <InfoPanel
           setSuppressDisplay={(x: boolean) =>

+ 20 - 6
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx

@@ -642,9 +642,7 @@ const DropdownAlt = styled(Dropdown)`
 `;
 
 const RangeWrapper = styled.div`
-  position: absolute;
-  top: 0;
-  right: 0;
+  float: right;
   font-weight: bold;
   width: 156px;
   margin-top: -8px;
@@ -681,11 +679,27 @@ const MetricsLabel = styled.div`
 
 const StyledMetricsSection = styled.div`
   width: 100%;
-  height: 100%;
+  min-height: 450px;
+  height: 50vh;
+  overflow: hidden;
   display: flex;
   flex-direction: column;
   position: relative;
   font-size: 13px;
-  border-radius: 5px;
-  overflow: hidden;
+  border-radius: 10px;
+  border: 1px solid #ffffff33;
+  padding: 18px 22px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;

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

@@ -382,7 +382,7 @@ const Refresh = styled.div`
 const LogTabs = styled.div`
   width: 100%;
   height: 25px;
-  background: #202227;
+  background: #121318;
   display: flex;
   flex-direction: row;
   align-items: center;
@@ -412,7 +412,7 @@ const LogStream = styled.div`
   flex: 1;
   float: right;
   height: 100%;
-  background: #202227;
+  background: #121318;
   user-select: text;
   max-width: 65%;
   overflow-y: auto;

+ 19 - 5
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -150,14 +150,28 @@ const TabWrapper = styled.div`
 `;
 
 const StyledStatusSection = styled.div`
-  width: 100%;
-  height: 100%;
-  position: relative;
-  font-size: 13px;
   padding: 0px;
   user-select: text;
-  border-radius: 5px;
   overflow: hidden;
+  width: 100%;
+  min-height: 450px;
+  height: 50vh;
+  font-size: 13px;
+  overflow: hidden;
+  border-radius: 10px;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
 `;
 
 const Wrapper = styled.div`

+ 2 - 2
dashboard/src/main/home/dashboard/ClusterList.tsx

@@ -322,8 +322,8 @@ const TemplateList = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   white-space: nowrap;

+ 9 - 35
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -12,6 +12,7 @@ import { RouteComponentProps, withRouter } from "react-router";
 import TabRegion from "components/TabRegion";
 import Provisioner from "../provisioner/Provisioner";
 import FormDebugger from "components/values-form/FormDebugger";
+import TitleSection from "components/TitleSection";
 
 import { pushQueryParams, pushFiltered } from "shared/routing";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
@@ -157,7 +158,7 @@ class Dashboard extends Component<PropsType, StateType> {
                       {currentProject && currentProject.name[0].toUpperCase()}
                     </Overlay>
                   </DashboardIcon>
-                  <Title>{currentProject && currentProject.name}</Title>
+                  {currentProject && currentProject.name}
                   {this.context.currentProject?.roles?.filter((obj: any) => {
                     return obj.user_id === this.context.user.userId;
                   })[0].kind === "admin" || (
@@ -169,6 +170,7 @@ class Dashboard extends Component<PropsType, StateType> {
                     </i>
                   )}
                 </TitleSection>
+                <Br />
 
                 <InfoSection>
                   <TopRow>
@@ -201,6 +203,11 @@ Dashboard.contextType = Context;
 
 export default withRouter(withAuth(Dashboard));
 
+const Br = styled.div`
+  width: 100%;
+  height: 1px;
+`;
+
 const DashboardWrapper = styled.div`
   padding-bottom: 100px;
 `;
@@ -288,6 +295,7 @@ const DashboardImage = styled.img`
 const DashboardIcon = styled.div`
   position: relative;
   height: 45px;
+  margin-right: 17px;
   width: 45px;
   border-radius: 5px;
   display: flex;
@@ -298,37 +306,3 @@ const DashboardIcon = styled.div`
     font-size: 22px;
   }
 `;
-
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 18px;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  height: 80px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding-left: 0px;
-
-  > i {
-    margin-left: 10px;
-    cursor: pointer;
-    font-size: 18px;
-    color: #858faaaa;
-    padding: 5px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-    margin-bottom: -3px;
-  }
-`;

+ 29 - 61
dashboard/src/main/home/integrations/IntegrationCategories.tsx

@@ -8,6 +8,7 @@ import { RouteComponentProps, withRouter } from "react-router";
 import IntegrationList from "./IntegrationList";
 import api from "shared/api";
 import { pushFiltered } from "shared/routing";
+import TitleSection from "components/TitleSection";
 
 type PropsType = RouteComponentProps & {
   category: string;
@@ -125,20 +126,16 @@ class IntegrationCategories extends Component<PropsType, StateType> {
       integrationList[currentCategory].buttonText;
     if (currentCategory !== "repo") {
       return (
-        <div>
-          <TitleSectionAlt>
-            <Flex>
-              <i
-                className="material-icons"
-                onClick={() =>
-                  pushFiltered(this.props, "/integrations", ["project_id"])
-                }
-              >
-                keyboard_backspace
-              </i>
-              <Icon src={icon && icon} />
-              <Title>{label}</Title>
-            </Flex>
+        <>
+          <Flex>
+            <TitleSection
+              handleNavBack={() =>
+                pushFiltered(this.props, "/integrations", ["project_id"])
+              }
+              icon={icon}
+            >
+              {label}
+            </TitleSection>
             <Button
               onClick={() =>
                 this.context.setCurrentModal("IntegrationsModal", {
@@ -155,9 +152,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               <i className="material-icons">add</i>
               {buttonText}
             </Button>
-          </TitleSectionAlt>
-
-          <LineBreak />
+          </Flex>
 
           <IntegrationList
             currentCategory={currentCategory}
@@ -168,24 +163,20 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               this.getIntegrationsForCategory(this.props.category)
             }
           />
-        </div>
+        </>
       );
     } else {
       return (
-        <div>
-          <TitleSectionAlt>
-            <Flex>
-              <i
-                className="material-icons"
-                onClick={() =>
-                  pushFiltered(this.props, "/integrations", ["project_id"])
-                }
-              >
-                keyboard_backspace
-              </i>
-              <Icon src={icon && icon} />
-              <Title>{label}</Title>
-            </Flex>
+        <>
+          <Flex>
+            <TitleSection
+              handleNavBack={() =>
+                pushFiltered(this.props, "/integrations", ["project_id"])
+              }
+              icon={icon}
+            >
+              {label}
+            </TitleSection>
             <Button
               onClick={() =>
                 window.open(
@@ -196,9 +187,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               <GHIcon />
               {buttonText}
             </Button>
-          </TitleSectionAlt>
-
-          <LineBreak />
+          </Flex>
 
           <IntegrationList
             currentCategory={currentCategory}
@@ -209,7 +198,7 @@ class IntegrationCategories extends Component<PropsType, StateType> {
               this.getIntegrationsForCategory(this.props.category)
             }
           />
-        </div>
+        </>
       );
     }
   };
@@ -228,6 +217,8 @@ const Icon = styled.img`
 const Flex = styled.div`
   display: flex;
   align-items: center;
+  margin-bottom: -20px;
+  justify-content: space-between;
 
   > i {
     cursor: pointer;
@@ -244,6 +235,7 @@ const Flex = styled.div`
 
 const Button = styled.div`
   height: 100%;
+  margin-top: -12px;
   background: #616feecc;
   :hover {
     background: #505edddd;
@@ -271,33 +263,9 @@ const Button = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  height: 40px;
-`;
-
-const TitleSectionAlt = styled(TitleSection)`
-  margin-left: -42px;
-  width: calc(100% + 42px);
-`;
-
 const LineBreak = styled.div`
   width: calc(100% - 0px);
   height: 2px;
   background: #ffffff20;
-  margin: 32px 0px 24px;
+  margin: 18px 0px 24px;
 `;

+ 19 - 40
dashboard/src/main/home/integrations/Integrations.tsx

@@ -8,6 +8,7 @@ import { pushFiltered } from "shared/routing";
 import CreateIntegrationForm from "./create-integration/CreateIntegrationForm";
 import IntegrationCategories from "./IntegrationCategories";
 import IntegrationList from "./IntegrationList";
+import TitleSection from "components/TitleSection";
 
 type PropsType = RouteComponentProps;
 
@@ -36,22 +37,17 @@ class Integrations extends Component<PropsType, StateType> {
               integrationList[integration] && integrationList[integration].icon;
             return (
               <div>
-                <TitleSectionAlt>
-                  <Flex>
-                    <i
-                      className="material-icons"
-                      onClick={() =>
-                        pushFiltered(this.props, `/integrations/${category}`, [
-                          "project_id",
-                        ])
-                      }
-                    >
-                      keyboard_backspace
-                    </i>
-                    <Icon src={icon && icon} />
-                    <Title>{integrationList[integration].label}</Title>
-                  </Flex>
-                </TitleSectionAlt>
+                <TitleSection
+                  icon={icon}
+                  handleNavBack={() =>
+                    pushFiltered(this.props, `/integrations/${category}`, [
+                      "project_id",
+                    ])
+                  }
+                >
+                  {integrationList[integration].label}
+                </TitleSection>
+                <Buffer />
                 <CreateIntegrationForm
                   integrationName={integration}
                   closeForm={() => {
@@ -77,9 +73,7 @@ class Integrations extends Component<PropsType, StateType> {
         />
         <Route>
           <div>
-            <TitleSection>
-              <Title>Integrations</Title>
-            </TitleSection>
+            <TitleSection>Integrations</TitleSection>
 
             <IntegrationList
               currentCategory={""}
@@ -99,6 +93,11 @@ class Integrations extends Component<PropsType, StateType> {
 
 export default withRouter(Integrations);
 
+const Buffer = styled.div`
+  width: 100%;
+  height: 10px;
+`;
+
 const Br = styled.div`
   width: 100%;
   height: 150px;
@@ -127,32 +126,12 @@ const Flex = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  height: 40px;
-`;
-
 const TitleSectionAlt = styled(TitleSection)`
   margin-left: -42px;
   width: calc(100% + 42px);
 `;
 
 const StyledIntegrations = styled.div`
-  width: calc(90% - 150px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 75px;
 `;

+ 3 - 34
dashboard/src/main/home/launch/Launch.tsx

@@ -10,6 +10,7 @@ import ExpandedTemplate from "./expanded-template/ExpandedTemplate";
 import Loading from "components/Loading";
 import LaunchFlow from "./launch-flow/LaunchFlow";
 import NoClusterPlaceholder from "../NoClusterPlaceholder";
+import TitleSection from "components/TitleSection";
 
 import { hardcodedNames } from "shared/hardcodedNameDict";
 import semver from "semver";
@@ -235,7 +236,7 @@ export default class Templates extends Component<PropsType, StateType> {
       return (
         <TemplatesWrapper>
           <TitleSection>
-            <Title>Launch</Title>
+            Launch
             <a href="https://docs.getporter.dev/docs/add-ons" target="_blank">
               <i className="material-icons">help_outline</i>
             </a>
@@ -399,39 +400,7 @@ const TemplateList = styled.div`
   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 15px;
-      color: #858faaaa;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const TemplatesWrapper = styled.div`
-  width: calc(90% - 130px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 75px;
 `;

+ 0 - 1040
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -1,1040 +0,0 @@
-import React, { Component } from "react";
-import styled from "styled-components";
-import randomWords from "random-words";
-import _ from "lodash";
-import { Context } from "shared/Context";
-import api from "shared/api";
-import { pushFiltered } from "shared/routing";
-import close from "assets/close.png";
-import { RouteComponentProps, withRouter } from "react-router";
-
-import {
-  ActionConfigType,
-  ChoiceType,
-  ClusterType,
-  StorageType,
-} from "shared/types";
-import Selector from "components/Selector";
-import ImageSelector from "components/image-selector/ImageSelector";
-import TabRegion from "components/TabRegion";
-import InputRow from "components/values-form/InputRow";
-import SaveButton from "components/SaveButton";
-import ActionConfEditor from "components/repo-selector/ActionConfEditor";
-import FormWrapper from "components/values-form/FormWrapper";
-import RadioSelector from "components/RadioSelector";
-import { isAlphanumeric } from "shared/common";
-
-type PropsType = RouteComponentProps & {
-  currentTemplate: any;
-  currentTab: string;
-  hideLaunch: () => void;
-  values: any;
-  form: any;
-  hideBackButton?: boolean;
-};
-
-type StateType = {
-  currentView: string;
-  clusterOptions: { label: string; value: string }[];
-  clusterMap: { [clusterId: string]: ClusterType };
-  saveValuesStatus: string | null;
-  selectedNamespace: string;
-  selectedCluster: string;
-  selectedClusterId: number;
-  selectedImageUrl: string | null;
-  sourceType: string;
-  selectedTag: string | null;
-  templateName: string;
-  tabOptions: ChoiceType[];
-  currentTab: string | null;
-  tabContents: any;
-  namespaceOptions: { label: string; value: string }[];
-  actionConfig: ActionConfigType;
-  procfileProcess: string;
-  branch: string;
-  repoType: string;
-  dockerfilePath: string | null;
-  procfilePath: string | null;
-  folderPath: string | null;
-  selectedRegistry: any | null;
-  env: any;
-  valuesToOverride: any | null;
-};
-
-const defaultActionConfig: ActionConfigType = {
-  git_repo: "",
-  image_repo_uri: "",
-  branch: "",
-  git_repo_id: 0,
-};
-
-class LaunchTemplate extends Component<PropsType, StateType> {
-  state = {
-    currentView: "repo",
-    clusterOptions: [] as { label: string; value: string }[],
-    clusterMap: {} as { [clusterId: string]: ClusterType },
-    saveValuesStatus: "" as string | null,
-    selectedCluster: this.context.currentCluster.name,
-    selectedClusterId: this.context.currentCluster.id,
-    selectedNamespace: "default",
-    selectedImageUrl: "" as string | null,
-    sourceType: "",
-    templateName: "",
-    selectedTag: "" as string | null,
-    tabOptions: [] as ChoiceType[],
-    currentTab: null as string | null,
-    tabContents: [] as any,
-    namespaceOptions: [] as { label: string; value: string }[],
-    actionConfig: { ...defaultActionConfig },
-    branch: "",
-    repoType: "",
-    dockerfilePath: null as string | null,
-    procfileProcess: null as string | null,
-    procfilePath: null as string | null,
-    folderPath: null as string | null,
-    selectedRegistry: null as any | null,
-    env: {},
-    valuesToOverride: null as any | null,
-  };
-
-  createGHAction = (chartName: string, chartNamespace: string) => {
-    let { currentProject, currentCluster } = this.context;
-    let { actionConfig } = this.state;
-    let imageRepoUri = `${this.state.selectedRegistry.url}/${chartName}-${chartNamespace}`;
-
-    // DockerHub registry integration is per repo
-    if (this.state.selectedRegistry.service === "dockerhub") {
-      imageRepoUri = this.state.selectedRegistry.url;
-    }
-
-    api
-      .createGHAction(
-        "<token>",
-        {
-          git_repo: actionConfig.git_repo,
-          git_branch: this.state.branch,
-          registry_id: this.state.selectedRegistry.id,
-          dockerfile_path: this.state.dockerfilePath,
-          folder_path: this.state.folderPath,
-          image_repo_uri: imageRepoUri,
-          git_repo_id: actionConfig.git_repo_id,
-          env: this.state.env,
-        },
-        {
-          project_id: currentProject.id,
-          CLUSTER_ID: currentCluster.id,
-          RELEASE_NAME: chartName,
-          RELEASE_NAMESPACE: chartNamespace,
-        }
-      )
-      .then((res) => console.log(""))
-      .catch(console.log);
-  };
-
-  onSubmitAddon = (wildcard?: any) => {
-    let { currentCluster, currentProject, setCurrentError } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    let values = {};
-    for (let key in wildcard) {
-      _.set(values, key, wildcard[key]);
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.ADDON_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // TODO: redirect to appropriate cluster if not current context
-          let dst =
-            this.props.currentTemplate.name === "job"
-              ? "/jobs"
-              : "/applications";
-          setTimeout(() => {
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 500);
-          window.analytics.track("Deployed Add-on", {
-            name: this.props.currentTemplate.name,
-            namespace: this.state.selectedNamespace,
-            values: values,
-          });
-        });
-      })
-      .catch((err) => {
-        let parsedErr =
-          err?.response?.data?.errors && err.response.data.errors[0];
-        if (parsedErr) {
-          err = parsedErr;
-        }
-
-        this.setState({
-          saveValuesStatus: parsedErr,
-        });
-
-        setCurrentError(err);
-
-        window.analytics.track("Failed to Deploy Add-on", {
-          name: this.props.currentTemplate.name,
-          namespace: this.state.selectedNamespace,
-          values: values,
-          error: err,
-        });
-      });
-  };
-
-  onSubmit = async (rawValues: any) => {
-    let { currentCluster, currentProject } = this.context;
-    let name =
-      this.state.templateName || randomWords({ exactly: 3, join: "-" });
-    this.setState({ saveValuesStatus: "loading" });
-
-    // Convert dotted keys to nested objects
-    let values: any = {};
-    for (let key in rawValues) {
-      _.set(values, key, rawValues[key]);
-    }
-
-    let imageUrl = this.state.selectedImageUrl;
-    let tag = this.state.selectedTag;
-
-    if (this.state.selectedImageUrl.includes(":")) {
-      let splits = this.state.selectedImageUrl.split(":");
-      imageUrl = splits[0];
-      tag = splits[1];
-    } else if (!tag) {
-      tag = "latest";
-    }
-
-    if (this.state.sourceType === "repo") {
-      if (this.props.currentTemplate?.name == "job") {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter-job";
-        tag = "latest";
-      } else {
-        imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter";
-        tag = "latest";
-      }
-    }
-
-    let provider;
-    switch (currentCluster.service) {
-      case "eks":
-        provider = "aws";
-        break;
-      case "gke":
-        provider = "gcp";
-        break;
-      case "doks":
-        provider = "digitalocean";
-        break;
-      default:
-        provider = "";
-    }
-
-    // don't overwrite for templates that already have a source (i.e. non-Docker templates)
-    if (imageUrl && tag) {
-      _.set(values, "image.repository", imageUrl);
-      _.set(values, "image.tag", tag);
-    }
-
-    _.set(values, "ingress.provider", provider);
-    var url: string;
-    // check if template is docker and create external domain if necessary
-    if (this.props.currentTemplate.name == "web") {
-      if (values?.ingress?.enabled && !values?.ingress?.custom_domain) {
-        url = await new Promise((resolve, reject) => {
-          api
-            .createSubdomain(
-              "<token>",
-              {
-                release_name: name,
-              },
-              {
-                id: currentProject.id,
-                cluster_id: currentCluster.id,
-              }
-            )
-            .then((res) => {
-              resolve(res.data?.external_url);
-            })
-            .catch((err) => {
-              this.setState({ saveValuesStatus: "error" });
-            });
-        });
-
-        values.ingress.porter_hosts = [url];
-      }
-    }
-
-    api
-      .deployTemplate(
-        "<token>",
-        {
-          templateName: this.props.currentTemplate.name,
-          imageURL: this.state.selectedImageUrl,
-          storage: StorageType.Secret,
-          formValues: values,
-          namespace: this.state.selectedNamespace,
-          name,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-          name: this.props.currentTemplate.name.toLowerCase().trim(),
-          version: this.props.currentTemplate?.currentVersion || "latest",
-          repo_url: process.env.APPLICATION_CHART_REPO_URL,
-        }
-      )
-      .then((_) => {
-        console.log("Deployed template.");
-        if (this.state.sourceType === "repo") {
-          console.log("Creating GHA");
-          this.createGHAction(name, this.state.selectedNamespace);
-        }
-        // this.props.setCurrentView('cluster-dashboard');
-        this.setState({ saveValuesStatus: "successful" }, () => {
-          // redirect to dashboard with namespace
-          setTimeout(() => {
-            let dst =
-              this.props.currentTemplate.name === "job"
-                ? "/jobs"
-                : "/applications";
-            pushFiltered(this.props, dst, ["project_id"], {
-              cluster: currentCluster.name,
-            });
-          }, 1000);
-        });
-      })
-      .catch((err) => {
-        this.setState({ saveValuesStatus: "error" });
-      });
-  };
-
-  submitIsDisabled = () => {
-    let {
-      templateName,
-      sourceType,
-      selectedImageUrl,
-      dockerfilePath,
-      folderPath,
-    } = this.state;
-
-    // Allow if name is invalid
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return true;
-    }
-
-    if (this.state.saveValuesStatus == "loading") {
-      return true;
-    }
-
-    if (this.props.form?.hasSource) {
-      // Allow if source type is registry and image URL is specified
-      if (sourceType === "registry" && selectedImageUrl) {
-        return false;
-      }
-
-      // Allow if source type is repo and dockerfile or folder path is set
-      if (sourceType === "repo" && (dockerfilePath || folderPath)) {
-        return !this.state.selectedRegistry;
-      }
-
-      return true;
-    } else {
-      return false;
-    }
-  };
-
-  getStatus = () => {
-    let {
-      selectedRegistry,
-      sourceType,
-      dockerfilePath,
-      folderPath,
-      procfilePath,
-    } = this.state;
-
-    if (!this.submitIsDisabled()) {
-      return this.state.saveValuesStatus;
-    }
-
-    // handle exception when deploy process is on loading
-    if (this.state.saveValuesStatus === "loading") {
-      return "loading";
-    }
-
-    if (
-      sourceType === "repo" &&
-      (dockerfilePath || folderPath) &&
-      !selectedRegistry
-    ) {
-      return "A connected container registry is required";
-    }
-    let { templateName } = this.state;
-    if (templateName.length > 0 && !isAlphanumeric(templateName)) {
-      return "Template name contains illegal characters";
-    }
-    return "No application source specified";
-  };
-
-  componentDidMount() {
-    if (this.props.currentTemplate.name !== "docker") {
-      this.setState({ saveValuesStatus: "" });
-    }
-    // Retrieve tab options
-    let tabOptions = [] as ChoiceType[];
-    this.props.form?.tabs.map((tab: any, i: number) => {
-      if (tab.context.type === "helm/values") {
-        tabOptions.push({ value: tab.name, label: tab.label });
-      }
-    });
-
-    this.setState({
-      tabOptions,
-      currentTab: tabOptions[0] && tabOptions[0]["value"],
-    });
-
-    // TODO: query with selected filter once implemented
-    let { currentProject, currentCluster } = this.context;
-    api.getClusters("<token>", {}, { id: currentProject.id }).then((res) => {
-      if (res.data) {
-        let clusterOptions: { label: string; value: string }[] = [];
-        let clusterMap: { [clusterId: string]: ClusterType } = {};
-        res.data.forEach((cluster: ClusterType, i: number) => {
-          clusterOptions.push({ label: cluster.name, value: cluster.name });
-          clusterMap[cluster.name] = cluster;
-        });
-        if (res.data.length > 0) {
-          this.setState({ clusterOptions, clusterMap });
-        }
-      }
-    });
-
-    this.updateNamespaces(currentCluster.id);
-  }
-
-  updateNamespaces = (id: number) => {
-    let { currentProject } = this.context;
-    api
-      .getNamespaces(
-        "<token>",
-        {
-          cluster_id: id,
-        },
-        { id: currentProject.id }
-      )
-      .then((res) => {
-        if (res.data) {
-          const availableNamespaces = res.data.items.filter(
-            (namespace: any) => {
-              return namespace.status.phase !== "Terminating";
-            }
-          );
-          const namespaceOptions = availableNamespaces.map(
-            (x: { metadata: { name: string } }) => {
-              return { label: x.metadata.name, value: x.metadata.name };
-            }
-          );
-          if (availableNamespaces.length > 0) {
-            this.setState({ namespaceOptions });
-          }
-        }
-      })
-      .catch(console.log);
-  };
-
-  setSelectedImageUrl = (x: string) => {
-    this.setState({ selectedImageUrl: x });
-  };
-
-  renderIcon = (icon: string) => {
-    if (icon) {
-      return <Icon src={icon} />;
-    }
-
-    return (
-      <Polymer>
-        <i className="material-icons">layers</i>
-      </Polymer>
-    );
-  };
-
-  renderSettingsRegion = () => {
-    if (this.state.tabOptions.length > 0) {
-      return (
-        <>
-          <Heading>Additional Settings</Heading>
-          <Subtitle>
-            Configure additional settings for this template. (Optional)
-          </Subtitle>
-          <FormWrapper
-            formData={this.props.form}
-            saveValuesStatus={this.state.saveValuesStatus}
-            valuesToOverride={this.state.valuesToOverride}
-            clearValuesToOverride={() =>
-              this.setState({ valuesToOverride: null })
-            }
-            externalValues={{
-              namespace: this.state.selectedNamespace,
-              clusterId: this.context.currentCluster.id,
-              isLaunch: true,
-            }}
-            onSubmit={
-              this.props.currentTab === "docker"
-                ? this.onSubmit
-                : this.onSubmitAddon
-            }
-          />
-        </>
-      );
-    } else {
-      return (
-        <Wrapper>
-          <Placeholder>
-            To configure this chart through Porter,
-            <Link
-              target="_blank"
-              href="https://docs.getporter.dev/docs/add-ons"
-            >
-              refer to our docs
-            </Link>
-            .
-          </Placeholder>
-          <SaveButton
-            text="Deploy"
-            onClick={this.onSubmitAddon}
-            status={this.state.saveValuesStatus}
-            makeFlush={true}
-          />
-        </Wrapper>
-      );
-    }
-  };
-
-  // Display if current template uses source (image or repo)
-  renderSourceSelectorContent = () => {
-    let { capabilities } = this.context;
-
-    if (this.state.sourceType === "") {
-      return (
-        <BlockList>
-          {capabilities.github && (
-            <Block
-              onClick={() => {
-                this.setState({ sourceType: "repo" });
-              }}
-            >
-              <BlockIcon src="https://3.bp.blogspot.com/-xhNpNJJyQhk/XIe4GY78RQI/AAAAAAAAItc/ouueFUj2Hqo5dntmnKqEaBJR4KQ4Q2K3ACK4BGAYYCw/s1600/logo%2Bgit%2Bicon.png" />
-              <BlockTitle>Git Repository</BlockTitle>
-              <BlockDescription>
-                Deploy using source from a Git repo.
-              </BlockDescription>
-            </Block>
-          )}
-          <Block
-            onClick={() => {
-              this.setState({ sourceType: "registry" });
-            }}
-          >
-            <BlockIcon src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
-            <BlockTitle>Docker Registry</BlockTitle>
-            <BlockDescription>
-              Deploy a container from an image registry.
-            </BlockDescription>
-          </Block>
-        </BlockList>
-      );
-    } else if (this.state.sourceType === "registry") {
-      return (
-        <StyledSourceBox>
-          <CloseButton
-            onClick={() =>
-              this.setState({
-                sourceType: "",
-                selectedImageUrl: "",
-                selectedTag: "",
-              })
-            }
-          >
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Specify the container image you would like to connect to this
-            template.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/registry", [
-                  "project_id",
-                ])
-              }
-            >
-              Manage Docker registries
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ImageSelector
-            selectedTag={this.state.selectedTag}
-            selectedImageUrl={this.state.selectedImageUrl}
-            setSelectedImageUrl={this.setSelectedImageUrl}
-            setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
-            forceExpanded={true}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    } else {
-      return (
-        <StyledSourceBox>
-          <CloseButton onClick={() => this.setState({ sourceType: "" })}>
-            <CloseButtonImg src={close} />
-          </CloseButton>
-          <Subtitle>
-            Provide a repo folder to use as source.
-            <Highlight
-              onClick={() =>
-                pushFiltered(this.props, "/integrations/repo", ["project_id"])
-              }
-            >
-              Manage Git repos
-            </Highlight>
-            <Required>*</Required>
-          </Subtitle>
-          <DarkMatter antiHeight="-4px" />
-          <ActionConfEditor
-            actionConfig={this.state.actionConfig}
-            branch={this.state.branch}
-            setActionConfig={(actionConfig: ActionConfigType) =>
-              this.setState({ actionConfig }, () => {
-                this.setSelectedImageUrl(
-                  this.state.actionConfig.image_repo_uri
-                );
-              })
-            }
-            procfileProcess={this.state.procfileProcess}
-            setProcfileProcess={(procfileProcess: string) =>
-              this.setState({
-                procfileProcess,
-                valuesToOverride: {
-                  "container.command": {
-                    value: procfileProcess || "",
-                  },
-                  showStartCommand: {
-                    value: !procfileProcess,
-                  },
-                },
-              })
-            }
-            setBranch={(branch: string) => this.setState({ branch })}
-            setDockerfilePath={(x: string) =>
-              this.setState({ dockerfilePath: x })
-            }
-            setProcfilePath={(x: string) => {
-              this.setState({ procfilePath: x });
-            }}
-            procfilePath={this.state.procfilePath}
-            dockerfilePath={this.state.dockerfilePath}
-            folderPath={this.state.folderPath}
-            setFolderPath={(x: string) => this.setState({ folderPath: x })}
-            reset={() => {
-              this.setState({
-                actionConfig: { ...defaultActionConfig },
-                branch: "",
-                dockerfilePath: null,
-                folderPath: null,
-              });
-            }}
-            setSelectedRegistry={(x: any) => {
-              this.setState({ selectedRegistry: x });
-            }}
-            selectedRegistry={this.state.selectedRegistry}
-          />
-          <br />
-        </StyledSourceBox>
-      );
-    }
-  };
-
-  renderSourceSelector = () => {
-    return (
-      <>
-        <Heading>Deployment Method</Heading>
-        <Subtitle>
-          Choose the deployment method you would like to use for this
-          application.
-          <Required>*</Required>
-        </Subtitle>
-        {this.renderSourceSelectorContent()}
-      </>
-    );
-  };
-
-  render() {
-    console.log("RENDERING");
-    let { name, icon } = this.props.currentTemplate;
-    let { currentTemplate } = this.props;
-
-    return (
-      <StyledLaunchTemplate>
-        <HeaderSection>
-          <i className="material-icons" onClick={this.props.hideLaunch}>
-            keyboard_backspace
-          </i>
-          {icon ? this.renderIcon(icon) : this.renderIcon(currentTemplate.icon)}
-          <Title>{name}</Title>
-        </HeaderSection>
-        <DarkMatter antiHeight="-13px" />
-        <Heading isAtTop={true}>Name</Heading>
-        <Subtitle>
-          Randomly generated if left blank.
-          <Warning
-            highlight={
-              !isAlphanumeric(this.state.templateName) &&
-              this.state.templateName !== ""
-            }
-          >
-            Lowercase letters, numbers, and "-" only.
-          </Warning>
-        </Subtitle>
-        <DarkMatter antiHeight="-29px" />
-        <InputRow
-          type="text"
-          value={this.state.templateName}
-          setValue={(x: string) => this.setState({ templateName: x })}
-          placeholder="ex: doctor-scientist"
-          width="100%"
-        />
-
-        {this.props.form?.hasSource && this.renderSourceSelector()}
-
-        <Heading>Destination</Heading>
-        <Subtitle>
-          Specify the cluster and namespace you would like to deploy your
-          application to.
-        </Subtitle>
-        <ClusterSection>
-          <ClusterLabel>
-            <i className="material-icons">device_hub</i>Cluster
-          </ClusterLabel>
-          <Selector
-            activeValue={this.state.selectedCluster}
-            setActiveValue={(cluster: string) => {
-              this.context.setCurrentCluster(this.state.clusterMap[cluster]);
-              this.updateNamespaces(this.state.clusterMap[cluster].id);
-              this.setState({
-                selectedCluster: cluster,
-                selectedClusterId: this.state.clusterMap[cluster].id,
-              });
-            }}
-            options={this.state.clusterOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-          <NamespaceLabel>
-            <i className="material-icons">view_list</i>Namespace
-          </NamespaceLabel>
-          <Selector
-            key={"namespace"}
-            activeValue={this.state.selectedNamespace}
-            setActiveValue={(namespace: string) =>
-              this.setState({ selectedNamespace: namespace })
-            }
-            addButton={true}
-            options={this.state.namespaceOptions}
-            width="250px"
-            dropdownWidth="335px"
-            closeOverlay={true}
-          />
-        </ClusterSection>
-        {this.renderSettingsRegion()}
-      </StyledLaunchTemplate>
-    );
-  }
-}
-
-LaunchTemplate.contextType = Context;
-export default withRouter(LaunchTemplate);
-
-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 BlockIcon = styled.img<{ bw?: boolean }>`
-  height: 38px;
-  padding: 2px;
-  margin-top: 30px;
-  margin-bottom: 15px;
-  filter: ${(props) => (props.bw ? "grayscale(1)" : "")};
-`;
-
-const BlockDescription = styled.div`
-  margin-bottom: 12px;
-  color: #ffffff66;
-  text-align: center;
-  font-weight: default;
-  font-size: 13px;
-  padding: 0px 25px;
-  height: 2.4em;
-  font-size: 12px;
-  display: -webkit-box;
-  overflow: hidden;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-`;
-
-const BlockTitle = styled.div`
-  margin-bottom: 12px;
-  width: 80%;
-  text-align: center;
-  font-size: 14px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const Block = styled.div<{ disabled?: boolean }>`
-  align-items: center;
-  user-select: none;
-  border-radius: 5px;
-  display: flex;
-  font-size: 13px;
-  overflow: hidden;
-  font-weight: 500;
-  padding: 3px 0px 12px;
-  flex-direction: column;
-  align-item: center;
-  justify-content: space-between;
-  height: 170px;
-  cursor: ${(props) => (props.disabled ? "" : "pointer")};
-  color: #ffffff;
-  position: relative;
-  background: #26282f;
-  box-shadow: 0 3px 5px 0px #00000022;
-  :hover {
-    background: ${(props) => (props.disabled ? "" : "#ffffff11")};
-  }
-
-  animation: fadeIn 0.3s 0s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const BlockList = styled.div`
-  overflow: visible;
-  margin-top: 6px;
-  margin-bottom: 27px;
-  display: grid;
-  grid-column-gap: 25px;
-  grid-row-gap: 25px;
-  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-`;
-
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  margin-left: 10px;
-  border-radius: 2px;
-  color: #ffffff;
-`;
-
-const HeaderSection = styled.div`
-  display: flex;
-  align-items: center;
-  margin-bottom: 30px;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    padding: 3px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-`;
-
-const Heading = styled.div<{ isAtTop?: boolean }>`
-  color: white;
-  font-weight: 500;
-  font-size: 16px;
-  margin-bottom: 5px;
-  margin-top: ${(props) => (props.isAtTop ? "10px" : "30px")};
-  display: flex;
-  align-items: center;
-`;
-
-const Warning = styled.span<{ highlight: boolean; makeFlush?: boolean }>`
-  color: ${(props) => (props.highlight ? "#f5cb42" : "")};
-  margin-left: ${(props) => (props.makeFlush ? "" : "5px")};
-`;
-
-const Required = styled.div`
-  margin-left: 8px;
-  color: #fc4976;
-  display: inline-block;
-`;
-
-const Link = styled.a`
-  margin-left: 5px;
-`;
-
-const Wrapper = styled.div`
-  width: 100%;
-  position: relative;
-  padding-top: 20px;
-  padding-bottom: 70px;
-`;
-
-const Placeholder = styled.div`
-  width: 100%;
-  height: 200px;
-  background: #ffffff11;
-  border: 1px solid #ffffff44;
-  border-radius: 5px;
-  color: #aaaabb;
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-`;
-
-const DarkMatter = styled.div<{ antiHeight?: string }>`
-  width: 100%;
-  margin-top: ${(props) => props.antiHeight || "-15px"};
-`;
-
-const Subtitle = styled.div`
-  padding: 11px 0px 16px;
-  font-family: "Work Sans", sans-serif;
-  font-size: 13px;
-  color: #aaaabb;
-  line-height: 1.6em;
-  display: flex;
-  align-items: center;
-`;
-
-const ClusterLabel = styled.div`
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const NamespaceLabel = styled.div`
-  margin-left: 15px;
-  margin-right: 10px;
-  display: flex;
-  align-items: center;
-  > i {
-    font-size: 16px;
-    margin-right: 6px;
-  }
-`;
-
-const Icon = styled.img`
-  width: 21px;
-  margin-right: 6px;
-  margin-left: 10px;
-`;
-
-const Polymer = styled.div`
-  margin-bottom: -3px;
-
-  > i {
-    color: ${(props) => props.theme.containerIcon};
-    font-size: 18px;
-    margin-right: 10px;
-  }
-`;
-
-const ClusterSection = styled.div`
-  display: flex;
-  align-items: center;
-  color: #ffffff;
-  font-family: "Work Sans", sans-serif;
-  font-size: 14px;
-  margin-top: 2px;
-  font-weight: 500;
-  margin-bottom: 32px;
-
-  > i {
-    font-size: 25px;
-    color: #ffffff44;
-    margin-right: 13px;
-  }
-`;
-
-const StyledLaunchTemplate = styled.div`
-  width: 100%;
-  padding-bottom: 150px;
-`;
-
-const Highlight = styled.div`
-  color: #8590ff;
-  text-decoration: none;
-  margin-left: 5px;
-  cursor: pointer;
-`;
-
-const StyledSourceBox = styled.div`
-  width: 100%;
-  height: 100%;
-  background: #ffffff11;
-  color: #ffffff;
-  padding: 14px 35px 20px;
-  position: relative;
-  border-radius: 5px;
-  font-size: 13px;
-  margin-top: 6px;
-  overflow: auto;
-  margin-bottom: 25px;
-`;

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

@@ -318,8 +318,8 @@ const Polymer = styled.div`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   margin-left: 10px;
   border-radius: 2px;

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

@@ -11,6 +11,7 @@ import { pushFiltered } from "shared/routing";
 import { hardcodedNames } from "shared/hardcodedNameDict";
 import SourcePage from "./SourcePage";
 import SettingsPage from "./SettingsPage";
+import TitleSection from "components/TitleSection";
 
 import {
   PorterTemplate,
@@ -455,14 +456,9 @@ class LaunchFlow extends Component<PropsType, StateType> {
 
     return (
       <StyledLaunchFlow>
-        <TitleSection>
-          <i className="material-icons" onClick={this.props.hideLaunchFlow}>
-            keyboard_backspace
-          </i>
+        <TitleSection handleNavBack={this.props.hideLaunchFlow}>
           {this.renderIcon()}
-          <Title>
-            New {name} {currentTab === "porter" ? null : "Instance"}
-          </Title>
+          New {name} {currentTab === "porter" ? null : "Instance"}
         </TitleSection>
         {this.renderCurrentPage()}
         <Br />
@@ -509,54 +505,8 @@ const Polymer = styled.div`
   }
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > i {
-    cursor: pointer;
-    font-size 24px;
-    color: #969Fbbaa;
-    margin-right: 10px;
-    padding: 3px;
-    margin-left: 0px;
-    border-radius: 100px;
-    :hover {
-      background: #ffffff11;
-    }
-  }
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 18px;
-      color: #858faaaa;
-      cursor: pointer;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const StyledLaunchFlow = styled.div`
   width: calc(90% - 130px);
   min-width: 300px;
-  padding-top: 20px;
-  margin-top: calc(50vh - 340px);
+  margin-top: calc(50vh - 380px);
 `;

+ 4 - 28
dashboard/src/main/home/new-project/NewProject.tsx

@@ -8,6 +8,7 @@ import { isAlphanumeric } from "shared/common";
 import InputRow from "components/values-form/InputRow";
 import Helper from "components/values-form/Helper";
 import ProvisionerSettings from "../provisioner/ProvisionerSettings";
+import TitleSection from "components/TitleSection";
 
 type PropsType = {};
 
@@ -27,9 +28,7 @@ export default class NewProject extends Component<PropsType, StateType> {
     let { projectName } = this.state;
     return (
       <StyledNewProject>
-        <TitleSection>
-          <Title>New Project</Title>
-        </TitleSection>
+        <TitleSection>New Project</TitleSection>
         <Helper>
           Project name
           <Warning
@@ -130,8 +129,8 @@ const Warning = styled.span`
 `;
 
 const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
+  font-size: 20px;
+  font-weight: 500;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   white-space: nowrap;
@@ -139,32 +138,9 @@ const Title = styled.div`
   text-overflow: ellipsis;
 `;
 
-const TitleSection = styled.div`
-  margin-bottom: 20px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-
-  > a {
-    > i {
-      display: flex;
-      align-items: center;
-      margin-bottom: -2px;
-      font-size: 18px;
-      margin-left: 18px;
-      color: #858faaaa;
-      cursor: pointer;
-      :hover {
-        color: #aaaabb;
-      }
-    }
-  }
-`;
-
 const StyledNewProject = styled.div`
   width: calc(90% - 130px);
   min-width: 300px;
   position: relative;
-  padding-top: 50px;
   margin-top: calc(50vh - 340px);
 `;

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

@@ -7,6 +7,7 @@ import InvitePage from "./InviteList";
 import TabRegion from "components/TabRegion";
 import Heading from "components/values-form/Heading";
 import Helper from "components/values-form/Helper";
+import TitleSection from "components/TitleSection";
 import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 
 type PropsType = WithAuthProps & {};
@@ -91,9 +92,7 @@ class ProjectSettings extends Component<PropsType, StateType> {
   render() {
     return (
       <StyledProjectSettings>
-        <TitleSection>
-          <Title>Project Settings</Title>
-        </TitleSection>
+        <TitleSection>Project Settings</TitleSection>
         <TabRegion
           currentTab={this.state.currentTab}
           setCurrentTab={(x: string) => this.setState({ currentTab: x })}
@@ -117,28 +116,9 @@ const Warning = styled.div`
   margin-bottom: 20px;
 `;
 
-const Title = styled.div`
-  font-size: 24px;
-  font-weight: 600;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
-const TitleSection = styled.div`
-  margin-bottom: 13px;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  height: 40px;
-`;
-
 const StyledProjectSettings = styled.div`
-  width: calc(90% - 130px);
+  width: 83%;
   min-width: 300px;
-  padding-top: 70px;
   height: 100vh;
 `;