فهرست منبع

Merge pull request #2454 from porter-dev/support-registry-only-source

Support registry only source
Porter Support 3 سال پیش
والد
کامیت
991a4dfd42
39فایلهای تغییر یافته به همراه750 افزوده شده و 210 حذف شده
  1. 1 0
      dashboard/src/App.tsx
  2. 0 4
      dashboard/src/assets/Iconly/Bulk/Info Square.svg
  3. 4 0
      dashboard/src/assets/folder-outline.svg
  4. 4 0
      dashboard/src/assets/last-run.svg
  5. 6 0
      dashboard/src/assets/sort.svg
  6. 6 0
      dashboard/src/assets/tag.svg
  7. 126 0
      dashboard/src/components/CheckboxList.tsx
  8. 73 0
      dashboard/src/components/CheckboxRow.tsx
  9. 2 1
      dashboard/src/components/MultiSelectFilter.tsx
  10. 252 0
      dashboard/src/components/RadioFilter.tsx
  11. 3 3
      dashboard/src/components/Table.tsx
  12. 89 56
      dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx
  13. 1 1
      dashboard/src/main/home/cluster-dashboard/DashboardHeader.tsx
  14. 10 16
      dashboard/src/main/home/cluster-dashboard/LastRunStatusSelector.tsx
  15. 10 15
      dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx
  16. 8 11
      dashboard/src/main/home/cluster-dashboard/SortSelector.tsx
  17. 15 19
      dashboard/src/main/home/cluster-dashboard/TagFilter.tsx
  18. 1 1
      dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx
  19. 2 2
      dashboard/src/main/home/cluster-dashboard/chart/JobRunTable.tsx
  20. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/NamespaceList.tsx
  21. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/NodeList.tsx
  22. 1 1
      dashboard/src/main/home/cluster-dashboard/databases/DatabasesList.tsx
  23. 1 1
      dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroup.tsx
  24. 26 18
      dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupDashboard.tsx
  25. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx
  26. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx
  27. 1 0
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/PreviewEnvironmentsHeader.tsx
  28. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentCard.tsx
  29. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx
  30. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentCard.tsx
  31. 92 44
      dashboard/src/main/home/cluster-dashboard/stacks/Dashboard.tsx
  32. 2 3
      dashboard/src/main/home/cluster-dashboard/stacks/launch/components/styles.tsx
  33. 1 1
      dashboard/src/main/home/dashboard/ClusterList.tsx
  34. 1 1
      dashboard/src/main/home/infrastructure/InfrastructureList.tsx
  35. 1 1
      dashboard/src/main/home/integrations/IntegrationList.tsx
  36. 1 1
      dashboard/src/main/home/integrations/IntegrationRow.tsx
  37. 1 1
      dashboard/src/main/home/launch/TemplateList.tsx
  38. 1 1
      dashboard/src/main/home/launch/launch-flow/SourcePage.tsx
  39. 1 1
      dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

+ 1 - 0
dashboard/src/App.tsx

@@ -24,6 +24,7 @@ const GlobalStyle = createGlobalStyle`
   * {
     box-sizing: border-box;
     font-family: 'Work Sans', sans-serif;
+    color-scheme: dark;
   }
   
   body {

+ 0 - 4
dashboard/src/assets/Iconly/Bulk/Info Square.svg

@@ -1,4 +0,0 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path opacity="0.4" d="M16.34 1.9998H7.67C4.28 1.9998 2 4.3798 2 7.9198V16.0898C2 19.6198 4.28 21.9998 7.67 21.9998H16.34C19.73 21.9998 22 19.6198 22 16.0898V7.9198C22 4.3798 19.73 1.9998 16.34 1.9998Z" fill="white"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1247 8.1893C11.1247 8.6713 11.5157 9.0643 11.9947 9.0643C12.4877 9.0643 12.8797 8.6713 12.8797 8.1893C12.8797 7.7073 12.4877 7.3143 12.0047 7.3143C11.5197 7.3143 11.1247 7.7073 11.1247 8.1893ZM12.8697 11.3621C12.8697 10.8801 12.4767 10.4871 11.9947 10.4871C11.5127 10.4871 11.1197 10.8801 11.1197 11.3621V15.7821C11.1197 16.2641 11.5127 16.6571 11.9947 16.6571C12.4767 16.6571 12.8697 16.2641 12.8697 15.7821V11.3621Z" fill="white"/>
-</svg>

+ 4 - 0
dashboard/src/assets/folder-outline.svg

@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.4446 15.7579C21.4446 19.336 19.336 21.4446 15.7579 21.4446H7.97172C4.38443 21.4446 2.27588 19.336 2.27588 15.7579V7.9626C2.27588 4.38444 3.5903 2.27588 7.16846 2.27588H9.16749C9.88576 2.27588 10.5621 2.61406 10.9931 3.18868L11.9059 4.40269C12.3378 4.97618 13.0135 5.31406 13.7315 5.31549H16.5611C20.1484 5.31549 21.472 7.14108 21.472 10.7923L21.4446 15.7579Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.05893 14.4891H16.6524" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 4 - 0
dashboard/src/assets/last-run.svg

@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.3002 12.2513L20.2502 12.2513" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.3002 7.25031L3.36317 12.2513L11.3002 17.2523L11.3002 7.25031Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 6 - 0
dashboard/src/assets/sort.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M16.8396 20.1642V6.54645" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M20.9172 16.0681L16.8394 20.1648L12.7617 16.0681" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.91112 3.83289V17.4507" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.83344 7.929L6.91121 3.83234L10.989 7.929" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 6 - 0
dashboard/src/assets/tag.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 d="M14.8055 18.9994V3" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.19465 3.00064V19" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.00065 14.8054L19 14.8054" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M18.9994 7.19465L3.00004 7.19465" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 126 - 0
dashboard/src/components/CheckboxList.tsx

@@ -0,0 +1,126 @@
+import React, { useEffect } from "react";
+import styled from "styled-components";
+
+type PropsType = {
+  label?: string;
+  options: { disabled?: boolean; value: any; label: string }[];
+  selected: { value: any; label: string }[];
+  setSelected: (x: { value: any; label: string }[]) => void;
+};
+
+const arraysEqual = (a: any, b: any) => {
+  if (a === b) return true;
+  if (a == null || b == null) return false;
+  if (a.length !== b.length) return false;
+
+  // If you don't care about the order of the elements inside
+  // the array, you should sort both arrays here.
+  // Please note that calling sort on an array will modify that array.
+  // you might want to clone your array first.
+
+  for (var i = 0; i < a.length; ++i) {
+    if (a[i] !== b[i]) return false;
+  }
+  return true;
+};
+
+const CheckboxList = ({ label, options, selected, setSelected }: PropsType) => {
+  let onSelectOption = (option: { value: any; label: string }) => {
+    const tmp = [...selected];
+    if (
+      tmp.filter(
+        (e) => e.value === option.value || arraysEqual(e.value, option.value)
+      ).length === 0
+    ) {
+      setSelected([...tmp, option]);
+    } else {
+      tmp.forEach((x, i) => {
+        if (x.value === option.value || arraysEqual(x.value, option.value)) {
+          tmp.splice(i, 1);
+        }
+      });
+      setSelected(tmp);
+    }
+  };
+
+  return (
+    <StyledCheckboxList>
+      {label && <Label>{label}</Label>}
+      {options.map((option: { value: any; label: string }, i: number) => {
+        return (
+          <CheckboxOption
+            isLast={i === options.length - 1}
+            onClick={() => onSelectOption(option)}
+            key={i}
+          >
+            <Checkbox
+              checked={
+                selected.filter(
+                  (e) =>
+                    e.value === option.value ||
+                    arraysEqual(e.value, option.value)
+                ).length > 0
+              }
+            >
+              <i className="material-icons">done</i>
+            </Checkbox>
+            <Text>{option.label}</Text>
+          </CheckboxOption>
+        );
+      })}
+    </StyledCheckboxList>
+  );
+};
+export default CheckboxList;
+
+const Text = styled.div`
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  word-break: anywhere;
+  margin-right: 10px;
+`;
+
+const Checkbox = styled.div`
+  width: 14px;
+  height: 14px;
+  min-width: 14px;
+  border: 1px solid #ffffff55;
+  margin: 1px 10px 0px 1px;
+  border-radius: 3px;
+  background: ${(props: { checked: boolean }) =>
+    props.checked ? "#ffffff22" : "#ffffff11"};
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  > i {
+    font-size: 12px;
+    padding-left: 0px;
+    display: ${(props: { checked: boolean }) => (props.checked ? "" : "none")};
+  }
+`;
+
+const CheckboxOption = styled.div<{ isLast: boolean }>`
+  width: 100%;
+  height: 35px;
+  padding-left: 10px;
+  display: flex;
+  cursor: pointer;
+  align-items: center;
+  font-size: 13px;
+
+  :hover {
+    background: #ffffff18;
+  }
+`;
+
+const Label = styled.div`
+  color: #ffffff;
+  margin-bottom: 10px;
+`;
+
+const StyledCheckboxList = styled.div`
+  border-radius: 3px;
+  padding: 0;
+`;

+ 73 - 0
dashboard/src/components/CheckboxRow.tsx

@@ -0,0 +1,73 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+
+type PropsType = {
+  label: string;
+  checked: boolean;
+  toggle: () => void;
+  isRequired?: boolean;
+  disabled?: boolean;
+};
+
+type StateType = {};
+
+export default class CheckboxRow extends Component<PropsType, StateType> {
+  render() {
+    return (
+      <StyledCheckboxRow>
+        <CheckboxWrapper
+          disabled={this.props.disabled}
+          onClick={!this.props.disabled ? this.props.toggle : undefined}
+        >
+          <Checkbox checked={this.props.checked}>
+            <i className="material-icons">done</i>
+          </Checkbox>
+          {this.props.label}
+          {this.props.isRequired && <Required>*</Required>}
+        </CheckboxWrapper>
+      </StyledCheckboxRow>
+    );
+  }
+}
+
+const Required = styled.section`
+  margin-left: 8px;
+  color: #fc4976;
+`;
+
+const CheckboxWrapper = styled.div<{ disabled?: boolean }>`
+  display: flex;
+  align-items: center;
+  cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
+  font-size: 13px;
+  :hover {
+    > div {
+      background: #ffffff22;
+    }
+  }
+`;
+
+const Checkbox = styled.div<{ checked: boolean }>`
+  width: 16px;
+  height: 16px;
+  border: 1px solid #ffffff55;
+  margin: 1px 10px 0px 1px;
+  border-radius: 3px;
+  background: ${(props) => (props.checked ? "#ffffff22" : "#ffffff11")};
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  > i {
+    font-size: 12px;
+    padding-left: 0px;
+    display: ${(props) => (props.checked ? "" : "none")};
+  }
+`;
+
+const StyledCheckboxRow = styled.div`
+  display: flex;
+  align-items: center;
+  margin-bottom: 15px;
+  margin-top: 20px;
+`;

+ 2 - 1
dashboard/src/components/MultiSelectFilter.tsx

@@ -3,7 +3,7 @@ import React, { useEffect, useState, useRef } from "react";
 import styled from "styled-components";
 import arrow from "assets/arrow-down.svg";
 
-import CheckboxList from "components/form-components/CheckboxList";
+import CheckboxList from "./CheckboxList";
 
 type Props = {
   name: string;
@@ -169,6 +169,7 @@ const Dropdown = styled.div`
   overflow-y: auto;
   margin-bottom: 20px;
   background: #2f3135;
+  padding: 0;
   border-radius: 5px;
   border: 1px solid #aaaabb33;
 `;

+ 252 - 0
dashboard/src/components/RadioFilter.tsx

@@ -0,0 +1,252 @@
+import React, { useEffect, useState, useRef } from "react";
+
+import styled from "styled-components";
+import arrow from "assets/arrow-down.svg";
+
+type Props = {
+  name: string;
+  icon?: any;
+  options: { value: any; label: string }[];
+  selected: any;
+  setSelected: any;
+  noMargin?: boolean;
+};
+
+const RadioFilter: React.FC<Props> = (props) => {
+  const [expanded, setExpanded] = useState(false);
+
+  const wrapperRef = useRef<HTMLInputElement>(null);
+  const parentRef = useRef<HTMLInputElement>(null);
+
+  useEffect(() => {
+    document.addEventListener("mousedown", handleClickOutside.bind(this));
+    return () =>
+      document.removeEventListener("mousedown", handleClickOutside.bind(this));
+  }, []);
+
+  const handleClickOutside = (event: any) => {
+    if (
+      wrapperRef &&
+      wrapperRef.current &&
+      !wrapperRef.current.contains(event.target) &&
+      parentRef &&
+      parentRef.current &&
+      !parentRef.current.contains(event.target)
+    ) {
+      setExpanded(false);
+    }
+  };
+
+  const getLabel = (value: string): any => {
+    let tgt = props.options.find(
+      (element: { value: string; label: string }) => element.value === value
+    );
+    if (tgt) {
+      return tgt.label;
+    }
+  };
+
+  const renderDropdown = () => {
+    let { options } = props;
+    if (expanded) {
+      return (
+        <DropdownWrapper>
+          <Dropdown ref={wrapperRef}>
+            {options?.length > 0 ? (
+              <ScrollableWrapper>
+                {options.map(
+                  (option: { value: any; label: string }, i: number) => {
+                    return (
+                      <OptionRow
+                        isLast={i === options.length - 1}
+                        onClick={() => props.setSelected(option.value)}
+                        key={i}
+                        selected={props.selected === option.value}
+                      >
+                        <Text>{option.label}</Text>
+                      </OptionRow>
+                    );
+                  }
+                )}
+              </ScrollableWrapper>
+            ) : (
+              <Placeholder>No options found</Placeholder>
+            )}
+          </Dropdown>
+        </DropdownWrapper>
+      );
+    }
+  };
+
+  return (
+    <Relative>
+      <StyledRadioFilter
+        onClick={() => setExpanded(!expanded)}
+        ref={parentRef}
+        noMargin={props.noMargin}
+      >
+        {props.icon && <FilterIcon src={props.icon} />}
+        {props.name}
+        <Bar />
+        <Selected>
+          {props.selected
+            ? props.selected === ""
+              ? "All"
+              : getLabel(props.selected)
+            : ""}
+        </Selected>
+        <DropdownIcon src={arrow} />
+      </StyledRadioFilter>
+      {renderDropdown()}
+    </Relative>
+  );
+};
+
+export default RadioFilter;
+
+const Bar = styled.div`
+  width: 1px;
+  height: calc(18px);
+  background: #494b4f;
+  margin: 0 8px;
+`;
+
+const Selected = styled.div`
+  color: #aaaaaa;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  max-width: 120px;
+`;
+
+const Text = styled.div`
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  word-break: anywhere;
+  margin-right: 10px;
+`;
+
+const OptionRow = styled.div<{ isLast: boolean; selected?: boolean }>`
+  width: 100%;
+  height: 35px;
+  padding-left: 10px;
+  display: flex;
+  cursor: pointer;
+  align-items: center;
+  font-size: 13px;
+  background: ${(props) => (props.selected ? "#ffffff11" : "")};
+
+  :hover {
+    background: #ffffff18;
+  }
+`;
+
+const FilterCount = styled.div`
+  padding: 5px;
+  color: #ffffff;
+  background: #ffffff11;
+  margin-left: 7px;
+  font-size: 12px;
+  border-radius: 50px;
+  margin-right: -5px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 20px;
+`;
+
+const Placeholder = styled.div`
+  color: #aaaabb88;
+  font-size: 12px;
+  width: 100%;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ScrollableWrapper = styled.div`
+  overflow-y: auto;
+  max-height: 350px;
+`;
+
+const Label = styled.div`
+  height: 37px;
+  display: flex;
+  align-items: center;
+  margin-left: 10px;
+  font-size: 13px;
+`;
+
+const Option: any = styled.div`
+  width: 100%;
+  border-top: 1px solid #00000000;
+  height: 37px;
+  font-size: 13px;
+  align-items: center;
+  display: flex;
+  align-items: center;
+  padding-left: 15px;
+  cursor: pointer;
+  padding-right: 10px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  background: ${(props: any) => (props.selected ? "#ffffff11" : "")};
+
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const Relative = styled.div`
+  position: relative;
+`;
+
+const DropdownWrapper = styled.div`
+  position: absolute;
+  width: 100%;
+  right: 0;
+  z-index: 1;
+  top: calc(100% + 5px);
+`;
+
+const Dropdown = styled.div`
+  width: 260px;
+  border-radius: 3px;
+  z-index: 999;
+  overflow-y: auto;
+  background: #2f3135;
+  padding: 0;
+  border-radius: 5px;
+  border: 1px solid #aaaabb33;
+`;
+
+const DropdownIcon = styled.img`
+  width: 8px;
+  margin-left: 12px;
+`;
+
+const FilterIcon = styled.img`
+  width: 14px;
+  margin-right: 9px;
+`;
+
+const StyledRadioFilter = styled.div<{ noMargin?: boolean }>`
+  height: 30px;
+  font-size: 13px;
+  position: relative;
+  padding: 10px;
+  background: #26292e;
+  border-radius: 5px;
+  display: flex;
+  align-items: center;
+  margin-right: ${(props) => (props.noMargin ? "" : "10px")};
+  cursor: pointer;
+  border: 1px solid #494b4f;
+  :hover {
+    border: 1px solid #7a7b80;
+  }
+`;

+ 3 - 3
dashboard/src/components/Table.tsx

@@ -365,7 +365,7 @@ const SearchInput = styled.input`
   width: 100%;
   color: white;
   padding: 0;
-  height: 20px;
+  height: 21px;
 `;
 
 const SearchRow = styled.div`
@@ -376,7 +376,7 @@ const SearchRow = styled.div`
   border-radius: 4px;
   user-select: none;
   align-items: center;
-  padding: 10px 0px;
+  padding: 7px 0px;
   min-width: 300px;
   max-width: min-content;
   background: #ffffff11;
@@ -386,7 +386,7 @@ const SearchRow = styled.div`
     height: 18px;
     margin-left: 12px;
     margin-right: 12px;
-    font-size: 20px;
+    font-size: 18px;
   }
 `;
 

+ 89 - 56
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -29,7 +29,6 @@ import LastRunStatusSelector from "./LastRunStatusSelector";
 import loadable from "@loadable/component";
 import Loading from "components/Loading";
 import JobRunTable from "./chart/JobRunTable";
-import TabSelector from "components/TabSelector";
 import TagFilter from "./TagFilter";
 
 // @ts-ignore
@@ -149,11 +148,6 @@ class ClusterDashboard extends Component<PropsType, StateType> {
 
     return (
       <>
-        <TagFilter
-          onSelect={(newSelectedTag) =>
-            this.setState({ selectedTag: newSelectedTag })
-          }
-        />
         <NamespaceSelector
           setNamespace={(namespace) =>
             this.setState({ namespace }, () => {
@@ -165,10 +159,10 @@ class ClusterDashboard extends Component<PropsType, StateType> {
           }
           namespace={this.state.namespace}
         />
-        <SortSelector
-          setSortType={(sortType) => this.setState({ sortType })}
-          sortType={this.state.sortType}
-          currentView={currentView}
+        <TagFilter
+          onSelect={(newSelectedTag) =>
+            this.setState({ selectedTag: newSelectedTag })
+          }
         />
       </>
     );
@@ -185,16 +179,23 @@ class ClusterDashboard extends Component<PropsType, StateType> {
     return (
       <>
         <ControlRow>
-          <SortFilterWrapper>{this.renderCommonFilters()}</SortFilterWrapper>
-          {isAuthorizedToAdd && (
-            <Button
-              onClick={() =>
-                pushFiltered(this.props, "/launch", ["project_id"])
-              }
-            >
-              <i className="material-icons">add</i> Launch template
-            </Button>
-          )}
+          <FilterWrapper>{this.renderCommonFilters()}</FilterWrapper>
+          <Flex>
+            <SortSelector
+              setSortType={(sortType) => this.setState({ sortType })}
+              sortType={this.state.sortType}
+              currentView={currentView}
+            />
+            {isAuthorizedToAdd && (
+              <Button
+                onClick={() =>
+                  pushFiltered(this.props, "/launch", ["project_id"])
+                }
+              >
+                <i className="material-icons">add</i> Launch template
+              </Button>
+            )}
+          </Flex>
         </ControlRow>
 
         <ChartList
@@ -219,31 +220,8 @@ class ClusterDashboard extends Component<PropsType, StateType> {
 
     return (
       <>
-        <TabSelector
-          currentTab={this.state.showRuns ? "job_runs" : "chart_list"}
-          options={[
-            { label: "Jobs", value: "chart_list" },
-            { label: "Runs", value: "job_runs" },
-          ]}
-          setCurrentTab={(value) => {
-            if (value === "job_runs") {
-              this.setState({ showRuns: true });
-            } else {
-              this.setState({ showRuns: false });
-            }
-          }}
-        />
         <ControlRow style={{ marginTop: "35px" }}>
-          {isAuthorizedToAdd && (
-            <Button
-              onClick={() =>
-                pushFiltered(this.props, "/launch", ["project_id"])
-              }
-            >
-              <i className="material-icons">add</i> Launch template
-            </Button>
-          )}
-          <SortFilterWrapper>
+          <FilterWrapper>
             <LastRunStatusSelector
               lastRunStatus={this.state.lastRunStatus}
               setLastRunStatus={(lastRunStatus: JobStatusType) => {
@@ -251,7 +229,33 @@ class ClusterDashboard extends Component<PropsType, StateType> {
               }}
             />
             {this.renderCommonFilters()}
-          </SortFilterWrapper>
+          </FilterWrapper>
+          <Flex>
+            <ToggleButton>
+              <ToggleOption
+                onClick={() => this.setState({ showRuns: false })}
+                selected={!this.state.showRuns}
+              >
+                Jobs
+              </ToggleOption>
+              <ToggleOption
+                nudgeLeft
+                onClick={() => this.setState({ showRuns: true })}
+                selected={this.state.showRuns}
+              >
+                Runs
+              </ToggleOption>
+            </ToggleButton>
+            {isAuthorizedToAdd && (
+              <Button
+                onClick={() =>
+                  pushFiltered(this.props, "/launch", ["project_id"])
+                }
+              >
+                <i className="material-icons">add</i> Launch template
+              </Button>
+            )}
+          </Flex>
         </ControlRow>
         <HidableElement show={this.state.showRuns}>
           <JobRunTable
@@ -316,6 +320,7 @@ class ClusterDashboard extends Component<PropsType, StateType> {
             image={monoweb}
             title={currentView}
             description="Continuously running web services, workers, and add-ons."
+            disableLineBreak
           />
 
           {this.renderBodyForApps()}
@@ -343,36 +348,65 @@ ClusterDashboard.contextType = Context;
 
 export default withRouter(withAuth(ClusterDashboard));
 
+const ToggleOption = styled.div<{ selected: boolean; nudgeLeft?: boolean }>`
+  padding: 0 10px;
+  color: ${(props) => (props.selected ? "" : "#494b4f")};
+  border: 1px solid #494b4f;
+  height: 100%;
+  display: flex;
+  margin-left: ${(props) => (props.nudgeLeft ? "-1px" : "")};
+  align-items: center;
+  border-radius: ${(props) =>
+    props.nudgeLeft ? "0 5px 5px 0" : "5px 0 0 5px"};
+  :hover {
+    border: 1px solid #7a7b80;
+    z-index: 999;
+  }
+`;
+
+const ToggleButton = styled.div`
+  background: #26292e;
+  border-radius: 5px;
+  font-size: 13px;
+  height: 30px;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+`;
+
 const HidableElement = styled.div<{ show: boolean }>`
   display: ${(props) => (props.show ? "unset" : "none")};
 `;
 
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+  border-bottom: 30px solid transparent;
+`;
+
 const ControlRow = styled.div`
   display: flex;
-  margin-left: auto;
   justify-content: space-between;
   align-items: center;
   flex-wrap: wrap;
-  padding-left: 0px;
 `;
 
 const Button = styled.div`
   display: flex;
   flex-direction: row;
   align-items: center;
+  margin-left: 10px;
   justify-content: space-between;
   font-size: 13px;
   cursor: pointer;
   font-family: "Work Sans", sans-serif;
   border-radius: 5px;
+  font-weight: 500;
   color: white;
-  height: 35px;
-  margin-bottom: 35px;
-  padding: 0px 8px;
+  height: 30px;
+  padding: 0 8px;
   min-width: 155px;
-  padding-bottom: 1px;
-  font-weight: 500;
-  padding-right: 15px;
+  padding-right: 13px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
@@ -401,11 +435,10 @@ const Button = styled.div`
   }
 `;
 
-const SortFilterWrapper = styled.div`
+const FilterWrapper = styled.div`
   display: flex;
   justify-content: space-between;
-  margin-bottom: 35px;
+  border-bottom: 30px solid transparent;
   > div:not(:first-child) {
-    margin-left: 30px;
   }
 `;

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

@@ -58,7 +58,7 @@ const LineBreak = styled.div`
   height: 1px;
   background: #494b4f;
   width: 100%;
-  margin: 10px 0px 35px;
+  margin: 10px 0px 15px;
 `;
 
 const TopRow = styled.div`

+ 10 - 16
dashboard/src/main/home/cluster-dashboard/LastRunStatusSelector.tsx

@@ -1,9 +1,11 @@
 import React from "react";
 import styled from "styled-components";
 
-import Selector from "components/Selector";
+import RadioFilter from "components/RadioFilter";
 import { JobStatusType } from "shared/types";
 
+import last_run from "assets/last-run.svg";
+
 type PropsType = {
   lastRunStatus: JobStatusType;
   setLastRunStatus: (lastRunStatus: JobStatusType) => void;
@@ -23,21 +25,13 @@ const LastRunStatusSelector = (props: PropsType) => {
   );
 
   return (
-    <StyledLastRunStatusSelector>
-      <Label>
-        <i className="material-icons">filter_alt</i>
-        Last Run Status
-      </Label>
-      <Selector
-        activeValue={props.lastRunStatus}
-        setActiveValue={props.setLastRunStatus}
-        options={options}
-        dropdownLabel="Last Run Status"
-        width="150px"
-        dropdownWidth="230px"
-        closeOverlay={true}
-      />
-    </StyledLastRunStatusSelector>
+    <RadioFilter
+      selected={props.lastRunStatus}
+      setSelected={props.setLastRunStatus}
+      options={options}
+      name="Last run status"
+      icon={last_run}
+    />
   );
 };
 

+ 10 - 15
dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -1,10 +1,12 @@
 import React, { useContext, useEffect, useMemo, useState } from "react";
 import styled from "styled-components";
 
+import folder from "assets/folder-outline.svg";
+
 import { Context } from "shared/Context";
 import api from "shared/api";
 
-import Selector from "components/Selector";
+import RadioFilter from "components/RadioFilter";
 
 type Props = {
   setNamespace: (x: string) => void;
@@ -102,20 +104,13 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
   };
 
   return (
-    <StyledNamespaceSelector>
-      <Label>
-        <i className="material-icons">filter_alt</i> Namespace
-      </Label>
-      <Selector
-        activeValue={namespace}
-        setActiveValue={handleSetActive}
-        options={namespaceOptions}
-        dropdownLabel="Namespace"
-        width="150px"
-        dropdownWidth="230px"
-        closeOverlay={true}
-      />
-    </StyledNamespaceSelector>
+    <RadioFilter
+      icon={folder}
+      selected={namespace}
+      setSelected={handleSetActive}
+      options={namespaceOptions}
+      name="Namespace"
+    />
   );
 };
 

+ 8 - 11
dashboard/src/main/home/cluster-dashboard/SortSelector.tsx

@@ -3,7 +3,8 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 
-import Selector from "components/Selector";
+import RadioFilter from "components/RadioFilter";
+import sort from "assets/sort.svg";
 
 type PropsType = {
   setSortType: (x: string) => void;
@@ -46,17 +47,13 @@ export default class SortSelector extends Component<PropsType, StateType> {
   render() {
     return (
       <StyledSortSelector>
-        <Label>
-          <i className="material-icons">sort</i> Sort
-        </Label>
-        <Selector
-          activeValue={this.props.sortType}
-          setActiveValue={(sortType) => this.props.setSortType(sortType)}
+        <RadioFilter
+          selected={this.props.sortType}
+          setSelected={(sortType: any) => this.props.setSortType(sortType)}
           options={this.getSortOptions()}
-          dropdownLabel="Sort By"
-          width="150px"
-          dropdownWidth="230px"
-          closeOverlay={true}
+          name="Sort"
+          icon={sort}
+          noMargin
         />
       </StyledSortSelector>
     );

+ 15 - 19
dashboard/src/main/home/cluster-dashboard/TagFilter.tsx

@@ -1,9 +1,11 @@
-import Selector from "components/Selector";
+import RadioFilter from "components/RadioFilter";
 import React, { useContext, useEffect, useState } from "react";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import styled from "styled-components";
 
+import tag from "assets/tag.svg";
+
 const TagFilter = ({ onSelect }: { onSelect: (tag: any) => void }) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [selectedTag, setSelectedTag] = useState("none");
@@ -30,24 +32,18 @@ const TagFilter = ({ onSelect }: { onSelect: (tag: any) => void }) => {
   }, [selectedTag]);
 
   return (
-    <StyledTagSelector>
-      <Label>
-        <i className="material-icons">tag</i>
-        Tag
-      </Label>
-      <Selector
-        activeValue={selectedTag}
-        options={[{ label: "No tag selected", value: "none" }].concat(
-          tags.map((tag) => ({
-            value: tag.name,
-            label: tag.name,
-          }))
-        )}
-        setActiveValue={(newVal) => setSelectedTag(newVal)}
-        width={"150px"}
-        dropdownWidth="fit-content"
-      />
-    </StyledTagSelector>
+    <RadioFilter
+      selected={selectedTag}
+      options={[{ label: "All", value: "none" }].concat(
+        tags.map((tag) => ({
+          value: tag.name,
+          label: tag.name,
+        }))
+      )}
+      setSelected={(newVal: any) => setSelectedTag(newVal)}
+      name="Tag"
+      icon={tag}
+    />
   );
 };
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -369,7 +369,7 @@ const StyledChart = styled.div`
   width: calc(100% + 2px);
   height: calc(100% + 2px);
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/chart/JobRunTable.tsx

@@ -253,7 +253,7 @@ const JobRunTable: React.FC<Props> = ({
         },
       },
       {
-        Header: "Commit/Image tag",
+        Header: "Image tag",
         id: "commit_or_image_tag",
         accessor: (originalRow) => {
           const container = originalRow.spec?.template?.spec?.containers[0];
@@ -419,7 +419,7 @@ const CommandString = styled.div`
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-  max-width: 300px;
+  max-width: 160px;
   color: #ffffff55;
   margin-right: 27px;
   font-family: monospace;

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

@@ -301,7 +301,7 @@ const StyledCard = styled.div`
     }
   }
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

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

@@ -156,7 +156,7 @@ const StyledChart = styled.div`
     margin-bottom: 25px;
   }
   border-radius: 8px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
 `;
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/databases/DatabasesList.tsx

@@ -256,7 +256,7 @@ const StyledTableWrapper = styled.div`
   padding: 14px;
   position: relative;
   border-radius: 8px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   width: 100%;
   height: 100%;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroup.tsx

@@ -203,7 +203,7 @@ const StyledEnvGroup = styled.div`
   width: calc(100% + 2px);
   height: calc(100% + 2px);
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 26 - 18
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupDashboard.tsx

@@ -69,21 +69,23 @@ class EnvGroupDashboard extends Component<PropsType, StateType> {
                 }
                 namespace={this.state.namespace}
               />
+            </SortFilterWrapper>
+            <Flex>
               <SortSelector
                 currentView="env-groups"
                 setSortType={(sortType) => this.setState({ sortType })}
                 sortType={this.state.sortType}
               />
-            </SortFilterWrapper>
-            {isAuthorizedToAdd && (
-              <Button
-                onClick={() =>
-                  this.setState({ createEnvMode: !this.state.createEnvMode })
-                }
-              >
-                <i className="material-icons">add</i> Create env group
-              </Button>
-            )}
+              {isAuthorizedToAdd && (
+                <Button
+                  onClick={() =>
+                    this.setState({ createEnvMode: !this.state.createEnvMode })
+                  }
+                >
+                  <i className="material-icons">add</i> Create env group
+                </Button>
+              )}
+            </Flex>
           </ControlRow>
 
           <EnvGroupList
@@ -129,6 +131,7 @@ class EnvGroupDashboard extends Component<PropsType, StateType> {
             image={sliders}
             title="Environment Groups"
             description="Groups of environment variables for storing secrets and configuration."
+            disableLineBreak
           />
           {this.renderBody()}
         </>
@@ -145,11 +148,17 @@ EnvGroupDashboard.contextType = Context;
 
 export default withRouter(withAuth(EnvGroupDashboard));
 
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+  border-bottom: 30px solid transparent;
+`;
+
 const SortFilterWrapper = styled.div`
   display: flex;
   justify-content: space-between;
+  border-bottom: 30px solid transparent;
   > div:not(:first-child) {
-    margin-left: 30px;
   }
 `;
 
@@ -162,12 +171,12 @@ const ControlRow = styled.div`
     return "flex-end";
   }};
   align-items: center;
-  margin-bottom: 35px;
-  padding-left: 0px;
+  flex-wrap: wrap;
 `;
 
 const Button = styled.div`
   display: flex;
+  margin-left: 10px;
   flex-direction: row;
   align-items: center;
   justify-content: space-between;
@@ -176,11 +185,10 @@ const Button = styled.div`
   font-family: "Work Sans", sans-serif;
   border-radius: 5px;
   color: white;
-  height: 35px;
-  padding: 0px 8px;
-  padding-bottom: 1px;
-  font-weight: 500;
-  padding-right: 15px;
+  height: 30px;
+  padding: 0 8px;
+  min-width: 155px;
+  padding-right: 13px;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx

@@ -497,7 +497,7 @@ const StyledJob = styled.div`
   margin-bottom: 20px;
   overflow: hidden;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/components/ButtonEnablePREnvironments.tsx

@@ -125,7 +125,7 @@ const Button = styled(DynamicLink)`
   font-family: "Work Sans", sans-serif;
   border-radius: 5px;
   color: white;
-  height: 35px;
+  height: 30px;
   padding: 0px 8px;
   padding-bottom: 1px;
   margin-right: 10px;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/components/PreviewEnvironmentsHeader.tsx

@@ -26,6 +26,7 @@ export const PreviewEnvironmentsHeader = () => {
         image={PullRequestIcon}
         title="Preview Environments"
         description="Create full-stack preview environments for your pull requests."
+        disableLineBreak
       />
       {githubStatus != "no active incidents" ? (
         <AlertCard>

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentCard.tsx

@@ -316,7 +316,7 @@ const DeploymentCardWrapper = styled.div`
   padding: 12px;
   padding-left: 14px;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
 
   animation: fadeIn 0.5s;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/PullRequestCard.tsx

@@ -141,7 +141,7 @@ const DeploymentCardWrapper = styled.div`
   padding: 12px;
   padding-left: 14px;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
 
   animation: fadeIn 0.5s;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentCard.tsx

@@ -174,7 +174,7 @@ const EnvironmentCardWrapper = styled(DynamicLink)`
   padding: 12px;
   padding-left: 14px;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 92 - 44
dashboard/src/main/home/cluster-dashboard/stacks/Dashboard.tsx

@@ -1,13 +1,12 @@
 import DynamicLink from "components/DynamicLink";
-import Selector from "components/Selector";
+import RadioFilter from "components/RadioFilter";
 import React, { useEffect, useState } from "react";
 import { useHistory, useLocation } from "react-router";
 import { useRouting } from "shared/routing";
 import styled from "styled-components";
 import DashboardHeader from "../DashboardHeader";
 import { NamespaceSelector } from "../NamespaceSelector";
-import SortSelector from "../SortSelector";
-import { Action } from "./components/styles";
+import sort from "assets/sort.svg";
 import StackList from "./_StackList";
 const Dashboard = () => {
   const [currentNamespace, setCurrentNamespace] = useState("default");
@@ -38,46 +37,43 @@ const Dashboard = () => {
         image={"lan"}
         title="Stacks"
         description="Groups of applications deployed from a shared source."
+        disableLineBreak
       />
-      <Action.Row>
+      <ControlRow>
         <FilterWrapper>
-          <StyledSortSelector>
-            <Label>
-              <i className="material-icons">sort</i> Sort
-            </Label>
-            <Selector
-              activeValue={currentSort}
-              setActiveValue={(sortType) => setCurrentSort(sortType as any)}
-              options={[
-                {
-                  value: "created_at",
-                  label: "Created At",
-                },
-                {
-                  value: "updated_at",
-                  label: "Last Updated",
-                },
-                {
-                  value: "alphabetical",
-                  label: "Alphabetical",
-                },
-              ]}
-              dropdownLabel="Sort By"
-              width="150px"
-              dropdownWidth="230px"
-              closeOverlay={true}
-            />
-          </StyledSortSelector>
           <NamespaceSelector
             namespace={currentNamespace}
             setNamespace={handleNamespaceChange}
           />
         </FilterWrapper>
-        <Action.Button to={"/stacks/launch"}>
-          <i className="material-icons">add</i>
-          Create stack
-        </Action.Button>
-      </Action.Row>
+        <Flex>
+          <RadioFilter
+            selected={currentSort}
+            noMargin
+            setSelected={(sortType: any) => setCurrentSort(sortType as any)}
+            options={[
+              {
+                value: "created_at",
+                label: "Created At",
+              },
+              {
+                value: "updated_at",
+                label: "Last Updated",
+              },
+              {
+                value: "alphabetical",
+                label: "Alphabetical",
+              },
+            ]}
+            name="Sort"
+            icon={sort}
+          />
+          <Button to={"/stacks/launch"}>
+            <i className="material-icons">add</i>
+            Create stack
+          </Button>
+        </Flex>
+      </ControlRow>
       <StackList namespace={currentNamespace} sortBy={currentSort} />
     </>
   );
@@ -85,24 +81,76 @@ const Dashboard = () => {
 
 export default Dashboard;
 
-const Label = styled.div`
+const Flex = styled.div`
   display: flex;
   align-items: center;
-  margin-right: 12px;
+  border-bottom: 30px solid transparent;
+`;
+
+const Button = styled(DynamicLink)`
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 13px;
+  cursor: pointer;
+  font-family: "Work Sans", sans-serif;
+  border-radius: 5px;
+  color: white;
+  margin-left: 10px;
+  height: 30px;
+  padding: 0 8px;
+  padding-right: 13px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  box-shadow: 0 5px 8px 0px #00000010;
+  cursor: ${(props: { disabled?: boolean }) =>
+    props.disabled ? "not-allowed" : "pointer"};
+
+  background: ${(props: { disabled?: boolean }) =>
+    props.disabled ? "#aaaabbee" : "#616FEEcc"};
+  :hover {
+    background: ${(props: { disabled?: boolean }) =>
+      props.disabled ? "" : "#505edddd"};
+  }
 
   > i {
-    margin-right: 8px;
-    font-size: 18px;
+    color: white;
+    width: 18px;
+    height: 18px;
+    font-weight: 600;
+    font-size: 12px;
+    border-radius: 20px;
+    display: flex;
+    align-items: center;
+    margin-right: 5px;
+    justify-content: center;
   }
 `;
 
-const StyledSortSelector = styled.div`
+const FilterWrapper = styled.div`
   display: flex;
+  justify-content: space-between;
+  border-bottom: 30px solid transparent;
+  > div:not(:first-child) {
+  }
+`;
+
+const ControlRow = styled.div`
+  display: flex;
+  justify-content: space-between;
   align-items: center;
-  font-size: 13px;
-  margin-right: 30px;
+  flex-wrap: wrap;
 `;
 
-const FilterWrapper = styled.div`
+const Label = styled.div`
   display: flex;
+  align-items: center;
+  margin-right: 12px;
+
+  > i {
+    margin-right: 8px;
+    font-size: 18px;
+  }
 `;

+ 2 - 3
dashboard/src/main/home/cluster-dashboard/stacks/launch/components/styles.tsx

@@ -3,10 +3,9 @@ import styled from "styled-components";
 
 export const Card = {
   Grid: styled.div`
-    margin-top: 32px;
     margin-bottom: 32px;
     display: grid;
-    grid-row-gap: 25px;
+    grid-row-gap: 15px;
   `,
   Wrapper: styled.div<{ variant?: "clickable" | "unclickable" }>`
     display: flex;
@@ -17,7 +16,7 @@ export const Card = {
     padding-left: 14px;
     align-items: center;
     border-radius: 5px;
-    background: #262a30;
+    background: #26292e;
     border: 1px solid #494b4f;
 
     ${(props) => {

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

@@ -242,7 +242,7 @@ const TemplateBlock = styled.div`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/infrastructure/InfrastructureList.tsx

@@ -226,7 +226,7 @@ const StyledTableWrapper = styled.div`
   padding: 14px;
   position: relative;
   border-radius: 8px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   width: 100%;
   height: 100%;

+ 1 - 1
dashboard/src/main/home/integrations/IntegrationList.tsx

@@ -233,7 +233,7 @@ const Integration = styled.div`
     props.disabled ? "not-allowed" : "pointer"};
   margin-bottom: 20px;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/integrations/IntegrationRow.tsx

@@ -133,7 +133,7 @@ const Integration = styled.div`
     props.disabled ? "not-allowed" : "pointer"};
   margin-bottom: 15px;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

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

@@ -207,7 +207,7 @@ const TemplateBlock = styled.div`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

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

@@ -417,7 +417,7 @@ const Block = styled.div<{ disabled?: boolean }>`
   position: relative;
 
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
   }

+ 1 - 1
dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

@@ -366,7 +366,7 @@ const Block = styled.div<{ disabled?: boolean }>`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: #262a30;
+  background: #26292e;
   border: 1px solid #494b4f;
   :hover {
     border: ${(props) => (props.disabled ? "" : "1px solid #7a7b80")};