소스 검색

demote cluster dashboard

Justin Rhee 3 년 전
부모
커밋
63de892394
26개의 변경된 파일2019개의 추가작업 그리고 283개의 파일을 삭제
  1. 15 0
      dashboard/src/assets/Panopticon.html
  2. BIN
      dashboard/src/assets/Panopticon_files/52158967ad80feb4ca49c879aed820f1.png
  3. BIN
      dashboard/src/assets/Panopticon_files/52ea44b6f8289c93959942f21c89b93b.png
  4. 3 0
      dashboard/src/assets/Panopticon_files/6976476a375f71fe22ad3c7a9614d835.svg
  5. 5 0
      dashboard/src/assets/Panopticon_files/6db4ee3bde3b6a2505c0d45a3c85286e.svg
  6. BIN
      dashboard/src/assets/Panopticon_files/7bbfaf510f63aaf329553855208de8cc.png
  7. 8 0
      dashboard/src/assets/Panopticon_files/900408bca12da0e4729754147378903f.svg
  8. 4 0
      dashboard/src/assets/Panopticon_files/92a51629c794fa4a2aca047cf1e72f82.svg
  9. 3 0
      dashboard/src/assets/Panopticon_files/bf5fa4562595972e72df3820e9e96ca3.svg
  10. 1007 0
      dashboard/src/assets/Panopticon_files/bundle.js
  11. 6 0
      dashboard/src/assets/Panopticon_files/c3df6986b9c2399109961f5789a46184.svg
  12. 72 0
      dashboard/src/assets/Panopticon_files/css
  13. 5 0
      dashboard/src/assets/Panopticon_files/d05601a92074786f925f34883d742137.svg
  14. 4 0
      dashboard/src/assets/Panopticon_files/d165e41d185d4ff0eaccc5b0a5ac1366.svg
  15. 0 0
      dashboard/src/assets/Panopticon_files/devicon.min.css
  16. BIN
      dashboard/src/assets/Panopticon_files/e9208ae20def7ffa11cd7e1eb9d3812b.png
  17. 69 0
      dashboard/src/assets/Panopticon_files/icon
  18. 0 0
      dashboard/src/assets/Panopticon_files/katex.min.css
  19. 417 0
      dashboard/src/assets/Panopticon_files/main.883c79fc6a23f966494b.hot-update.js
  20. 8 0
      dashboard/src/assets/cluster.svg
  21. 2 2
      dashboard/src/components/form-components/CheckboxRow.tsx
  22. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx
  23. 143 263
      dashboard/src/main/home/sidebar/ClusterSection.tsx
  24. 225 0
      dashboard/src/main/home/sidebar/Clusters.tsx
  25. 2 2
      dashboard/src/main/home/sidebar/ProjectSection.tsx
  26. 20 15
      dashboard/src/main/home/sidebar/Sidebar.tsx

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 15 - 0
dashboard/src/assets/Panopticon.html


BIN
dashboard/src/assets/Panopticon_files/52158967ad80feb4ca49c879aed820f1.png


BIN
dashboard/src/assets/Panopticon_files/52ea44b6f8289c93959942f21c89b93b.png


+ 3 - 0
dashboard/src/assets/Panopticon_files/6976476a375f71fe22ad3c7a9614d835.svg

@@ -0,0 +1,3 @@
+<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8.15722 19.7714V16.7047C8.1572 15.9246 8.79312 15.2908 9.58101 15.2856H12.4671C13.2587 15.2856 13.9005 15.9209 13.9005 16.7047V16.7047V19.7809C13.9003 20.4432 14.4343 20.9845 15.103 21H17.0271C18.9451 21 20.5 19.4607 20.5 17.5618V17.5618V8.83784C20.4898 8.09083 20.1355 7.38935 19.538 6.93303L12.9577 1.6853C11.8049 0.771566 10.1662 0.771566 9.01342 1.6853L2.46203 6.94256C1.86226 7.39702 1.50739 8.09967 1.5 8.84736V17.5618C1.5 19.4607 3.05488 21 4.97291 21H6.89696C7.58235 21 8.13797 20.4499 8.13797 19.7714V19.7714" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
dashboard/src/assets/Panopticon_files/6db4ee3bde3b6a2505c0d45a3c85286e.svg

@@ -0,0 +1,5 @@
+<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12.1642 16.7638V11.4242" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.1642 7.59037H12.1762" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.414 1H6.93896C3.28858 1 1 3.58462 1 7.24225V17.1119C1 20.7695 3.27771 23.3542 6.93896 23.3542H17.4128C21.0753 23.3542 23.3542 20.7695 23.3542 17.1119V7.24225C23.3542 3.58462 21.0753 1 17.414 1Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

BIN
dashboard/src/assets/Panopticon_files/7bbfaf510f63aaf329553855208de8cc.png


+ 8 - 0
dashboard/src/assets/Panopticon_files/900408bca12da0e4729754147378903f.svg

@@ -0,0 +1,8 @@
+<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.207 12.4403C16.8094 12.4403 18.1092 11.1414 18.1092 9.53907C18.1092 7.93673 16.8094 6.63782 15.207 6.63782" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.90217 12.4403C2.29983 12.4403 1 11.1414 1 9.53907C1 7.93673 2.29983 6.63782 3.90217 6.63782" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.54993 13.4133C7.4086 13.4133 5.69168 11.6964 5.69168 9.55417C5.69168 7.41284 7.4086 5.69592 9.54993 5.69592C11.6913 5.69592 13.4082 7.41284 13.4082 9.55417C13.4082 11.6964 11.6913 13.4133 9.54993 13.4133Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.66895 15.207C6.66895 16.8094 7.96787 18.1092 9.5702 18.1092C11.1725 18.1092 12.4715 16.8094 12.4715 15.207" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.66895 3.90217C6.66895 2.29983 7.96787 1 9.5702 1C11.1725 1 12.4715 2.29983 12.4715 3.90217" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.69591 9.54996C5.69591 7.40863 7.41283 5.69171 9.55508 5.69171C11.6964 5.69171 13.4133 7.40863 13.4133 9.54996C13.4133 11.6913 11.6964 13.4082 9.55508 13.4082C7.41283 13.4082 5.69591 11.6913 5.69591 9.54996Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
dashboard/src/assets/Panopticon_files/92a51629c794fa4a2aca047cf1e72f82.svg

@@ -0,0 +1,4 @@
+<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M17.3164 77.6135C17.2687 77.6135 17.2247 77.6135 17.1734 77.6098C16.0184 77.5511 14.8854 77.3018 13.8074 76.8655C8.50171 74.7095 5.94237 68.6485 8.09471 63.3465L34.9384 16.3178C35.8624 14.6458 37.263 13.2451 38.9717 12.2991C43.9767 9.52715 50.3054 11.3495 53.0737 16.3508L79.7414 63.0201C80.3354 64.4171 80.5884 65.5538 80.6507 66.7125C80.7937 69.4845 79.8477 72.1428 77.9924 74.1998C76.137 76.2568 73.5887 77.4705 70.8204 77.6098L17.5804 77.6135H17.3164Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M40.79 36.743C40.79 34.9757 42.231 33.5347 43.9984 33.5347C45.7657 33.5347 47.2067 34.9757 47.2067 36.743V47.1123C47.2067 48.8833 45.7657 50.3207 43.9984 50.3207C42.231 50.3207 40.79 48.8833 40.79 47.1123V36.743ZM40.79 59.6564C40.79 57.878 42.231 56.4297 43.9984 56.4297C45.7657 56.4297 47.2067 57.8597 47.2067 59.616C47.2067 61.4237 45.7657 62.8647 43.9984 62.8647C42.231 62.8647 40.79 61.4237 40.79 59.6564Z" fill="white"/>
+</svg>

+ 3 - 0
dashboard/src/assets/Panopticon_files/bf5fa4562595972e72df3820e9e96ca3.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="10" viewBox="0 0 16 10" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15 1.5L8 8.5L1 1.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1007 - 0
dashboard/src/assets/Panopticon_files/bundle.js


+ 6 - 0
dashboard/src/assets/Panopticon_files/c3df6986b9c2399109961f5789a46184.svg

@@ -0,0 +1,6 @@
+<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2855 1H18.5521C19.9036 1 21 2.1059 21 3.47018V6.7641C21 8.12735 19.9036 9.23429 18.5521 9.23429H15.2855C13.933 9.23429 12.8366 8.12735 12.8366 6.7641V3.47018C12.8366 2.1059 13.933 1 15.2855 1Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.44892 1H6.71449C8.06703 1 9.16341 2.1059 9.16341 3.47018V6.7641C9.16341 8.12735 8.06703 9.23429 6.71449 9.23429H3.44892C2.09638 9.23429 1 8.12735 1 6.7641V3.47018C1 2.1059 2.09638 1 3.44892 1Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.44892 12.7657H6.71449C8.06703 12.7657 9.16341 13.8716 9.16341 15.2369V18.5298C9.16341 19.8941 8.06703 21 6.71449 21H3.44892C2.09638 21 1 19.8941 1 18.5298V15.2369C1 13.8716 2.09638 12.7657 3.44892 12.7657Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2855 12.7657H18.5521C19.9036 12.7657 21 13.8716 21 15.2369V18.5298C21 19.8941 19.9036 21 18.5521 21H15.2855C13.933 21 12.8366 19.8941 12.8366 18.5298V15.2369C12.8366 13.8716 13.933 12.7657 15.2855 12.7657Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 72 - 0
dashboard/src/assets/Panopticon_files/css

@@ -0,0 +1,72 @@
+/* vietnamese */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6DptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDptfpA4Q.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 500;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6DptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 500;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 500;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDptfpA4Q.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 600;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6DptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 600;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDptfpA4cD3.woff2) format('woff2');
+  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Work Sans';
+  font-style: normal;
+  font-weight: 600;
+  src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDptfpA4Q.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}

+ 5 - 0
dashboard/src/assets/Panopticon_files/d05601a92074786f925f34883d742137.svg

@@ -0,0 +1,5 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99988 0.750183C15.1089 0.750183 19.2499 4.89218 19.2499 10.0002C19.2499 15.1082 15.1089 19.2502 9.99988 19.2502C4.89188 19.2502 0.749878 15.1082 0.749878 10.0002C0.749878 4.89218 4.89188 0.750183 9.99988 0.750183Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.995 6.20428V10.6233" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.995 13.7961H10.005" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
dashboard/src/assets/Panopticon_files/d165e41d185d4ff0eaccc5b0a5ac1366.svg

@@ -0,0 +1,4 @@
+<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M20.0943 2.5H24.3268C26.0796 2.5 27.4999 3.93231 27.4999 5.69995V9.96816C27.4999 11.7358 26.0796 13.1681 24.3268 13.1681H20.0943C18.3415 13.1681 16.9211 11.7358 16.9211 9.96816V5.69995C16.9211 3.93231 18.3415 2.5 20.0943 2.5Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.67316 2.5H9.90561C11.6584 2.5 13.0788 3.93231 13.0788 5.69995V9.96816C13.0788 11.7358 11.6584 13.1681 9.90561 13.1681H5.67316C3.92032 13.1681 2.5 11.7358 2.5 9.96816V5.69995C2.5 3.93231 3.92032 2.5 5.67316 2.5ZM5.67316 16.8319H9.90561C11.6584 16.8319 13.0788 18.2642 13.0788 20.0318V24.3C13.0788 26.0665 11.6584 27.5 9.90561 27.5H5.67316C3.92032 27.5 2.5 26.0665 2.5 24.3V20.0318C2.5 18.2642 3.92032 16.8319 5.67316 16.8319ZM24.3268 16.8319H20.0944C18.3415 16.8319 16.9212 18.2642 16.9212 20.0318V24.3C16.9212 26.0665 18.3415 27.5 20.0944 27.5H24.3268C26.0797 27.5 27.5 26.0665 27.5 24.3V20.0318C27.5 18.2642 26.0797 16.8319 24.3268 16.8319Z" fill="white"/>
+</svg>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dashboard/src/assets/Panopticon_files/devicon.min.css


BIN
dashboard/src/assets/Panopticon_files/e9208ae20def7ffa11cd7e1eb9d3812b.png


+ 69 - 0
dashboard/src/assets/Panopticon_files/icon

@@ -0,0 +1,69 @@
+/* fallback */
+@font-face {
+  font-family: 'Material Icons';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/materialicons/v139/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
+}
+/* fallback */
+@font-face {
+  font-family: 'Material Icons Outlined';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v108/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2) format('woff2');
+}
+/* fallback */
+@font-face {
+  font-family: 'Material Icons Round';
+  font-style: normal;
+  font-weight: 400;
+  src: url(https://fonts.gstatic.com/s/materialiconsround/v107/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2) format('woff2');
+}
+
+.material-icons {
+  font-family: 'Material Icons';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 24px;
+  line-height: 1;
+  letter-spacing: normal;
+  text-transform: none;
+  display: inline-block;
+  white-space: nowrap;
+  word-wrap: normal;
+  direction: ltr;
+  -webkit-font-feature-settings: 'liga';
+  -webkit-font-smoothing: antialiased;
+}
+
+.material-icons-outlined {
+  font-family: 'Material Icons Outlined';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 24px;
+  line-height: 1;
+  letter-spacing: normal;
+  text-transform: none;
+  display: inline-block;
+  white-space: nowrap;
+  word-wrap: normal;
+  direction: ltr;
+  -webkit-font-feature-settings: 'liga';
+  -webkit-font-smoothing: antialiased;
+}
+
+.material-icons-round {
+  font-family: 'Material Icons Round';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 24px;
+  line-height: 1;
+  letter-spacing: normal;
+  text-transform: none;
+  display: inline-block;
+  white-space: nowrap;
+  word-wrap: normal;
+  direction: ltr;
+  -webkit-font-feature-settings: 'liga';
+  -webkit-font-smoothing: antialiased;
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dashboard/src/assets/Panopticon_files/katex.min.css


+ 417 - 0
dashboard/src/assets/Panopticon_files/main.883c79fc6a23f966494b.hot-update.js

@@ -0,0 +1,417 @@
+webpackHotUpdate("main",{
+
+/***/ "./src/Main.tsx":
+/*!**********************!*\
+  !*** ./src/Main.tsx ***!
+  \**********************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* WEBPACK VAR INJECTION */(function(__react_refresh_utils__, __react_refresh_error_overlay__) {/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var styled_components__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! styled-components */ "./node_modules/styled-components/dist/styled-components.browser.esm.js");
+/* harmony import */ var assets_gradient_png__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! assets/gradient.png */ "./src/assets/gradient.png");
+/* harmony import */ var components_TitleSection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! components/TitleSection */ "./src/components/TitleSection.tsx");
+/* harmony import */ var _filter_row_FilterRow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./filter-row/FilterRow */ "./src/filter-row/FilterRow.tsx");
+/* harmony import */ var navbar_Navbar__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! navbar/Navbar */ "./src/navbar/Navbar.tsx");
+/* harmony import */ var components_Loading__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! components/Loading */ "./src/components/Loading.tsx");
+/* harmony import */ var components_TabSelector__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! components/TabSelector */ "./src/components/TabSelector.tsx");
+/* harmony import */ var monitor_list_MonitorList__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! monitor-list/MonitorList */ "./src/monitor-list/MonitorList.tsx");
+/* harmony import */ var expanded_monitor_ExpandedMonitor__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! expanded-monitor/ExpandedMonitor */ "./src/expanded-monitor/ExpandedMonitor.tsx");
+/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! react-router-dom */ "./node_modules/react-router-dom/esm/react-router-dom.js");
+__webpack_require__.$Refresh$.runtime = __webpack_require__(/*! ./node_modules/react-refresh/runtime.js */ "./node_modules/react-refresh/runtime.js");
+__webpack_require__.$Refresh$.setup(module.i);
+
+var _s2 = __webpack_require__.$Refresh$.signature();
+
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
+
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
+
+function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
+
+function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
+
+function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+
+
+
+
+
+
+
+
+
+
+
+
+var Main = function Main() {
+  _s2();
+
+  var history = Object(react_router_dom__WEBPACK_IMPORTED_MODULE_10__["useHistory"])();
+
+  var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(false),
+      _useState2 = _slicedToArray(_useState, 2),
+      authCheckResult = _useState2[0],
+      setAuthCheckResult = _useState2[1];
+
+  var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])({}),
+      _useState4 = _slicedToArray(_useState3, 2),
+      projectDict = _useState4[0],
+      setProjectDict = _useState4[1];
+
+  var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])({}),
+      _useState6 = _slicedToArray(_useState5, 2),
+      clusterDict = _useState6[0],
+      setClusterDict = _useState6[1];
+
+  var _useState7 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])({}),
+      _useState8 = _slicedToArray(_useState7, 2),
+      clusterToProjectDict = _useState8[0],
+      setClusterToProjectDict = _useState8[1];
+
+  var _useState9 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])("all"),
+      _useState10 = _slicedToArray(_useState9, 2),
+      currentTab = _useState10[0],
+      _setCurrentTab = _useState10[1];
+
+  var _useState11 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null),
+      _useState12 = _slicedToArray(_useState11, 2),
+      expandedMonitor = _useState12[0],
+      setExpandedMonitor = _useState12[1];
+
+  var _useState13 = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])({
+    project: [],
+    cluster: [],
+    category: [],
+    priority: [],
+    status: []
+  }),
+      _useState14 = _slicedToArray(_useState13, 2),
+      filters = _useState14[0],
+      setFilters = _useState14[1];
+
+  var arrayToObject = function arrayToObject(arr, property) {
+    var obj = {};
+    arr.forEach(function (x) {
+      return obj[JSON.stringify(x.id)] = x[property || "name"];
+    });
+    return obj;
+  };
+
+  Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function () {
+    /* ret2: uncomment
+    api
+      .authCheck("<token>", {}, {})
+      .then((res) => {
+        setAuthCheckResult(true);
+      })
+      .catch((err) => {
+        window.location.href = "/api/v1/oauth/google/login";
+      });
+    */
+    setAuthCheckResult(true);
+  }, []);
+  Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function () {
+    var params = new URLSearchParams(window.location.search);
+    console.log(params.get("bloke"));
+    /* ret2: uncomment
+    api.listProjects("<token>", {}, {}).then((res) => {
+      setProjectDict(arrayToObject(res.data));
+    });
+     api.listClusters("<token>", {}, {}).then((res) => {
+      setClusterDict(arrayToObject(res.data));
+      setClusterToProjectDict(arrayToObject(res.data, "project_id"));
+    });
+    */
+    // Dummy project list query
+
+    setTimeout(function () {
+      return setProjectDict(arrayToObject([{
+        "id": 23,
+        "name": "in-dreams"
+      }, {
+        "id": 24,
+        "name": "blue-velvet"
+      }, {
+        "id": 26,
+        "name": "wagon-wheel"
+      }, {
+        "id": 209,
+        "name": "clarity"
+      }, {
+        "id": 277,
+        "name": "watermelon-man"
+      }, {
+        "id": 32,
+        "name": "you-know-how-it-is"
+      }, {
+        "id": 45,
+        "name": "shreddy-kruger"
+      }, {
+        "id": 96,
+        "name": "get-by"
+      }, {
+        "id": 88,
+        "name": "killer-queen"
+      }, {
+        "id": 4,
+        "name": "stairway-to-heaven"
+      }]));
+    }, 1000); // Dummy cluster list query
+
+    setTimeout(function () {
+      var dummyClusters = [{
+        "id": 45,
+        "name": "alexander-test-aks",
+        "project_id": 26
+      }, {
+        "id": 46,
+        "name": "scaling-test-cluster",
+        "project_id": 26
+      }, {
+        "id": 101,
+        "name": "wing-wing-staging-asdflkajsdflkjasdf-asdflkjasdflkjasdflkjasdlfkjasdlfkjasldkfjasdflkjasdflkjasdflkjasdf",
+        "project_id": 4
+      }, {
+        "id": 102,
+        "name": "comes-and-goes-alskdjflkasjdflkjasdf-asdflkjasdflkjasdflkjasdflkjasdflkjsdfjklsaasdflkjfdjsaklasdflkjfdsa",
+        "project_id": 88
+      }, {
+        "id": 455,
+        "name": "clarity-staging",
+        "project_id": 209
+      }, {
+        "id": 456,
+        "name": "clarity-production",
+        "project_id": 209
+      }];
+      setClusterDict(arrayToObject(dummyClusters));
+      setClusterToProjectDict(arrayToObject(dummyClusters, "project_id"));
+    }, 1200);
+  }, []);
+
+  if (!authCheckResult) {
+    return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(components_Loading__WEBPACK_IMPORTED_MODULE_6__["default"], null);
+  }
+
+  var renderContents = function renderContents() {
+    return expandedMonitor ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(expanded_monitor_ExpandedMonitor__WEBPACK_IMPORTED_MODULE_9__["ExpandedMonitor"], {
+      expandedMonitor: expandedMonitor,
+      closeExpanded: function closeExpanded() {
+        return setExpandedMonitor(null);
+      }
+    }) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(components_TitleSection__WEBPACK_IMPORTED_MODULE_3__["default"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(DashboardIcon, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(DashboardImage, {
+      src: assets_gradient_png__WEBPACK_IMPORTED_MODULE_2__["default"]
+    }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Overlay, null, "P")), "Porter Panopticon"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(InfoSection, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(TopRow, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(InfoLabel, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("i", {
+      className: "material-icons"
+    }, "info"), " Info")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Description, null, "This dashboard displays infrastructure monitors across all active Porter Cloud projects.")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(components_TabSelector__WEBPACK_IMPORTED_MODULE_7__["default"], {
+      currentTab: currentTab,
+      options: [{
+        value: "all",
+        label: "All"
+      }, {
+        value: "critical",
+        label: "Critical"
+      }, {
+        value: "high",
+        label: "High priority"
+      }, {
+        value: "low",
+        label: "Low priority"
+      }],
+      setCurrentTab: function setCurrentTab(x) {
+        x === "all" ? setFilters(_objectSpread(_objectSpread({}, filters), {}, {
+          priority: []
+        })) : setFilters(_objectSpread(_objectSpread({}, filters), {}, {
+          priority: [{
+            value: x
+          }]
+        }));
+
+        _setCurrentTab(x);
+      }
+    }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Br, null), Object.keys(projectDict).length > 0 && Object.keys(clusterDict).length > 0 ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(FadeWrapper, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_filter_row_FilterRow__WEBPACK_IMPORTED_MODULE_4__["FilterRow"], {
+      projectDict: projectDict,
+      clusterDict: clusterDict,
+      clusterToProjectDict: clusterToProjectDict,
+      setFilters: setFilters,
+      filters: filters
+    }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Br, null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(monitor_list_MonitorList__WEBPACK_IMPORTED_MODULE_8__["MonitorList"], {
+      projectDict: projectDict,
+      clusterDict: clusterDict,
+      setExpandedMonitor: setExpandedMonitor,
+      filters: filters
+    })) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(LoadingRow, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(components_Loading__WEBPACK_IMPORTED_MODULE_6__["default"], null)));
+  };
+
+  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(StyledMain, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(navbar_Navbar__WEBPACK_IMPORTED_MODULE_5__["default"], null), renderContents());
+};
+
+_s2(Main, "RFyQb1FsB2gLrasmA72++zOTM1k=", false, function () {
+  return [react_router_dom__WEBPACK_IMPORTED_MODULE_10__["useHistory"]];
+});
+
+_c = Main;
+/* harmony default export */ __webpack_exports__["default"] = (Main);
+var FadeWrapper = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__FadeWrapper",
+  componentId: "sc-atiuxc-0"
+})(["animation:fadeIn 0.3s;animation-timing-function:ease-out;animation-fill-mode:forwards;@keyframes fadeIn{from{opacity:0;}to{opacity:1;}}"]);
+_c2 = FadeWrapper;
+var LoadingRow = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__LoadingRow",
+  componentId: "sc-atiuxc-1"
+})(["width:100%;height:calc(100% - 300px);"]);
+_c3 = LoadingRow;
+var TopRow = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__TopRow",
+  componentId: "sc-atiuxc-2"
+})(["display:flex;align-items:center;"]);
+_c4 = TopRow;
+var Description = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__Description",
+  componentId: "sc-atiuxc-3"
+})(["color:#8b949f;margin-top:13px;margin-left:2px;font-size:13px;"]);
+_c5 = Description;
+var InfoLabel = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__InfoLabel",
+  componentId: "sc-atiuxc-4"
+})(["width:72px;height:20px;display:flex;align-items:center;color:#8b949f;font-size:13px;> i{color:#8b949f;font-size:18px;margin-right:5px;}"]);
+_c6 = InfoLabel;
+var InfoSection = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__InfoSection",
+  componentId: "sc-atiuxc-5"
+})(["margin-top:35px;font-family:\"Work Sans\",sans-serif;margin-left:0px;margin-bottom:30px;"]);
+_c7 = InfoSection;
+var StyledMain = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__StyledMain",
+  componentId: "sc-atiuxc-6"
+})(["width:100%;padding:45px 40px 45px;overflow:auto;position:relative;"]);
+_c8 = StyledMain;
+var Br = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__Br",
+  componentId: "sc-atiuxc-7"
+})(["width:100%;height:20px;"]);
+_c9 = Br;
+var Overlay = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__Overlay",
+  componentId: "sc-atiuxc-8"
+})(["height:100%;width:100%;position:absolute;top:0;left:0;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:500;font-family:\"Work Sans\",sans-serif;color:white;"]);
+_c10 = Overlay;
+var DashboardImage = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].img.withConfig({
+  displayName: "Main__DashboardImage",
+  componentId: "sc-atiuxc-9"
+})(["height:35px;width:35px;border-radius:5px;box-shadow:0 2px 5px 4px #00000011;"]);
+_c11 = DashboardImage;
+var DashboardIcon = styled_components__WEBPACK_IMPORTED_MODULE_1__["default"].div.withConfig({
+  displayName: "Main__DashboardIcon",
+  componentId: "sc-atiuxc-10"
+})(["position:relative;height:35px;margin-right:17px;width:35px;border-radius:5px;display:flex;align-items:center;justify-content:center;> i{font-size:22px;}"]);
+_c12 = DashboardIcon;
+
+var _c, _c2, _c3, _c4, _c5, _c6, _c7, _c8, _c9, _c10, _c11, _c12;
+
+__webpack_require__.$Refresh$.register(_c, "Main");
+__webpack_require__.$Refresh$.register(_c2, "FadeWrapper");
+__webpack_require__.$Refresh$.register(_c3, "LoadingRow");
+__webpack_require__.$Refresh$.register(_c4, "TopRow");
+__webpack_require__.$Refresh$.register(_c5, "Description");
+__webpack_require__.$Refresh$.register(_c6, "InfoLabel");
+__webpack_require__.$Refresh$.register(_c7, "InfoSection");
+__webpack_require__.$Refresh$.register(_c8, "StyledMain");
+__webpack_require__.$Refresh$.register(_c9, "Br");
+__webpack_require__.$Refresh$.register(_c10, "Overlay");
+__webpack_require__.$Refresh$.register(_c11, "DashboardImage");
+__webpack_require__.$Refresh$.register(_c12, "DashboardIcon");
+
+const currentExports = __react_refresh_utils__.getModuleExports(module.i);
+__react_refresh_utils__.registerExportsForReactRefresh(currentExports, module.i);
+
+if (true) {
+  const isHotUpdate = !!module.hot.data;
+  const prevExports = isHotUpdate ? module.hot.data.prevExports : null;
+
+  if (__react_refresh_utils__.isReactRefreshBoundary(currentExports)) {
+    module.hot.dispose(
+      /**
+       * A callback to performs a full refresh if React has unrecoverable errors,
+       * and also caches the to-be-disposed module.
+       * @param {*} data A hot module data object from Webpack HMR.
+       * @returns {void}
+       */
+      function hotDisposeCallback(data) {
+        // We have to mutate the data object to get data registered and cached
+        data.prevExports = currentExports;
+      }
+    );
+    module.hot.accept(
+      /**
+       * An error handler to allow self-recovering behaviours.
+       * @param {Error} error An error occurred during evaluation of a module.
+       * @returns {void}
+       */
+      function hotErrorHandler(error) {
+        if (
+          typeof __react_refresh_error_overlay__ !== 'undefined' &&
+          __react_refresh_error_overlay__
+        ) {
+          __react_refresh_error_overlay__.handleRuntimeError(error);
+        }
+
+        if (typeof __react_refresh_test__ !== 'undefined' && __react_refresh_test__) {
+          if (window.onHotAcceptError) {
+            window.onHotAcceptError(error.message);
+          }
+        }
+
+        __webpack_require__.c[module.i].hot.accept(hotErrorHandler);
+      }
+    );
+
+    if (isHotUpdate) {
+      if (
+        __react_refresh_utils__.isReactRefreshBoundary(prevExports) &&
+        __react_refresh_utils__.shouldInvalidateReactRefreshBoundary(prevExports, currentExports)
+      ) {
+        module.hot.invalidate();
+      } else {
+        __react_refresh_utils__.enqueueUpdate(
+          /**
+           * A function to dismiss the error overlay after performing React refresh.
+           * @returns {void}
+           */
+          function updateCallback() {
+            if (
+              typeof __react_refresh_error_overlay__ !== 'undefined' &&
+              __react_refresh_error_overlay__
+            ) {
+              __react_refresh_error_overlay__.clearRuntimeErrors();
+            }
+          }
+        );
+      }
+    }
+  } else {
+    if (isHotUpdate && __react_refresh_utils__.isReactRefreshBoundary(prevExports)) {
+      module.hot.invalidate();
+    }
+  }
+}
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js */ "./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js"), __webpack_require__(/*! ./node_modules/@pmmmwh/react-refresh-webpack-plugin/overlay/index.js */ "./node_modules/@pmmmwh/react-refresh-webpack-plugin/overlay/index.js")))
+
+/***/ })
+
+})
+//# sourceMappingURL=main.883c79fc6a23f966494b.hot-update.js.map

+ 8 - 0
dashboard/src/assets/cluster.svg

@@ -0,0 +1,8 @@
+<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M9.15631 13.4409C11.4924 13.4409 13.3656 11.5569 13.3656 9.20736C13.3656 6.85691 11.4924 4.97385 9.15631 4.97385C6.8202 4.97385 4.94702 6.85691 4.94702 9.20736C4.94702 11.5569 6.8202 13.4409 9.15631 13.4409" fill="white"/>
+<path opacity="0.4" d="M18.2952 10.1931C18.8996 7.81564 17.1276 5.68042 14.8712 5.68042C14.6259 5.68042 14.3913 5.70744 14.162 5.75336C14.1316 5.76057 14.0976 5.77588 14.0797 5.8029C14.0591 5.83712 14.0743 5.88305 14.0967 5.91276C14.7745 6.86915 15.164 8.03357 15.164 9.28354C15.164 10.4813 14.8067 11.598 14.1799 12.5246C14.1155 12.6201 14.1728 12.7489 14.2865 12.7687C14.4441 12.7966 14.6053 12.811 14.77 12.8155C16.4131 12.8588 17.8878 11.7952 18.2952 10.1931" fill="white"/>
+<path opacity="0.4" d="M4.25211 5.75364C4.02378 5.70681 3.78829 5.68069 3.54295 5.68069C1.28653 5.68069 -0.485469 7.81591 0.119824 10.1934C0.526336 11.7955 2.00106 12.859 3.64413 12.8158C3.80888 12.8113 3.97095 12.796 4.12765 12.769C4.24136 12.7492 4.29867 12.6204 4.2342 12.5249C3.60742 11.5973 3.25015 10.4816 3.25015 9.28382C3.25015 8.03295 3.64055 6.86853 4.31837 5.91304C4.33986 5.88332 4.35597 5.83739 4.33448 5.80317C4.31658 5.77525 4.28345 5.76084 4.25211 5.75364" fill="white"/>
+<path d="M13.4409 9.25842C13.4409 6.92232 11.5569 5.04914 9.20739 5.04914C6.85694 5.04914 4.97388 6.92232 4.97388 9.25842C4.97388 11.5945 6.85694 13.4677 9.20739 13.4677C11.5569 13.4677 13.4409 11.5945 13.4409 9.25842" fill="white"/>
+<path opacity="0.4" d="M10.1931 0.119514C7.81564 -0.484882 5.68042 1.28712 5.68042 3.54353C5.68042 3.78887 5.70744 4.02347 5.75336 4.25269C5.76057 4.28314 5.77588 4.31716 5.8029 4.33507C5.83712 4.35566 5.88305 4.34044 5.91276 4.31806C6.86915 3.64024 8.03357 3.25074 9.28354 3.25074C10.4813 3.25074 11.598 3.608 12.5246 4.23479C12.6201 4.29925 12.7489 4.24195 12.7687 4.12823C12.7966 3.97064 12.811 3.80947 12.8155 3.64471C12.8588 2.00165 11.7952 0.526922 10.1931 0.119514" fill="white"/>
+<path opacity="0.4" d="M5.75361 14.1626C5.70678 14.391 5.68066 14.6264 5.68066 14.8718C5.68066 17.1282 7.81588 18.9002 10.1934 18.2949C11.7954 17.8884 12.859 16.4137 12.8158 14.7706C12.8113 14.6059 12.796 14.4438 12.7689 14.2871C12.7491 14.1734 12.6203 14.1161 12.5249 14.1805C11.5973 14.8073 10.4815 15.1646 9.28379 15.1646C8.03292 15.1646 6.8685 14.7742 5.91301 14.0964C5.88329 14.0749 5.83736 14.0588 5.80314 14.0802C5.77522 14.0982 5.76081 14.1313 5.75361 14.1626" fill="white"/>
+</svg>

+ 2 - 2
dashboard/src/components/form-components/CheckboxRow.tsx

@@ -48,8 +48,8 @@ const CheckboxWrapper = styled.div<{ disabled?: boolean }>`
 `;
 `;
 
 
 const Checkbox = styled.div<{ checked: boolean }>`
 const Checkbox = styled.div<{ checked: boolean }>`
-  width: 16px;
-  height: 16px;
+  width: 12px;
+  height: 12px;
   border: 1px solid #ffffff55;
   border: 1px solid #ffffff55;
   margin: 1px 10px 0px 1px;
   margin: 1px 10px 0px 1px;
   border-radius: 3px;
   border-radius: 3px;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -87,7 +87,7 @@ export const Dashboard: React.FunctionComponent = () => {
           </InfoLabel>
           </InfoLabel>
         </TopRow>
         </TopRow>
         <Description>
         <Description>
-          Cluster dashboard for {context.currentCluster.name}
+          Cluster settings for {context.currentCluster.name}
         </Description>
         </Description>
       </InfoSection>
       </InfoSection>
 
 

+ 143 - 263
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -1,171 +1,84 @@
-import React, { Component } from "react";
+import React, { useEffect, useState } from "react";
+
 import styled from "styled-components";
 import styled from "styled-components";
+import { ClusterType, ProjectType } from "shared/types";
+import { Tooltip } from "@material-ui/core";
+import settings from "assets/settings.svg";
 
 
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { ClusterType } from "shared/types";
 import monojob from "assets/monojob.png";
 import monojob from "assets/monojob.png";
 import monoweb from "assets/monoweb.png";
 import monoweb from "assets/monoweb.png";
 import sliders from "assets/sliders.svg";
 import sliders from "assets/sliders.svg";
+import cluster from "assets/cluster.svg";
 
 
-import { RouteComponentProps, withRouter } from "react-router";
-import { pushFiltered } from "shared/routing";
-import { Tooltip } from "@material-ui/core";
 import SidebarLink from "./SidebarLink";
 import SidebarLink from "./SidebarLink";
 
 
-type PropsType = RouteComponentProps & {
-  setWelcome: (x: boolean) => void;
-  currentView: string;
-  isSelected: boolean;
-  forceRefreshClusters: boolean;
-  setRefreshClusters: (x: boolean) => void;
-};
-
-type StateType = {
-  clusters: ClusterType[];
-
-  // Track last project id for refreshing clusters on project change
-  prevProjectId: number;
+type Props = {
+  cluster: ClusterType,
+  currentCluster: ClusterType,
+  currentProject: ProjectType,
+  setCurrentCluster: (x: ClusterType, callback?: any) => void,
+  navToClusterDashboard: () => void,
 };
 };
 
 
-class ClusterSection extends Component<PropsType, StateType> {
-  // Need to track initialized for animation mounting
-  state = {
-    clusters: [] as ClusterType[],
-    prevProjectId: this.context.currentProject.id,
-  };
-
-  updateClusters = () => {
-    let {
-      user,
-      currentProject,
-      setCurrentCluster,
-      currentCluster,
-    } = this.context;
-
-    // TODO: query with selected filter once implemented
-    api
-      .getClusters("<token>", {}, { id: currentProject.id })
-      .then((res) => {
-        window.analytics?.identify(user.userId, {
-          currentProject,
-          clusters: res.data,
-        });
-
-        this.props.setWelcome(false);
-        // TODO: handle uninitialized kubeconfig
-        if (res.data) {
-          let clusters = res.data;
-          clusters.sort((a: any, b: any) => a.id - b.id);
-          if (clusters.length > 0) {
-            let queryString = window.location.search;
-            let urlParams = new URLSearchParams(queryString);
-            let paramClusterName = urlParams.get("cluster");
-            let params = this.props.match.params as any;
-            let pathClusterName = params.cluster;
-
-            // Set cluster from URL if in path or params
-            let defaultCluster = null as ClusterType;
-            if (paramClusterName || pathClusterName) {
-              clusters.forEach((cluster: ClusterType) => {
-                if (!defaultCluster) {
-                  if (cluster.name === pathClusterName) {
-                    defaultCluster = cluster;
-                  } else if (cluster.name === paramClusterName) {
-                    defaultCluster = cluster;
-                  }
-                }
-              });
-            }
-
-            this.setState({ clusters });
-            let saved = JSON.parse(
-              localStorage.getItem(currentProject.id + "-cluster")
-            );
-            if (!defaultCluster && saved && saved !== "null") {
-              // Ensures currentCluster isn't prematurely set (causes issues downstream)
-              let loaded = false;
-              for (let i = 0; i < clusters.length; i++) {
-                if (
-                  clusters[i].id === saved.id &&
-                  clusters[i].project_id === saved.project_id &&
-                  clusters[i].name === saved.name
-                ) {
-                  loaded = true;
-                  setCurrentCluster(clusters[i]);
-                  break;
-                }
-              }
-              if (!loaded) {
-                setCurrentCluster(clusters[0]);
-              }
-            } else {
-              setCurrentCluster(defaultCluster || clusters[0]);
-            }
-          } else if (
-            this.props.currentView !== "provisioner" &&
-            this.props.currentView !== "new-project"
-          ) {
-            this.setState({ clusters: [] });
-            setCurrentCluster(null);
-          }
-        }
-      })
-      .catch((err) => this.props.setWelcome(true));
-  };
-
-  componentDidMount() {
-    this.updateClusters();
-  }
-
-  componentDidUpdate(prevProps: PropsType) {
-    if (prevProps !== this.props) {
-      // Refresh clusters on project change
-      if (this.state.prevProjectId !== this.context.currentProject.id) {
-        this.updateClusters();
-        this.setState({ prevProjectId: this.context.currentProject.id });
-      } else if (this.props.forceRefreshClusters === true) {
-        this.updateClusters();
-        this.props.setRefreshClusters(false);
-      }
+export const ClusterSection: React.FC<Props> = ({
+  cluster,
+  currentCluster,
+  currentProject,
+  setCurrentCluster,
+  navToClusterDashboard,
+}) => {
+  const [isExpanded, setIsExpanded] = useState(false);
+
+  useEffect(() => {
+    if (!isExpanded) {
+      currentCluster.id === cluster.id && setIsExpanded(true);
     }
     }
-  }
-
-  showClusterConfigModal = () => {
-    this.context.setCurrentModal("ClusterConfigModal", {
-      updateClusters: this.updateClusters,
-    });
-  };
+  }, [currentCluster]);
 
 
-  renderClusterContent = () => {
-    let { currentCluster, currentProject } = this.context;
+  const renderClusterContent = (cluster: any) => {
+    let clusterId = cluster.id;
 
 
-    if (currentCluster) {
+    if (currentCluster && isExpanded) {
       return (
       return (
         <Relative>
         <Relative>
           <SideLine />
           <SideLine />
-          <NavButton path="/applications">
+          <NavButton 
+            path="/applications" 
+            active={currentCluster.id === clusterId && window.location.pathname === "/applications"}
+          >
             <Img src={monoweb} />
             <Img src={monoweb} />
             Applications
             Applications
           </NavButton>
           </NavButton>
-          <NavButton path="/jobs">
+          <NavButton 
+            path="/jobs"
+            active={currentCluster.id === clusterId && window.location.pathname === "/jobs"}
+          >
             <Img src={monojob} />
             <Img src={monojob} />
             Jobs
             Jobs
           </NavButton>
           </NavButton>
-          <NavButton path="/env-groups">
+          <NavButton 
+            path="/env-groups"
+            active={currentCluster.id === clusterId && window.location.pathname === "/env-groups"}
+          >
             <Img src={sliders} />
             <Img src={sliders} />
-            Env Groups
+            Env groups
           </NavButton>
           </NavButton>
-          {currentCluster.service === "eks" &&
-            currentCluster.infra_id > 0 &&
+          {cluster.service === "eks" &&
+            cluster.infra_id > 0 &&
             currentProject.enable_rds_databases && (
             currentProject.enable_rds_databases && (
-              <NavButton path="/databases">
+              <NavButton 
+                path="/databases"
+                active={currentCluster.id === clusterId && window.location.pathname === "/databases"}
+              >
                 <Icon className="material-icons-outlined">storage</Icon>
                 <Icon className="material-icons-outlined">storage</Icon>
                 Databases
                 Databases
               </NavButton>
               </NavButton>
             )}
             )}
           {currentProject?.preview_envs_enabled && (
           {currentProject?.preview_envs_enabled && (
-            <NavButton path="/preview-environments">
+            <NavButton 
+              path="/preview-environments"
+              active={currentCluster.id === clusterId && window.location.pathname === "/preview-environments"}
+            >
               <InlineSVGWrapper
               <InlineSVGWrapper
                 id="Flat"
                 id="Flat"
                 fill="#FFFFFF"
                 fill="#FFFFFF"
@@ -174,79 +87,88 @@ class ClusterSection extends Component<PropsType, StateType> {
               >
               >
                 <path d="M103.99951,68a36,36,0,1,0-44,35.0929v49.8142a36,36,0,1,0,16,0V103.0929A36.05516,36.05516,0,0,0,103.99951,68Zm-56,0a20,20,0,1,1,20,20A20.0226,20.0226,0,0,1,47.99951,68Zm40,120a20,20,0,1,1-20-20A20.0226,20.0226,0,0,1,87.99951,188ZM196.002,152.907l-.00146-33.02563a55.63508,55.63508,0,0,0-16.40137-39.59619L155.31348,56h20.686a8,8,0,0,0,0-16h-40c-.02978,0-.05859.00415-.08838.00446-.2334.00256-.46631.01245-.69824.03527-.12891.01258-.25391.03632-.38086.05494-.13135.01928-.26318.03424-.39355.06-.14014.02778-.27686.06611-.41455.10114-.11475.02924-.23047.05426-.34424.08862-.13428.04059-.26367.0907-.395.13806-.11524.04151-.231.07929-.34473.12629-.12109.05011-.23681.10876-.35449.16455-.11914.05621-.23926.10907-.356.17144-.11133.0597-.21728.12757-.32519.1922-.11621.06928-.23389.13483-.34668.21051-.11719.07831-.227.16553-.33985.24976-.09668.07227-.1958.1394-.28955.21655-.18652.1529-.36426.31531-.53564.48413-.01612.01593-.03418.02918-.05029.04529-.02051.02051-.0376.04321-.05762.06391-.16358.16711-.32178.33941-.47022.52032-.083.10059-.15527.20648-.23193.31006-.07861.10571-.16064.20862-.23438.3183-.08056.12072-.15087.24591-.2246.36993-.05958.1-.12208.19757-.17725.30036-.06787.12591-.125.25531-.18506.384-.05078.1084-.10547.21466-.15137.32568-.05127.12463-.09326.25189-.13867.37848-.04248.11987-.08887.238-.126.36047-.03857.12775-.06738.25757-.09912.38678-.03125.124-.06591.24622-.0913.37244-.02979.15088-.04786.30328-.06934.45544-.01465.10645-.03516.21094-.0459.31867q-.03955.39752-.04.79706V88a8,8,0,0,0,16,0V67.31378l24.28516,24.28485a39.73874,39.73874,0,0,1,11.71582,28.28321l.00146,33.02533a36.00007,36.00007,0,1,0,16-.00019ZM188.00244,208a20,20,0,1,1,20-20A20.0226,20.0226,0,0,1,188.00244,208Z" />
                 <path d="M103.99951,68a36,36,0,1,0-44,35.0929v49.8142a36,36,0,1,0,16,0V103.0929A36.05516,36.05516,0,0,0,103.99951,68Zm-56,0a20,20,0,1,1,20,20A20.0226,20.0226,0,0,1,47.99951,68Zm40,120a20,20,0,1,1-20-20A20.0226,20.0226,0,0,1,87.99951,188ZM196.002,152.907l-.00146-33.02563a55.63508,55.63508,0,0,0-16.40137-39.59619L155.31348,56h20.686a8,8,0,0,0,0-16h-40c-.02978,0-.05859.00415-.08838.00446-.2334.00256-.46631.01245-.69824.03527-.12891.01258-.25391.03632-.38086.05494-.13135.01928-.26318.03424-.39355.06-.14014.02778-.27686.06611-.41455.10114-.11475.02924-.23047.05426-.34424.08862-.13428.04059-.26367.0907-.395.13806-.11524.04151-.231.07929-.34473.12629-.12109.05011-.23681.10876-.35449.16455-.11914.05621-.23926.10907-.356.17144-.11133.0597-.21728.12757-.32519.1922-.11621.06928-.23389.13483-.34668.21051-.11719.07831-.227.16553-.33985.24976-.09668.07227-.1958.1394-.28955.21655-.18652.1529-.36426.31531-.53564.48413-.01612.01593-.03418.02918-.05029.04529-.02051.02051-.0376.04321-.05762.06391-.16358.16711-.32178.33941-.47022.52032-.083.10059-.15527.20648-.23193.31006-.07861.10571-.16064.20862-.23438.3183-.08056.12072-.15087.24591-.2246.36993-.05958.1-.12208.19757-.17725.30036-.06787.12591-.125.25531-.18506.384-.05078.1084-.10547.21466-.15137.32568-.05127.12463-.09326.25189-.13867.37848-.04248.11987-.08887.238-.126.36047-.03857.12775-.06738.25757-.09912.38678-.03125.124-.06591.24622-.0913.37244-.02979.15088-.04786.30328-.06934.45544-.01465.10645-.03516.21094-.0459.31867q-.03955.39752-.04.79706V88a8,8,0,0,0,16,0V67.31378l24.28516,24.28485a39.73874,39.73874,0,0,1,11.71582,28.28321l.00146,33.02533a36.00007,36.00007,0,1,0,16-.00019ZM188.00244,208a20,20,0,1,1,20-20A20.0226,20.0226,0,0,1,188.00244,208Z" />
               </InlineSVGWrapper>
               </InlineSVGWrapper>
-              Preview Envs
+              Preview envs
             </NavButton>
             </NavButton>
           )}
           )}
           {currentProject?.stacks_enabled ? (
           {currentProject?.stacks_enabled ? (
-            <NavButton path={"/stacks"}>
+            <NavButton 
+              path="/stacks"
+              active={currentCluster.id === clusterId && window.location.pathname === "/stacks"}
+            >
               <Icon className="material-icons-outlined">lan</Icon>
               <Icon className="material-icons-outlined">lan</Icon>
               Stacks
               Stacks
             </NavButton>
             </NavButton>
           ) : null}
           ) : null}
+          <NavButton 
+            path={"/cluster-dashboard"}
+            active={currentCluster.id === clusterId && window.location.pathname === "/cluster-dashboard"}
+          >
+            <Icon className="material-icons">device_hub</Icon>
+            Cluster settings
+          </NavButton>
         </Relative>
         </Relative>
       );
       );
     }
     }
   };
   };
 
 
-  renderContents = (): JSX.Element[] | JSX.Element => {
-    let { clusters } = this.state;
-    let { currentCluster, setCurrentCluster } = this.context;
-
-    if (clusters.length > 0 && currentCluster) {
-      clusters.sort((a, b) => a.id - b.id);
-
-      return clusters.map((cluster: ClusterType, i: number) => {  
-        return (
-          <>
-          <ClusterSelector 
-            key={i} 
-            active={cluster.name === currentCluster.name}
-            onClick={() => {
-              setCurrentCluster(cluster, () => {
-                pushFiltered(this.props, "/cluster-dashboard", ["project_id"], {
-                  cluster: cluster.name,
-                });
-              });
-            }}
-          >
-            <LinkWrapper>
-              <ClusterIcon>
-                <i className="material-icons">device_hub</i>
-              </ClusterIcon>
-              <Tooltip title={cluster?.name}>
-                <ClusterName>{cluster.name}</ClusterName>
-              </Tooltip>
-            </LinkWrapper>
-          </ClusterSelector>
-          {this.renderClusterContent()}
-          </>
-        );
-      });
-    }
+  return (
+    <>
+      <ClusterSelector onClick={() => setIsExpanded(!isExpanded)}>
+        <LinkWrapper>
+          <ClusterIcon>
+            <svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M15.207 12.4403C16.8094 12.4403 18.1092 11.1414 18.1092 9.53907C18.1092 7.93673 16.8094 6.63782 15.207 6.63782" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path d="M3.90217 12.4403C2.29983 12.4403 1 11.1414 1 9.53907C1 7.93673 2.29983 6.63782 3.90217 6.63782" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path fill-rule="evenodd" clip-rule="evenodd" d="M9.54993 13.4133C7.4086 13.4133 5.69168 11.6964 5.69168 9.55417C5.69168 7.41284 7.4086 5.69592 9.54993 5.69592C11.6913 5.69592 13.4082 7.41284 13.4082 9.55417C13.4082 11.6964 11.6913 13.4133 9.54993 13.4133Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path d="M6.66895 15.207C6.66895 16.8094 7.96787 18.1092 9.5702 18.1092C11.1725 18.1092 12.4715 16.8094 12.4715 15.207" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path d="M6.66895 3.90217C6.66895 2.29983 7.96787 1 9.5702 1C11.1725 1 12.4715 2.29983 12.4715 3.90217" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path fill-rule="evenodd" clip-rule="evenodd" d="M5.69591 9.54996C5.69591 7.40863 7.41283 5.69171 9.55508 5.69171C11.6964 5.69171 13.4133 7.40863 13.4133 9.54996C13.4133 11.6913 11.6964 13.4082 9.55508 13.4082C7.41283 13.4082 5.69591 11.6913 5.69591 9.54996Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            </svg>
+          </ClusterIcon>
+          <Tooltip title={cluster?.name}>
+            <ClusterName>medicspot-production</ClusterName>
+          </Tooltip>
+          <I isExpanded={isExpanded} className="material-icons">arrow_drop_down</I>
+          <Spacer />
+        </LinkWrapper>
+      </ClusterSelector>
+      <div onClick={() => setCurrentCluster(cluster)}>
+        {renderClusterContent(cluster)}
+      </div>
+    </>
+  );
+};
 
 
-    return (
-      <InitializeButton
-        onClick={() =>
-          this.context.setCurrentModal("ClusterInstructionsModal", {})
-        }
-      >
-        <Plus>+</Plus> Connect a Cluster
-      </InitializeButton>
-    );
-  };
+const Spacer = styled.div`
+  flex: 1;
+`;
 
 
-  render() {
-    return (
-      <>
-        {this.renderContents()}
-      </>
-    );
+const Settings = styled.p`
+  color: #ffffff44;
+  width: 16px;
+  padding-right: 7px;
+  height: 100%;
+  border-radius: 3px;
+  cursor: pointer;
+  margin-left: 1px;
+  :hover {
+    color: #ffffff;
   }
   }
-}
-
-ClusterSection.contextType = Context;
+  > i {
+    font-size: 16px;
+    display: flex;
+    height: 100%;
+    align-items: center;
+    justify-content: center;
+  }
+`;
 
 
-export default withRouter(ClusterSection);
+const I = styled.i`
+  color: #ffffff99;
+  font-size: 20px;
+  border-radius: 100px;
+  transform: ${(props: { isExpanded: boolean }) => props.isExpanded ? "" : "rotate(-90deg)"};
+`;
 
 
 const Relative = styled.div`
 const Relative = styled.div`
   position: relative;
   position: relative;
@@ -254,7 +176,7 @@ const Relative = styled.div`
 
 
 const SideLine = styled.div`
 const SideLine = styled.div`
   position: absolute;
   position: absolute;
-  left: 34px;
+  left: 32px;
   width: 1px;
   width: 1px;
   top: 5px;
   top: 5px;
   height: calc(100% - 12px);
   height: calc(100% - 12px);
@@ -263,11 +185,11 @@ const SideLine = styled.div`
 
 
 const Icon = styled.span`
 const Icon = styled.span`
   padding: 4px;
   padding: 4px;
-  width: 23px;
+  width: 22px;
   padding-top: 4px;
   padding-top: 4px;
   border-radius: 3px;
   border-radius: 3px;
-  margin-right: 10px;
-  font-size: 18px;
+  margin-right: 8px;
+  font-size: 16px;
 `;
 `;
 
 
 const NavButton = styled(SidebarLink)`
 const NavButton = styled(SidebarLink)`
@@ -276,26 +198,20 @@ const NavButton = styled(SidebarLink)`
   border-radius: 5px;
   border-radius: 5px;
   position: relative;
   position: relative;
   text-decoration: none;
   text-decoration: none;
-  height: 36px;
+  height: 34px;
   margin: 5px 15px;
   margin: 5px 15px;
-  margin-left: 40px;
+  margin-left: 39px;
   padding: 0 30px 2px 8px;
   padding: 0 30px 2px 8px;
-  font-size: 14px;
+  font-size: 13px;
   font-family: "Work Sans", sans-serif;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   color: #ffffff;
   cursor: ${(props: { disabled?: boolean }) =>
   cursor: ${(props: { disabled?: boolean }) =>
     props.disabled ? "not-allowed" : "pointer"};
     props.disabled ? "not-allowed" : "pointer"};
 
 
-  &.active {
-    background: #ffffff11;
-
-    :hover {
-      background: #ffffff11;
-    }
-  }
+  background: ${(props: any) => props.active ? "#ffffff11" : ""};
 
 
   :hover {
   :hover {
-    background: #ffffff08;
+    background: ${(props: any) => props.active ? "#ffffff11" : "#ffffff08"};
   }
   }
 
 
   > i {
   > i {
@@ -308,11 +224,11 @@ const NavButton = styled(SidebarLink)`
 
 
 const Img = styled.img<{ enlarge?: boolean }>`
 const Img = styled.img<{ enlarge?: boolean }>`
   padding: ${(props) => (props.enlarge ? "0 0 0 1px" : "4px")};
   padding: ${(props) => (props.enlarge ? "0 0 0 1px" : "4px")};
-  height: 23px;
-  width: 23px;
+  height: 22px;
+  width: 22px;
   padding-top: 4px;
   padding-top: 4px;
   border-radius: 3px;
   border-radius: 3px;
-  margin-right: 10px;
+  margin-right: 8px;
 `;
 `;
 
 
 const InlineSVGWrapper = styled.svg`
 const InlineSVGWrapper = styled.svg`
@@ -326,65 +242,24 @@ const InlineSVGWrapper = styled.svg`
   }
   }
 `;
 `;
 
 
-const Plus = styled.div`
-  margin-right: 10px;
-  font-size: 15px;
-`;
-
-const InitializeButton = styled.div`
-  position: relative;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: calc(100% - 30px);
-  height: 38px;
-  margin: 10px 15px 12px;
-  font-size: 13px;
-  font-weight: 500;
-  border-radius: 3px;
-  color: #ffffff;
-  padding-bottom: 1px;
-  cursor: pointer;
-  background: #ffffff11;
-
-  :hover {
-    background: #ffffff22;
-  }
-`;
-
-const BgAccent = styled.img`
-  height: 30px;
-  background: #819bfd;
-  width: 30px;
-  border-top-left-radius: 100px;
-  max-width: 30px;
-  border-bottom-left-radius: 100px;
-  position: absolute;
-  top: 6px;
-  right: -8px;
-  border: none;
-  outline: none;
-`;
-
 const ClusterName = styled.div`
 const ClusterName = styled.div`
   white-space: nowrap;
   white-space: nowrap;
   overflow: hidden;
   overflow: hidden;
-  padding-right: 15px;
   text-overflow: ellipsis;
   text-overflow: ellipsis;
   display: inline-block;
   display: inline-block;
   margin-left: 3px;
   margin-left: 3px;
+  margin-right: 4px;
   font-weight: 400;
   font-weight: 400;
   color: #ffffff;
   color: #ffffff;
 `;
 `;
 
 
 const ClusterIcon = styled.div`
 const ClusterIcon = styled.div`
-  > i {
-    font-size: 16px;
+  > svg {
+    width: 13px;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    margin-bottom: 0px;
-    margin-left: 4px;
-    margin-right: 10px;
+    margin-bottom: -1x;
+    margin-right: 9px;
     color: #ffffff;
     color: #ffffff;
   }
   }
 `;
 `;
@@ -394,6 +269,7 @@ const LinkWrapper = styled.div`
   height: 100%;
   height: 100%;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
+  justify-content: space-between;
   width: 100%;
   width: 100%;
 `;
 `;
 
 
@@ -402,10 +278,10 @@ const ClusterSelector = styled.div`
   display: block;
   display: block;
   border-radius: 5px;
   border-radius: 5px;
   width: calc(100% - 30px);
   width: calc(100% - 30px);
-  height: 36px;
+  height: 34px;
+  padding: 0 6px 2px 11px;
+  font-size: 13px;
   margin: 5px 15px;
   margin: 5px 15px;
-  padding: 0 10px 2px 8px;
-  font-size: 14px;
   font-weight: 500;
   font-weight: 500;
   color: white;
   color: white;
   cursor: pointer;
   cursor: pointer;
@@ -413,6 +289,10 @@ const ClusterSelector = styled.div`
   background: ${(props: { active?: boolean }) =>
   background: ${(props: { active?: boolean }) =>
     props.active ? "#ffffff11" : ""};
     props.active ? "#ffffff11" : ""};
   :hover {
   :hover {
-    background: #ffffff08;
+    > div {
+      > i {
+        background: #ffffff11;
+      }
+    }
   }
   }
 `;
 `;

+ 225 - 0
dashboard/src/main/home/sidebar/Clusters.tsx

@@ -0,0 +1,225 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+
+import api from "shared/api";
+import { pushFiltered } from "shared/routing";
+import { Context } from "shared/Context";
+import { ClusterType } from "shared/types";
+import { ClusterSection } from "./ClusterSection";
+
+import { RouteComponentProps, withRouter } from "react-router";
+
+type PropsType = RouteComponentProps & {
+  setWelcome: (x: boolean) => void;
+  currentView: string;
+  isSelected: boolean;
+  forceRefreshClusters: boolean;
+  setRefreshClusters: (x: boolean) => void;
+};
+
+type StateType = {
+  clusters: ClusterType[];
+
+  // Track last project id for refreshing clusters on project change
+  prevProjectId: number;
+};
+
+class Clusters extends Component<PropsType, StateType> {
+  // Need to track initialized for animation mounting
+  state = {
+    clusters: [] as ClusterType[],
+    prevProjectId: this.context.currentProject.id,
+  };
+
+  updateClusters = () => {
+    let {
+      user,
+      currentProject,
+      setCurrentCluster,
+      currentCluster,
+    } = this.context;
+
+    // TODO: query with selected filter once implemented
+    api
+      .getClusters("<token>", {}, { id: currentProject.id })
+      .then((res) => {
+        window.analytics?.identify(user.userId, {
+          currentProject,
+          clusters: res.data,
+        });
+
+        this.props.setWelcome(false);
+        // TODO: handle uninitialized kubeconfig
+        if (res.data) {
+          let clusters = res.data;
+          clusters.sort((a: any, b: any) => a.id - b.id);
+          if (clusters.length > 0) {
+            let queryString = window.location.search;
+            let urlParams = new URLSearchParams(queryString);
+            let paramClusterName = urlParams.get("cluster");
+            let params = this.props.match.params as any;
+            let pathClusterName = params.cluster;
+
+            // Set cluster from URL if in path or params
+            let defaultCluster = null as ClusterType;
+            if (paramClusterName || pathClusterName) {
+              clusters.forEach((cluster: ClusterType) => {
+                if (!defaultCluster) {
+                  if (cluster.name === pathClusterName) {
+                    defaultCluster = cluster;
+                  } else if (cluster.name === paramClusterName) {
+                    defaultCluster = cluster;
+                  }
+                }
+              });
+            }
+
+            this.setState({ clusters });
+            let saved = JSON.parse(
+              localStorage.getItem(currentProject.id + "-cluster")
+            );
+            if (!defaultCluster && saved && saved !== "null") {
+              // Ensures currentCluster isn't prematurely set (causes issues downstream)
+              let loaded = false;
+              for (let i = 0; i < clusters.length; i++) {
+                if (
+                  clusters[i].id === saved.id &&
+                  clusters[i].project_id === saved.project_id &&
+                  clusters[i].name === saved.name
+                ) {
+                  loaded = true;
+                  setCurrentCluster(clusters[i]);
+                  break;
+                }
+              }
+              if (!loaded) {
+                setCurrentCluster(clusters[0]);
+              }
+            } else {
+              setCurrentCluster(defaultCluster || clusters[0]);
+            }
+          } else if (
+            this.props.currentView !== "provisioner" &&
+            this.props.currentView !== "new-project"
+          ) {
+            this.setState({ clusters: [] });
+            setCurrentCluster(null);
+          }
+        }
+      })
+      .catch((err) => this.props.setWelcome(true));
+  };
+
+  componentDidMount() {
+    this.updateClusters();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (prevProps !== this.props) {
+      // Refresh clusters on project change
+      if (this.state.prevProjectId !== this.context.currentProject.id) {
+        this.updateClusters();
+        this.setState({ prevProjectId: this.context.currentProject.id });
+      } else if (this.props.forceRefreshClusters === true) {
+        this.updateClusters();
+        this.props.setRefreshClusters(false);
+      }
+    }
+  }
+
+  showClusterConfigModal = () => {
+    this.context.setCurrentModal("ClusterConfigModal", {
+      updateClusters: this.updateClusters,
+    });
+  };
+
+  renderContents = (): JSX.Element[] | JSX.Element => {
+    let { clusters } = this.state;
+    let { currentCluster, setCurrentCluster, currentProject } = this.context;
+
+    if (clusters.length > 0 && currentCluster) {
+      clusters.sort((a, b) => a.id - b.id);
+
+      return clusters.map((cluster: ClusterType, i: number) => {  
+        return (
+          <ClusterSection 
+            key={i} 
+            cluster={cluster} 
+            currentCluster={currentCluster}
+            currentProject={currentProject}
+            setCurrentCluster={setCurrentCluster}
+            navToClusterDashboard={() => {
+              setCurrentCluster(cluster, () => {
+                pushFiltered(this.props, "/cluster-dashboard", ["project_id"], {
+                  cluster: cluster.name,
+                });
+              });
+            }}
+          />
+        );
+      });
+    }
+
+    return (
+      <InitializeButton
+        onClick={() =>
+          this.context.setCurrentModal("ClusterInstructionsModal", {})
+        }
+      >
+        <Plus>+</Plus> Connect a Cluster
+      </InitializeButton>
+    );
+  };
+
+  render() {
+    return (
+      <>
+        {this.renderContents()}
+      </>
+    );
+  }
+}
+
+Clusters.contextType = Context;
+
+export default withRouter(Clusters);
+
+const Plus = styled.div`
+  margin-right: 10px;
+  font-size: 15px;
+`;
+
+const InitializeButton = styled.div`
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: calc(100% - 30px);
+  height: 38px;
+  margin: 10px 15px 12px;
+  font-size: 13px;
+  font-weight: 500;
+  border-radius: 3px;
+  color: #ffffff;
+  padding-bottom: 1px;
+  cursor: pointer;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const BgAccent = styled.img`
+  height: 30px;
+  background: #819bfd;
+  width: 30px;
+  border-top-left-radius: 100px;
+  max-width: 30px;
+  border-bottom-left-radius: 100px;
+  position: absolute;
+  top: 6px;
+  right: -8px;
+  border: none;
+  outline: none;
+`;

+ 2 - 2
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -83,7 +83,7 @@ class ProjectSection extends Component<PropsType, StateType> {
                 }
                 }
               >
               >
                 <ProjectIconAlt>+</ProjectIconAlt>
                 <ProjectIconAlt>+</ProjectIconAlt>
-                <ProjectLabel>Create a Project</ProjectLabel>
+                <ProjectLabel>Create a project</ProjectLabel>
               </Option>
               </Option>
             )}
             )}
           </Dropdown>
           </Dropdown>
@@ -124,7 +124,7 @@ class ProjectSection extends Component<PropsType, StateType> {
           })
           })
         }
         }
       >
       >
-        <Plus>+</Plus> Create a Project
+        <Plus>+</Plus> Create a project
       </InitializeButton>
       </InitializeButton>
     );
     );
   }
   }

+ 20 - 15
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -7,7 +7,7 @@ import settings from "assets/settings.svg";
 
 
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 
 
-import ClusterSection from "./ClusterSection";
+import Clusters from "./Clusters";
 import ProjectSectionContainer from "./ProjectSectionContainer";
 import ProjectSectionContainer from "./ProjectSectionContainer";
 import { RouteComponentProps, withRouter } from "react-router";
 import { RouteComponentProps, withRouter } from "react-router";
 import { getQueryParam, pushFiltered } from "shared/routing";
 import { getQueryParam, pushFiltered } from "shared/routing";
@@ -104,7 +104,7 @@ class Sidebar extends Component<PropsType, StateType> {
     let { currentProject } = this.context;
     let { currentProject } = this.context;
     if (currentProject) {
     if (currentProject) {
       return (
       return (
-        <>
+        <ScrollWrapper>
           <SidebarLabel>Home</SidebarLabel>
           <SidebarLabel>Home</SidebarLabel>
           <NavButton path={"/dashboard"}>
           <NavButton path={"/dashboard"}>
             <Img src={category} />
             <Img src={category} />
@@ -138,7 +138,7 @@ class Sidebar extends Component<PropsType, StateType> {
           ]) && (
           ]) && (
             <NavButton path={"/project-settings"}>
             <NavButton path={"/project-settings"}>
               <Img enlarge={true} src={settings} />
               <Img enlarge={true} src={settings} />
-              Settings
+              Project settings
             </NavButton>
             </NavButton>
           )}
           )}
 
 
@@ -147,7 +147,7 @@ class Sidebar extends Component<PropsType, StateType> {
           {this.context.hasFinishedOnboarding && (
           {this.context.hasFinishedOnboarding && (
             <>
             <>
               <SidebarLabel>Clusters</SidebarLabel>
               <SidebarLabel>Clusters</SidebarLabel>
-              <ClusterSection
+              <Clusters
                 setWelcome={this.props.setWelcome}
                 setWelcome={this.props.setWelcome}
                 currentView={currentView}
                 currentView={currentView}
                 isSelected={false}
                 isSelected={false}
@@ -156,7 +156,7 @@ class Sidebar extends Component<PropsType, StateType> {
               />
               />
             </>
             </>
           )}
           )}
-        </>
+        </ScrollWrapper>
       );
       );
     }
     }
 
 
@@ -199,6 +199,11 @@ Sidebar.contextType = Context;
 
 
 export default withRouter(withAuth(Sidebar));
 export default withRouter(withAuth(Sidebar));
 
 
+const ScrollWrapper = styled.div`
+  overflow-y: auto;
+  max-height: calc(100vh - 95px);
+`;
+
 const ProjectPlaceholder = styled.div`
 const ProjectPlaceholder = styled.div`
   background: #ffffff11;
   background: #ffffff11;
   border-radius: 5px;
   border-radius: 5px;
@@ -224,10 +229,10 @@ const NavButton = styled(SidebarLink)`
   border-radius: 5px;
   border-radius: 5px;
   position: relative;
   position: relative;
   text-decoration: none;
   text-decoration: none;
-  height: 36px;
+  height: 34px;
   margin: 5px 15px;
   margin: 5px 15px;
-  padding: 0 30px 2px 8px;
-  font-size: 14px;
+  padding: 0 30px 2px 6px;
+  font-size: 13px;
   font-family: "Work Sans", sans-serif;
   font-family: "Work Sans", sans-serif;
   color: #ffffff;
   color: #ffffff;
   cursor: ${(props: { disabled?: boolean }) =>
   cursor: ${(props: { disabled?: boolean }) =>
@@ -255,11 +260,11 @@ const NavButton = styled(SidebarLink)`
 
 
 const Img = styled.img<{ enlarge?: boolean }>`
 const Img = styled.img<{ enlarge?: boolean }>`
   padding: ${(props) => (props.enlarge ? "0 0 0 1px" : "4px")};
   padding: ${(props) => (props.enlarge ? "0 0 0 1px" : "4px")};
-  height: 23px;
-  width: 23px;
+  height: 22px;
+  width: 22px;
   padding-top: 4px;
   padding-top: 4px;
   border-radius: 3px;
   border-radius: 3px;
-  margin-right: 10px;
+  margin-right: 8px;
 `;
 `;
 
 
 const SidebarBg = styled.div`
 const SidebarBg = styled.div`
@@ -277,7 +282,7 @@ const SidebarLabel = styled.div`
   color: #ffffff99;
   color: #ffffff99;
   padding: 5px 23px;
   padding: 5px 23px;
   margin-bottom: 5px;
   margin-bottom: 5px;
-  font-size: 14px;
+  font-size: 13px;
   z-index: 1;
   z-index: 1;
   font-weight: 500;
   font-weight: 500;
 `;
 `;
@@ -363,7 +368,7 @@ const CollapseButton = styled.div`
 
 
 const StyledSidebar = styled.section`
 const StyledSidebar = styled.section`
   font-family: "Work Sans", sans-serif;
   font-family: "Work Sans", sans-serif;
-  width: 225px;
+  width: 235px;
   position: relative;
   position: relative;
   padding-top: 20px;
   padding-top: 20px;
   height: 100vh;
   height: 100vh;
@@ -373,7 +378,7 @@ const StyledSidebar = styled.section`
   animation-fill-mode: forwards;
   animation-fill-mode: forwards;
   @keyframes showSidebar {
   @keyframes showSidebar {
     from {
     from {
-      margin-left: -225px;
+      margin-left: -235px;
     }
     }
     to {
     to {
       margin-left: 0px;
       margin-left: 0px;
@@ -384,7 +389,7 @@ const StyledSidebar = styled.section`
       margin-left: 0px;
       margin-left: 0px;
     }
     }
     to {
     to {
-      margin-left: -225px;
+      margin-left: -235px;
     }
     }
   }
   }
 `;
 `;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.