|
|
@@ -1,44 +1,43 @@
|
|
|
import React, {
|
|
|
- useEffect,
|
|
|
- useState,
|
|
|
+ useCallback,
|
|
|
useContext,
|
|
|
+ useEffect,
|
|
|
useMemo,
|
|
|
- useCallback
|
|
|
+ useState,
|
|
|
} from "react";
|
|
|
-import styled from "styled-components";
|
|
|
import _ from "lodash";
|
|
|
+import { Link } from "react-router-dom";
|
|
|
+import styled from "styled-components";
|
|
|
+
|
|
|
+import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
|
|
|
+import Loading from "components/Loading";
|
|
|
+import Button from "components/porter/Button";
|
|
|
+import Container from "components/porter/Container";
|
|
|
+import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
|
|
|
+import Fieldset from "components/porter/Fieldset";
|
|
|
+import PorterLink from "components/porter/Link";
|
|
|
+import SearchBar from "components/porter/SearchBar";
|
|
|
+import ShowIntercomButton from "components/porter/ShowIntercomButton";
|
|
|
+import Spacer from "components/porter/Spacer";
|
|
|
+import Text from "components/porter/Text";
|
|
|
+import Toggle from "components/porter/Toggle";
|
|
|
+import { useAuthState } from "main/auth/context";
|
|
|
|
|
|
+import api from "shared/api";
|
|
|
+import { Context } from "shared/Context";
|
|
|
+import { hardcodedIcons } from "shared/hardcodedNameDict";
|
|
|
+import { search } from "shared/search";
|
|
|
+import { readableDate } from "shared/string_utils";
|
|
|
import addOnGrad from "assets/add-on-grad.svg";
|
|
|
-import time from "assets/time.png";
|
|
|
-import healthy from "assets/status-healthy.png";
|
|
|
import grid from "assets/grid.png";
|
|
|
import list from "assets/list.png";
|
|
|
import notFound from "assets/not-found.png";
|
|
|
-
|
|
|
-import { Context } from "shared/Context";
|
|
|
-import { search } from "shared/search";
|
|
|
-import api from "shared/api";
|
|
|
-import { hardcodedIcons } from "shared/hardcodedNameDict";
|
|
|
+import healthy from "assets/status-healthy.png";
|
|
|
+import time from "assets/time.png";
|
|
|
|
|
|
import DashboardHeader from "../cluster-dashboard/DashboardHeader";
|
|
|
|
|
|
-import Container from "components/porter/Container";
|
|
|
-import Button from "components/porter/Button";
|
|
|
-import Spacer from "components/porter/Spacer";
|
|
|
-import Text from "components/porter/Text";
|
|
|
-import SearchBar from "components/porter/SearchBar";
|
|
|
-import Toggle from "components/porter/Toggle";
|
|
|
-import { readableDate } from "shared/string_utils";
|
|
|
-import Loading from "components/Loading";
|
|
|
-import { Link } from "react-router-dom";
|
|
|
-import Fieldset from "components/porter/Fieldset";
|
|
|
-import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
|
|
|
-import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
|
|
|
-import { useAuthState } from "main/auth/context";
|
|
|
-import ShowIntercomButton from "components/porter/ShowIntercomButton";
|
|
|
-
|
|
|
-type Props = {
|
|
|
-};
|
|
|
+type Props = {};
|
|
|
|
|
|
export const RestrictedNamespaces = [
|
|
|
"ack-system",
|
|
|
@@ -61,9 +60,7 @@ const templateBlacklist = [
|
|
|
"redis-managed",
|
|
|
];
|
|
|
|
|
|
-const AddOnDashboard: React.FC<Props> = ({
|
|
|
-}) => {
|
|
|
-
|
|
|
+const AddOnDashboard: React.FC<Props> = ({}) => {
|
|
|
const { currentProject, currentCluster } = useContext(Context);
|
|
|
const [addOns, setAddOns] = useState([]);
|
|
|
const [searchValue, setSearchValue] = useState("");
|
|
|
@@ -78,14 +75,10 @@ const AddOnDashboard: React.FC<Props> = ({
|
|
|
);
|
|
|
});
|
|
|
|
|
|
- const filteredBySearch = search(
|
|
|
- filtered ?? [],
|
|
|
- searchValue,
|
|
|
- {
|
|
|
- keys: ["name", "chart.metadata.name"],
|
|
|
- isCaseSensitive: false,
|
|
|
- }
|
|
|
- );
|
|
|
+ const filteredBySearch = search(filtered ?? [], searchValue, {
|
|
|
+ keys: ["name", "chart.metadata.name"],
|
|
|
+ isCaseSensitive: false,
|
|
|
+ });
|
|
|
|
|
|
return _.sortBy(filteredBySearch);
|
|
|
}, [addOns, searchValue]);
|
|
|
@@ -120,7 +113,7 @@ const AddOnDashboard: React.FC<Props> = ({
|
|
|
setAddOns(charts);
|
|
|
} catch (err) {
|
|
|
setIsLoading(false);
|
|
|
- };
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
|
@@ -130,22 +123,25 @@ const AddOnDashboard: React.FC<Props> = ({
|
|
|
}
|
|
|
}, [currentCluster, currentProject]);
|
|
|
|
|
|
- const getExpandedChartLinkURL = useCallback((x: any) => {
|
|
|
- const params = new Proxy(new URLSearchParams(window.location.search), {
|
|
|
- get: (searchParams, prop: string) => searchParams.get(prop),
|
|
|
- });
|
|
|
- const cluster = currentCluster?.name;
|
|
|
- const route = `/applications/${cluster}/${x.namespace}/${x.name}`;
|
|
|
- const newParams = {
|
|
|
- // @ts-expect-error
|
|
|
- project_id: params.project_id,
|
|
|
- closeChartRedirectUrl: '/addons',
|
|
|
- };
|
|
|
- const newURLSearchParams = new URLSearchParams(
|
|
|
- _.omitBy(newParams, _.isNil)
|
|
|
- );
|
|
|
- return `${route}?${newURLSearchParams.toString()}`;
|
|
|
- }, [currentCluster]);
|
|
|
+ const getExpandedChartLinkURL = useCallback(
|
|
|
+ (x: any) => {
|
|
|
+ const params = new Proxy(new URLSearchParams(window.location.search), {
|
|
|
+ get: (searchParams, prop: string) => searchParams.get(prop),
|
|
|
+ });
|
|
|
+ const cluster = currentCluster?.name;
|
|
|
+ const route = `/applications/${cluster}/${x.namespace}/${x.name}`;
|
|
|
+ const newParams = {
|
|
|
+ // @ts-expect-error
|
|
|
+ project_id: params.project_id,
|
|
|
+ closeChartRedirectUrl: "/addons",
|
|
|
+ };
|
|
|
+ const newURLSearchParams = new URLSearchParams(
|
|
|
+ _.omitBy(newParams, _.isNil)
|
|
|
+ );
|
|
|
+ return `${route}?${newURLSearchParams.toString()}`;
|
|
|
+ },
|
|
|
+ [currentCluster]
|
|
|
+ );
|
|
|
|
|
|
return (
|
|
|
<StyledAppDashboard>
|
|
|
@@ -158,136 +154,140 @@ const AddOnDashboard: React.FC<Props> = ({
|
|
|
/>
|
|
|
{currentCluster?.status === "UPDATING_UNAVAILABLE" ? (
|
|
|
<ClusterProvisioningPlaceholder />
|
|
|
- ) : (
|
|
|
- currentProject?.sandbox_enabled ? (
|
|
|
+ ) : currentProject?.sandbox_enabled ? (
|
|
|
+ <DashboardPlaceholder>
|
|
|
+ <Text size={16}>Add-ons are not enabled for sandbox users</Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <Text color={"helper"}>
|
|
|
+ Eject to your own cloud account to enable Porter add-ons.
|
|
|
+ </Text>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <PorterLink to="https://docs.porter.run/other/eject">
|
|
|
+ <Button alt height="35px">
|
|
|
+ Request ejection
|
|
|
+ </Button>
|
|
|
+ </PorterLink>
|
|
|
+ </DashboardPlaceholder>
|
|
|
+ ) : addOns.length === 0 ||
|
|
|
+ (filteredAddOns.length === 0 && searchValue === "") ? (
|
|
|
+ isLoading ? (
|
|
|
+ <Loading offset="-150px" />
|
|
|
+ ) : (
|
|
|
<DashboardPlaceholder>
|
|
|
- <Text size={16}>Add-ons are not enabled for sandbox users</Text>
|
|
|
+ <Text size={16}>No add-ons have been created yet</Text>
|
|
|
<Spacer y={0.5} />
|
|
|
<Text color={"helper"}>
|
|
|
- Eject to your own cloud account to enable Porter add-ons.
|
|
|
+ Deploy from our suite of curated add-ons.
|
|
|
</Text>
|
|
|
<Spacer y={1} />
|
|
|
- <ShowIntercomButton
|
|
|
- alt
|
|
|
- message="I would like to eject to my own cloud account"
|
|
|
- height="35px"
|
|
|
- >
|
|
|
- Request ejection
|
|
|
- </ShowIntercomButton>
|
|
|
+ <Link to="/addons/new">
|
|
|
+ <Button alt onClick={() => {}} height="35px">
|
|
|
+ Deploy a new add-on <Spacer inline x={1} />{" "}
|
|
|
+ <i className="material-icons" style={{ fontSize: "18px" }}>
|
|
|
+ east
|
|
|
+ </i>
|
|
|
+ </Button>
|
|
|
+ </Link>
|
|
|
</DashboardPlaceholder>
|
|
|
- ) : (addOns.length === 0 || (filteredAddOns.length === 0 && searchValue === "")) ? (
|
|
|
-
|
|
|
- isLoading ?
|
|
|
- (<Loading offset="-150px" />) : (
|
|
|
- <DashboardPlaceholder>
|
|
|
- <Text size={16}>
|
|
|
- No add-ons have been created yet
|
|
|
- </Text>
|
|
|
- <Spacer y={0.5} />
|
|
|
- <Text color={"helper"}>
|
|
|
- Deploy from our suite of curated add-ons.
|
|
|
+ )
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <Container row spaced>
|
|
|
+ <SearchBar
|
|
|
+ value={searchValue}
|
|
|
+ setValue={setSearchValue}
|
|
|
+ placeholder="Search add-ons . . ."
|
|
|
+ width="100%"
|
|
|
+ />
|
|
|
+ <Spacer inline x={2} />
|
|
|
+ <Toggle
|
|
|
+ items={[
|
|
|
+ { label: <ToggleIcon src={grid} />, value: "grid" },
|
|
|
+ { label: <ToggleIcon src={list} />, value: "list" },
|
|
|
+ ]}
|
|
|
+ active={view}
|
|
|
+ setActive={setView}
|
|
|
+ />
|
|
|
+ <Spacer inline x={2} />
|
|
|
+ <Link to="/addons/new">
|
|
|
+ <Button onClick={() => {}} height="30px" width="130px">
|
|
|
+ <I className="material-icons">add</I> New add-on
|
|
|
+ </Button>
|
|
|
+ </Link>
|
|
|
+ </Container>
|
|
|
+ <Spacer y={1} />
|
|
|
+
|
|
|
+ {filteredAddOns.length === 0 ? (
|
|
|
+ <Fieldset>
|
|
|
+ <Container row>
|
|
|
+ <PlaceholderIcon src={notFound} />
|
|
|
+ <Text color="helper">
|
|
|
+ {searchValue === ""
|
|
|
+ ? "No add-ons have been deployed yet."
|
|
|
+ : "No matching add-ons were found."}
|
|
|
</Text>
|
|
|
- <Spacer y={1} />
|
|
|
- <Link to="/addons/new">
|
|
|
- <Button alt onClick={() => { }} height="35px" >
|
|
|
- Deploy a new add-on <Spacer inline x={1} /> <i className="material-icons" style={{ fontSize: '18px' }}>east</i>
|
|
|
- </Button>
|
|
|
- </Link>
|
|
|
- </DashboardPlaceholder>
|
|
|
- )
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <Container row spaced>
|
|
|
- <SearchBar
|
|
|
- value={searchValue}
|
|
|
- setValue={setSearchValue}
|
|
|
- placeholder="Search add-ons . . ."
|
|
|
- width="100%"
|
|
|
- />
|
|
|
- <Spacer inline x={2} />
|
|
|
- <Toggle
|
|
|
- items={[
|
|
|
- { label: <ToggleIcon src={grid} />, value: "grid" },
|
|
|
- { label: <ToggleIcon src={list} />, value: "list" },
|
|
|
- ]}
|
|
|
- active={view}
|
|
|
- setActive={setView}
|
|
|
- />
|
|
|
- <Spacer inline x={2} />
|
|
|
- <Link to="/addons/new">
|
|
|
- <Button onClick={() => { }} height="30px" width="130px">
|
|
|
- <I className="material-icons">add</I> New add-on
|
|
|
- </Button>
|
|
|
- </Link>
|
|
|
- </Container>
|
|
|
- <Spacer y={1} />
|
|
|
-
|
|
|
- {filteredAddOns.length === 0 ? (
|
|
|
- <Fieldset>
|
|
|
- <Container row>
|
|
|
- <PlaceholderIcon src={notFound} />
|
|
|
- <Text color="helper">{searchValue === "" ? "No add-ons have been deployed yet." : "No matching add-ons were found."}</Text>
|
|
|
- </Container>
|
|
|
- </Fieldset>
|
|
|
- ) : (isLoading ? <Loading offset="-150px" /> : view === "grid" ? (
|
|
|
- <GridList>
|
|
|
- {(filteredAddOns ?? []).map((app: any, i: number) => {
|
|
|
- return (
|
|
|
- <Block to={getExpandedChartLinkURL(app)} key={i}>
|
|
|
- <Container row>
|
|
|
- <Icon
|
|
|
- src={
|
|
|
- hardcodedIcons[app.chart.metadata.name] ||
|
|
|
- app.chart.metadata.icon
|
|
|
- }
|
|
|
- />
|
|
|
- <Text size={14}>{app.name}</Text>
|
|
|
- <Spacer inline x={2} />
|
|
|
- </Container>
|
|
|
- <StatusIcon src={healthy} />
|
|
|
- <Container row>
|
|
|
- <SmallIcon opacity="0.4" src={time} />
|
|
|
- <Text size={13} color="#ffffff44">
|
|
|
- {readableDate(app.info.last_deployed)}
|
|
|
- </Text>
|
|
|
- </Container>
|
|
|
- </Block>
|
|
|
- );
|
|
|
- })}
|
|
|
- </GridList>
|
|
|
- ) : (
|
|
|
- <List>
|
|
|
- {(filteredAddOns ?? []).map((app: any, i: number) => {
|
|
|
- return (
|
|
|
- <Row to={getExpandedChartLinkURL(app)} key={i}>
|
|
|
- <Container row>
|
|
|
- <MidIcon
|
|
|
- src={
|
|
|
- hardcodedIcons[app.chart.metadata.name] ||
|
|
|
- app.chart.metadata.icon
|
|
|
- }
|
|
|
- />
|
|
|
- <Text size={14}>{app.name}</Text>
|
|
|
- <Spacer inline x={1} />
|
|
|
- <MidIcon src={healthy} height="16px" />
|
|
|
- </Container>
|
|
|
- <Spacer height="15px" />
|
|
|
- <Container row>
|
|
|
- <SmallIcon opacity="0.4" src={time} />
|
|
|
- <Text size={13} color="#ffffff44">
|
|
|
- {readableDate(app.info.last_deployed)}
|
|
|
- </Text>
|
|
|
- </Container>
|
|
|
- </Row>
|
|
|
- );
|
|
|
- })}
|
|
|
- </List>
|
|
|
- )
|
|
|
- )}
|
|
|
- </>
|
|
|
- ))}
|
|
|
+ </Container>
|
|
|
+ </Fieldset>
|
|
|
+ ) : isLoading ? (
|
|
|
+ <Loading offset="-150px" />
|
|
|
+ ) : view === "grid" ? (
|
|
|
+ <GridList>
|
|
|
+ {(filteredAddOns ?? []).map((app: any, i: number) => {
|
|
|
+ return (
|
|
|
+ <Block to={getExpandedChartLinkURL(app)} key={i}>
|
|
|
+ <Container row>
|
|
|
+ <Icon
|
|
|
+ src={
|
|
|
+ hardcodedIcons[app.chart.metadata.name] ||
|
|
|
+ app.chart.metadata.icon
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <Text size={14}>{app.name}</Text>
|
|
|
+ <Spacer inline x={2} />
|
|
|
+ </Container>
|
|
|
+ <StatusIcon src={healthy} />
|
|
|
+ <Container row>
|
|
|
+ <SmallIcon opacity="0.4" src={time} />
|
|
|
+ <Text size={13} color="#ffffff44">
|
|
|
+ {readableDate(app.info.last_deployed)}
|
|
|
+ </Text>
|
|
|
+ </Container>
|
|
|
+ </Block>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </GridList>
|
|
|
+ ) : (
|
|
|
+ <List>
|
|
|
+ {(filteredAddOns ?? []).map((app: any, i: number) => {
|
|
|
+ return (
|
|
|
+ <Row to={getExpandedChartLinkURL(app)} key={i}>
|
|
|
+ <Container row>
|
|
|
+ <MidIcon
|
|
|
+ src={
|
|
|
+ hardcodedIcons[app.chart.metadata.name] ||
|
|
|
+ app.chart.metadata.icon
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <Text size={14}>{app.name}</Text>
|
|
|
+ <Spacer inline x={1} />
|
|
|
+ <MidIcon src={healthy} height="16px" />
|
|
|
+ </Container>
|
|
|
+ <Spacer height="15px" />
|
|
|
+ <Container row>
|
|
|
+ <SmallIcon opacity="0.4" src={time} />
|
|
|
+ <Text size={13} color="#ffffff44">
|
|
|
+ {readableDate(app.info.last_deployed)}
|
|
|
+ </Text>
|
|
|
+ </Container>
|
|
|
+ </Row>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </List>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
<Spacer y={5} />
|
|
|
- </StyledAppDashboard >
|
|
|
+ </StyledAppDashboard>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
@@ -299,12 +299,13 @@ const PlaceholderIcon = styled.img`
|
|
|
opacity: 0.65;
|
|
|
`;
|
|
|
|
|
|
-const Row = styled(Link) <{ isAtBottom?: boolean }>`
|
|
|
+const Row = styled(Link)<{ isAtBottom?: boolean }>`
|
|
|
cursor: pointer;
|
|
|
display: block;
|
|
|
padding: 15px;
|
|
|
- border-bottom: ${props => props.isAtBottom ? "none" : "1px solid #494b4f"};
|
|
|
- background: ${props => props.theme.clickable.bg};
|
|
|
+ border-bottom: ${(props) =>
|
|
|
+ props.isAtBottom ? "none" : "1px solid #494b4f"};
|
|
|
+ background: ${(props) => props.theme.clickable.bg};
|
|
|
position: relative;
|
|
|
border: 1px solid #494b4f;
|
|
|
border-radius: 5px;
|
|
|
@@ -335,14 +336,14 @@ const Icon = styled.img`
|
|
|
`;
|
|
|
|
|
|
const MidIcon = styled.img<{ height?: string }>`
|
|
|
- height: ${props => props.height || "18px"};
|
|
|
+ height: ${(props) => props.height || "18px"};
|
|
|
margin-right: 11px;
|
|
|
`;
|
|
|
|
|
|
const SmallIcon = styled.img<{ opacity?: string }>`
|
|
|
margin-left: 2px;
|
|
|
height: 14px;
|
|
|
- opacity: ${props => props.opacity || 1};
|
|
|
+ opacity: ${(props) => props.opacity || 1};
|
|
|
margin-right: 10px;
|
|
|
`;
|
|
|
|
|
|
@@ -353,10 +354,10 @@ const Block = styled(Link)`
|
|
|
justify-content: space-between;
|
|
|
cursor: pointer;
|
|
|
padding: 20px;
|
|
|
- color: ${props => props.theme.text.primary};
|
|
|
+ color: ${(props) => props.theme.text.primary};
|
|
|
position: relative;
|
|
|
border-radius: 5px;
|
|
|
- background: ${props => props.theme.clickable.bg};
|
|
|
+ background: ${(props) => props.theme.clickable.bg};
|
|
|
border: 1px solid #494b4f;
|
|
|
:hover {
|
|
|
border: 1px solid #7a7b80;
|
|
|
@@ -398,5 +399,5 @@ const CentralContainer = styled.div`
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
justify-content: left;
|
|
|
- align-items: left;
|
|
|
-`;
|
|
|
+ align-items: left;
|
|
|
+`;
|