|
|
@@ -2,46 +2,90 @@ import React, { useContext, useEffect, useMemo, useState } from "react";
|
|
|
import { Context } from "shared/Context";
|
|
|
import api from "shared/api";
|
|
|
import styled from "styled-components";
|
|
|
-import Selector from "components/Selector";
|
|
|
-
|
|
|
import Loading from "components/Loading";
|
|
|
-
|
|
|
import _ from "lodash";
|
|
|
import DeploymentCard from "./DeploymentCard";
|
|
|
-import { Environment, PRDeployment, PullRequest } from "../types";
|
|
|
+import { PRDeployment, PullRequest } from "../types";
|
|
|
import { useRouting } from "shared/routing";
|
|
|
import { useHistory, useLocation, useParams } from "react-router";
|
|
|
import { deployments, pull_requests } from "../mocks";
|
|
|
-import PullRequestCard from "./PullRequestCard";
|
|
|
import DynamicLink from "components/DynamicLink";
|
|
|
-import { PreviewEnvironmentsHeader } from "../components/PreviewEnvironmentsHeader";
|
|
|
-import SearchBar from "components/SearchBar";
|
|
|
-import CheckboxRow from "components/form-components/CheckboxRow";
|
|
|
-import DocsHelper from "components/DocsHelper";
|
|
|
-import EnvironmentSettings from "../environments/EnvironmentSettings";
|
|
|
-
|
|
|
-const AvailableStatusFilters = [
|
|
|
- "all",
|
|
|
- "created",
|
|
|
- "failed",
|
|
|
- "active",
|
|
|
- "inactive",
|
|
|
- "not_deployed",
|
|
|
-];
|
|
|
+import DashboardHeader from "../../DashboardHeader";
|
|
|
+import RadioFilter from "components/RadioFilter";
|
|
|
+import Placeholder from "components/Placeholder";
|
|
|
+import Banner from "components/Banner";
|
|
|
+import Modal from "main/home/modals/Modal";
|
|
|
+
|
|
|
+import pullRequestIcon from "assets/pull_request_icon.svg";
|
|
|
+import filterOutline from "assets/filter-outline.svg";
|
|
|
+import sort from "assets/sort.svg";
|
|
|
+import { search } from "shared/search";
|
|
|
+import { getPRDeploymentList, validatePorterYAML } from "../utils";
|
|
|
+
|
|
|
+const AvailableStatusFilters = ["all", "created", "failed", "not_deployed"];
|
|
|
|
|
|
type AvailableStatusFiltersType = typeof AvailableStatusFilters[number];
|
|
|
|
|
|
+const HARD_CODED_DEPLOYMENTS: PRDeployment[] = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ created_at: "2021-03-01T00:00:00.000Z",
|
|
|
+ updated_at: "2021-03-01T00:00:00.000Z",
|
|
|
+ subdomain: "subdomain",
|
|
|
+ status: "created",
|
|
|
+ environment_id: 1,
|
|
|
+ pull_request_id: 1,
|
|
|
+ namespace: "namespace",
|
|
|
+ last_workflow_run_url: "",
|
|
|
+ gh_installation_id: 1,
|
|
|
+ gh_deployment_id: 1,
|
|
|
+ gh_pr_name: "gh_pr_name",
|
|
|
+ gh_repo_owner: "meehawk",
|
|
|
+ gh_repo_name: "meehawk",
|
|
|
+ gh_commit_sha: "3659ef050a687da4d04bb870b27058bd9d1957be",
|
|
|
+ gh_pr_branch_from: "gh_pr_branch_from",
|
|
|
+ gh_pr_branch_into: "gh_pr_branch_into",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ created_at: "2021-03-01T00:00:00.000Z",
|
|
|
+ updated_at: "2021-03-01T00:00:00.000Z",
|
|
|
+ subdomain: "subdomain",
|
|
|
+ status: "created",
|
|
|
+ environment_id: 1,
|
|
|
+ pull_request_id: 1,
|
|
|
+ namespace: "namespace",
|
|
|
+ last_workflow_run_url: "",
|
|
|
+ gh_installation_id: 1,
|
|
|
+ gh_deployment_id: 1,
|
|
|
+ gh_pr_name: "some_awesome_pr",
|
|
|
+ gh_repo_owner: "godzilla",
|
|
|
+ gh_repo_name: "kong",
|
|
|
+ gh_commit_sha: "3659ef050a687da4d04bb870b27058bd9d1957be",
|
|
|
+ gh_pr_branch_from: "gh_pr_branch_from",
|
|
|
+ gh_pr_branch_into: "gh_pr_branch_into",
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
const DeploymentList = () => {
|
|
|
+ const [sortOrder, setSortOrder] = useState("Newest");
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
const [hasError, setHasError] = useState(false);
|
|
|
- const [deploymentList, setDeploymentList] = useState<PRDeployment[]>([]);
|
|
|
+ const [deploymentList, setDeploymentList] = useState<PRDeployment[]>(
|
|
|
+ HARD_CODED_DEPLOYMENTS
|
|
|
+ );
|
|
|
const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);
|
|
|
const [searchValue, setSearchValue] = useState("");
|
|
|
+ const [newCommentsDisabled, setNewCommentsDisabled] = useState(false);
|
|
|
+ const [porterYAMLErrors, setPorterYAMLErrors] = useState<string[]>([]);
|
|
|
+ const [expandedPorterYAMLErrors, setExpandedPorterYAMLErrors] = useState<
|
|
|
+ string[]
|
|
|
+ >([]);
|
|
|
|
|
|
const [
|
|
|
statusSelectorVal,
|
|
|
setStatusSelectorVal,
|
|
|
- ] = useState<AvailableStatusFiltersType>("active");
|
|
|
+ ] = useState<AvailableStatusFiltersType>("all");
|
|
|
|
|
|
const { currentProject, currentCluster } = useContext(Context);
|
|
|
const { getQueryParam, pushQueryParams } = useRouting();
|
|
|
@@ -55,18 +99,16 @@ const DeploymentList = () => {
|
|
|
|
|
|
const selectedRepo = `${repo_owner}/${repo_name}`;
|
|
|
|
|
|
- const getPRDeploymentList = () => {
|
|
|
- return api.getPRDeploymentList(
|
|
|
+ const getEnvironment = () => {
|
|
|
+ return api.getEnvironment(
|
|
|
"<token>",
|
|
|
- {
|
|
|
- environment_id: Number(environment_id),
|
|
|
- },
|
|
|
+ {},
|
|
|
{
|
|
|
project_id: currentProject.id,
|
|
|
cluster_id: currentCluster.id,
|
|
|
+ environment_id: Number(environment_id),
|
|
|
}
|
|
|
);
|
|
|
- // return mockRequest();
|
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
|
@@ -90,28 +132,72 @@ const DeploymentList = () => {
|
|
|
let isSubscribed = true;
|
|
|
setIsLoading(true);
|
|
|
|
|
|
- getPRDeploymentList().then(({ data }) => {
|
|
|
- const deploymentList = data;
|
|
|
-
|
|
|
- if (!isSubscribed) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ Promise.allSettled([
|
|
|
+ validatePorterYAML({
|
|
|
+ projectID: currentProject.id,
|
|
|
+ clusterID: currentCluster.id,
|
|
|
+ environmentID: Number(environment_id),
|
|
|
+ }),
|
|
|
+ getPRDeploymentList({
|
|
|
+ projectID: currentProject.id,
|
|
|
+ clusterID: currentCluster.id,
|
|
|
+ environmentID: Number(environment_id),
|
|
|
+ }),
|
|
|
+ getEnvironment(),
|
|
|
+ ])
|
|
|
+ .then(
|
|
|
+ ([
|
|
|
+ validatePorterYAMLResponse,
|
|
|
+ getDeploymentsResponse,
|
|
|
+ getEnvironmentResponse,
|
|
|
+ ]) => {
|
|
|
+ const deploymentList =
|
|
|
+ getDeploymentsResponse.status === "fulfilled"
|
|
|
+ ? getDeploymentsResponse.value.data
|
|
|
+ : {};
|
|
|
+ const environmentList =
|
|
|
+ getEnvironmentResponse.status === "fulfilled"
|
|
|
+ ? getEnvironmentResponse.value.data
|
|
|
+ : {};
|
|
|
+ const porterYAMLErrors =
|
|
|
+ validatePorterYAMLResponse.status === "fulfilled"
|
|
|
+ ? validatePorterYAMLResponse.value.data.errors
|
|
|
+ : [];
|
|
|
+
|
|
|
+ if (!isSubscribed) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setPorterYAMLErrors(porterYAMLErrors);
|
|
|
+ setDeploymentList(
|
|
|
+ deploymentList.deployments || HARD_CODED_DEPLOYMENTS
|
|
|
+ );
|
|
|
+ setPullRequests(deploymentList.pull_requests || []);
|
|
|
|
|
|
- setDeploymentList(deploymentList.deployments || []);
|
|
|
- setPullRequests(deploymentList.pull_requests || []);
|
|
|
+ setNewCommentsDisabled(
|
|
|
+ environmentList.new_comments_disabled || false
|
|
|
+ );
|
|
|
|
|
|
- setIsLoading(false);
|
|
|
- });
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .catch(() => {
|
|
|
+ setDeploymentList(HARD_CODED_DEPLOYMENTS);
|
|
|
+ });
|
|
|
|
|
|
return () => {
|
|
|
isSubscribed = false;
|
|
|
};
|
|
|
- }, [currentCluster, currentProject]);
|
|
|
+ }, [currentCluster, currentProject, environment_id]);
|
|
|
|
|
|
const handleRefresh = async () => {
|
|
|
setIsLoading(true);
|
|
|
try {
|
|
|
- const { data } = await getPRDeploymentList();
|
|
|
+ const { data } = await getPRDeploymentList({
|
|
|
+ projectID: currentProject.id,
|
|
|
+ clusterID: currentCluster.id,
|
|
|
+ environmentID: Number(environment_id),
|
|
|
+ });
|
|
|
setDeploymentList(data.deployments || []);
|
|
|
setPullRequests(data.pull_requests || []);
|
|
|
} catch (error) {
|
|
|
@@ -141,29 +227,29 @@ const DeploymentList = () => {
|
|
|
};
|
|
|
|
|
|
const filteredDeployments = useMemo(() => {
|
|
|
- // Only filter out inactive when status filter is "active"
|
|
|
- if (statusSelectorVal === "active") {
|
|
|
- return deploymentList
|
|
|
- .filter((d) => {
|
|
|
- return d.status !== "inactive";
|
|
|
- })
|
|
|
- .filter((d) => {
|
|
|
- return Object.values(d).find(searchFilter) !== undefined;
|
|
|
- });
|
|
|
- }
|
|
|
+ const filteredByStatus = deploymentList.filter(
|
|
|
+ (d) => !["deleted", "inactive"].includes(d.status)
|
|
|
+ );
|
|
|
|
|
|
- if (statusSelectorVal === "inactive") {
|
|
|
- return deploymentList
|
|
|
- .filter((d) => {
|
|
|
- return d.status === "inactive";
|
|
|
- })
|
|
|
- .filter((d) => {
|
|
|
- return Object.values(d).find(searchFilter) !== undefined;
|
|
|
- });
|
|
|
- }
|
|
|
+ const filteredBySearch = search<PRDeployment>(
|
|
|
+ filteredByStatus,
|
|
|
+ searchValue,
|
|
|
+ {
|
|
|
+ isCaseSensitive: false,
|
|
|
+ keys: ["gh_pr_name", "gh_repo_name", "gh_repo_owner"],
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- return deploymentList;
|
|
|
- }, [statusSelectorVal, deploymentList, searchValue]);
|
|
|
+ switch (sortOrder) {
|
|
|
+ case "Newest":
|
|
|
+ return _.sortBy(filteredBySearch, "updated_at").reverse();
|
|
|
+ case "Oldest":
|
|
|
+ return _.sortBy(filteredBySearch, "updated_at");
|
|
|
+ case "Alphabetical":
|
|
|
+ default:
|
|
|
+ return _.sortBy(filteredBySearch, "gh_pr_name");
|
|
|
+ }
|
|
|
+ }, [statusSelectorVal, deploymentList, searchValue, sortOrder]);
|
|
|
|
|
|
const filteredPullRequests = useMemo(() => {
|
|
|
if (statusSelectorVal !== "inactive") {
|
|
|
@@ -178,24 +264,24 @@ const DeploymentList = () => {
|
|
|
const renderDeploymentList = () => {
|
|
|
if (isLoading) {
|
|
|
return (
|
|
|
- <Placeholder>
|
|
|
+ <LoadingWrapper>
|
|
|
<Loading />
|
|
|
- </Placeholder>
|
|
|
+ </LoadingWrapper>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (!deploymentList.length && !pullRequests.length) {
|
|
|
+ if (!deploymentList.length) {
|
|
|
return (
|
|
|
- <Placeholder>
|
|
|
+ <Placeholder height="calc(100vh - 400px)">
|
|
|
No preview apps have been found. Open a PR to create a new preview
|
|
|
app.
|
|
|
</Placeholder>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (!filteredDeployments.length && !filteredPullRequests.length) {
|
|
|
+ if (!filteredDeployments.length) {
|
|
|
return (
|
|
|
- <Placeholder>
|
|
|
+ <Placeholder height="calc(100vh - 400px)">
|
|
|
No preview apps have been found with the given filter.
|
|
|
</Placeholder>
|
|
|
);
|
|
|
@@ -203,7 +289,8 @@ const DeploymentList = () => {
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- {filteredPullRequests.map((pr) => {
|
|
|
+ {/* Deprecated -> New Preview Env button */}
|
|
|
+ {/* {filteredPullRequests.map((pr) => {
|
|
|
return (
|
|
|
<PullRequestCard
|
|
|
key={pr.pr_title}
|
|
|
@@ -211,8 +298,8 @@ const DeploymentList = () => {
|
|
|
onCreation={handlePreviewEnvironmentManualCreation}
|
|
|
/>
|
|
|
);
|
|
|
- })}
|
|
|
- {filteredDeployments.map((d) => {
|
|
|
+ })} */}
|
|
|
+ {filteredDeployments.map((d: any) => {
|
|
|
return (
|
|
|
<DeploymentCard
|
|
|
key={d.id}
|
|
|
@@ -227,25 +314,87 @@ const DeploymentList = () => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- const handleStatusFilterChange = (value: string) => {
|
|
|
- pushQueryParams({ status_filter: value });
|
|
|
- setStatusSelectorVal(value);
|
|
|
+ const handleToggleCommentStatus = (currentlyDisabled: boolean) => {
|
|
|
+ api
|
|
|
+ .toggleNewCommentForEnvironment(
|
|
|
+ "<token>",
|
|
|
+ {
|
|
|
+ disable: !currentlyDisabled,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ project_id: currentProject.id,
|
|
|
+ cluster_id: currentCluster.id,
|
|
|
+ environment_id: Number(environment_id),
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then(() => {
|
|
|
+ setNewCommentsDisabled(!currentlyDisabled);
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ pushQueryParams({ status_filter: statusSelectorVal });
|
|
|
+ }, [statusSelectorVal]);
|
|
|
+
|
|
|
return (
|
|
|
<>
|
|
|
- <PreviewEnvironmentsHeader />
|
|
|
- <Flex>
|
|
|
- <BackButton to={"/preview-environments"} className="material-icons">
|
|
|
- keyboard_backspace
|
|
|
- </BackButton>
|
|
|
-
|
|
|
- <Icon
|
|
|
- src="https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"
|
|
|
- alt="git repository icon"
|
|
|
- />
|
|
|
- <Title>{selectedRepo}</Title>
|
|
|
-
|
|
|
+ {expandedPorterYAMLErrors.length && (
|
|
|
+ <Modal
|
|
|
+ onRequestClose={() => setExpandedPorterYAMLErrors([])}
|
|
|
+ height="auto"
|
|
|
+ >
|
|
|
+ <Message>
|
|
|
+ {expandedPorterYAMLErrors.map((el) => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ {"- "}
|
|
|
+ {el}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </Message>
|
|
|
+ </Modal>
|
|
|
+ )}
|
|
|
+ <BreadcrumbRow>
|
|
|
+ <Breadcrumb to="/preview-environments">
|
|
|
+ <ArrowIcon src={pullRequestIcon} />
|
|
|
+ <Wrap>Preview environments</Wrap>
|
|
|
+ </Breadcrumb>
|
|
|
+ </BreadcrumbRow>
|
|
|
+ <DashboardHeader
|
|
|
+ image="https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png"
|
|
|
+ title={
|
|
|
+ <Flex>
|
|
|
+ <StyledLink
|
|
|
+ to={`https://github.com/${selectedRepo}`}
|
|
|
+ target="_blank"
|
|
|
+ >
|
|
|
+ {selectedRepo}
|
|
|
+ </StyledLink>
|
|
|
+ <DynamicLink
|
|
|
+ to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/settings`}
|
|
|
+ >
|
|
|
+ <I className="material-icons">more_vert</I>
|
|
|
+ </DynamicLink>
|
|
|
+ </Flex>
|
|
|
+ }
|
|
|
+ description={`Preview environments for the ${selectedRepo} repository.`}
|
|
|
+ disableLineBreak
|
|
|
+ capitalize={false}
|
|
|
+ />
|
|
|
+ {porterYAMLErrors.length > 0 ? (
|
|
|
+ <Banner type="error">
|
|
|
+ Your porter.yaml file has errors. Please fix them before deploying.
|
|
|
+ <LinkButton
|
|
|
+ onClick={() => {
|
|
|
+ setExpandedPorterYAMLErrors(porterYAMLErrors);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ View details
|
|
|
+ </LinkButton>
|
|
|
+ </Banner>
|
|
|
+ ) : null}
|
|
|
+ {/* <Flex>
|
|
|
<ActionsWrapper>
|
|
|
<StyledStatusSelector>
|
|
|
<RefreshButton color={"#7d7d81"} onClick={handleRefresh}>
|
|
|
@@ -282,8 +431,54 @@ const DeploymentList = () => {
|
|
|
<EnvironmentSettings environmentId={environment_id} />
|
|
|
</StyledStatusSelector>
|
|
|
</ActionsWrapper>
|
|
|
- </Flex>
|
|
|
-
|
|
|
+ </Flex> */}
|
|
|
+ <FlexRow>
|
|
|
+ <Flex>
|
|
|
+ <SearchRowWrapper>
|
|
|
+ <SearchBarWrapper>
|
|
|
+ <i className="material-icons">search</i>
|
|
|
+ <SearchInput
|
|
|
+ value={searchValue}
|
|
|
+ onChange={(e: any) => {
|
|
|
+ setSearchValue(e.target.value);
|
|
|
+ }}
|
|
|
+ placeholder="Search"
|
|
|
+ />
|
|
|
+ </SearchBarWrapper>
|
|
|
+ </SearchRowWrapper>
|
|
|
+ <RadioFilter
|
|
|
+ icon={filterOutline}
|
|
|
+ selected={statusSelectorVal}
|
|
|
+ setSelected={setStatusSelectorVal}
|
|
|
+ options={AvailableStatusFilters.map((filter) => ({
|
|
|
+ value: filter,
|
|
|
+ label: _.startCase(filter),
|
|
|
+ }))}
|
|
|
+ name="Status"
|
|
|
+ />
|
|
|
+ </Flex>
|
|
|
+ <Flex>
|
|
|
+ <RefreshButton color={"#7d7d81"} onClick={handleRefresh}>
|
|
|
+ <i className="material-icons">refresh</i>
|
|
|
+ </RefreshButton>
|
|
|
+ <RadioFilter
|
|
|
+ icon={sort}
|
|
|
+ selected={sortOrder}
|
|
|
+ setSelected={setSortOrder}
|
|
|
+ options={[
|
|
|
+ { label: "Newest", value: "Newest" },
|
|
|
+ { label: "Oldest", value: "Oldest" },
|
|
|
+ { label: "Alphabetical", value: "Alphabetical" },
|
|
|
+ ]}
|
|
|
+ name="Sort"
|
|
|
+ />
|
|
|
+ <CreatePreviewEnvironmentButton
|
|
|
+ to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/create`}
|
|
|
+ >
|
|
|
+ <i className="material-icons">add</i> New preview deployment
|
|
|
+ </CreatePreviewEnvironmentButton>
|
|
|
+ </Flex>
|
|
|
+ </FlexRow>
|
|
|
<Container>
|
|
|
<EventsGrid>{renderDeploymentList()}</EventsGrid>
|
|
|
</Container>
|
|
|
@@ -304,6 +499,85 @@ const mockRequest = () =>
|
|
|
);
|
|
|
});
|
|
|
|
|
|
+const LoadingWrapper = styled.div`
|
|
|
+ padding-top: 100px;
|
|
|
+`;
|
|
|
+
|
|
|
+const I = styled.i`
|
|
|
+ font-size: 18px;
|
|
|
+ user-select: none;
|
|
|
+ margin-left: 15px;
|
|
|
+ color: #aaaabb;
|
|
|
+ margin-bottom: -3px;
|
|
|
+ cursor: pointer;
|
|
|
+ width: 30px;
|
|
|
+ border-radius: 40px;
|
|
|
+ height: 30px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ :hover {
|
|
|
+ background: #26292e;
|
|
|
+ border: 1px solid #494b4f;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const StyledLink = styled(DynamicLink)`
|
|
|
+ color: white;
|
|
|
+ :hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const LinkButton = styled.a`
|
|
|
+ text-decoration: underline;
|
|
|
+ margin-left: 7px;
|
|
|
+ cursor: pointer;
|
|
|
+`;
|
|
|
+
|
|
|
+const Message = styled.div`
|
|
|
+ padding: 20px;
|
|
|
+ background: #26292e;
|
|
|
+ border-radius: 5px;
|
|
|
+ line-height: 1.5em;
|
|
|
+ border: 1px solid #aaaabb33;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-top: 40px;
|
|
|
+`;
|
|
|
+
|
|
|
+const BreadcrumbRow = styled.div`
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+`;
|
|
|
+
|
|
|
+const ArrowIcon = styled.img`
|
|
|
+ width: 15px;
|
|
|
+ margin-right: 8px;
|
|
|
+ opacity: 50%;
|
|
|
+`;
|
|
|
+
|
|
|
+const Wrap = styled.div`
|
|
|
+ z-index: 999;
|
|
|
+`;
|
|
|
+
|
|
|
+const Breadcrumb = styled(DynamicLink)`
|
|
|
+ color: #aaaabb88;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: -10px;
|
|
|
+ z-index: 999;
|
|
|
+ padding: 5px;
|
|
|
+ padding-right: 7px;
|
|
|
+ border-radius: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+ :hover {
|
|
|
+ background: #ffffff11;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
const Flex = styled.div`
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -333,7 +607,6 @@ const Icon = styled.img`
|
|
|
width: 25px;
|
|
|
height: 25px;
|
|
|
margin-right: 6px;
|
|
|
- margin-left: 14px;
|
|
|
`;
|
|
|
|
|
|
const Title = styled.div`
|
|
|
@@ -345,11 +618,6 @@ const Title = styled.div`
|
|
|
color: #ffffff;
|
|
|
`;
|
|
|
|
|
|
-const ActionsWrapper = styled.div`
|
|
|
- display: flex;
|
|
|
- margin-left: auto;
|
|
|
-`;
|
|
|
-
|
|
|
const RefreshButton = styled.button`
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -372,26 +640,6 @@ const RefreshButton = styled.button`
|
|
|
}
|
|
|
`;
|
|
|
|
|
|
-const Placeholder = styled.div`
|
|
|
- padding: 30px;
|
|
|
- padding-bottom: 40px;
|
|
|
- font-size: 13px;
|
|
|
- color: #ffffff44;
|
|
|
- min-height: 400px;
|
|
|
- height: 40vh;
|
|
|
- border-radius: 8px;
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- flex-direction: column;
|
|
|
-
|
|
|
- > i {
|
|
|
- font-size: 18px;
|
|
|
- margin-right: 8px;
|
|
|
- }
|
|
|
-`;
|
|
|
-
|
|
|
const Container = styled.div`
|
|
|
margin-top: 33px;
|
|
|
padding-bottom: 120px;
|
|
|
@@ -419,30 +667,84 @@ const SearchInput = styled.input`
|
|
|
background: none;
|
|
|
width: 100%;
|
|
|
color: white;
|
|
|
- padding: 0;
|
|
|
- height: 20px;
|
|
|
+ height: 100%;
|
|
|
`;
|
|
|
|
|
|
const SearchRow = styled.div`
|
|
|
display: flex;
|
|
|
- width: 100%;
|
|
|
- font-size: 13px;
|
|
|
- color: #ffffff55;
|
|
|
- border-radius: 4px;
|
|
|
- user-select: none;
|
|
|
align-items: center;
|
|
|
- padding: 10px 0px;
|
|
|
- min-width: 300px;
|
|
|
- max-width: min-content;
|
|
|
- max-height: 35px;
|
|
|
- background: #ffffff11;
|
|
|
- margin-right: 15px;
|
|
|
+ height: 30px;
|
|
|
+ margin-right: 10px;
|
|
|
+ background: #26292e;
|
|
|
+ border-radius: 5px;
|
|
|
+ border: 1px solid #aaaabb33;
|
|
|
+`;
|
|
|
+
|
|
|
+const SearchRowWrapper = styled(SearchRow)`
|
|
|
+ border-radius: 5px;
|
|
|
+ width: 250px;
|
|
|
+`;
|
|
|
+
|
|
|
+const SearchBarWrapper = styled.div`
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ > i {
|
|
|
+ color: #aaaabb;
|
|
|
+ padding-top: 1px;
|
|
|
+ margin-left: 8px;
|
|
|
+ font-size: 16px;
|
|
|
+ margin-right: 8px;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const FlexRow = styled.div`
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+`;
|
|
|
+
|
|
|
+const CreatePreviewEnvironmentButton = styled(DynamicLink)`
|
|
|
+ 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: 30px;
|
|
|
+ padding: 0 8px;
|
|
|
+ min-width: 155px;
|
|
|
+ padding-right: 13px;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ 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 {
|
|
|
+ > i {
|
|
|
+ color: white;
|
|
|
width: 18px;
|
|
|
height: 18px;
|
|
|
- margin-left: 12px;
|
|
|
- margin-right: 12px;
|
|
|
- font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 12px;
|
|
|
+ border-radius: 20px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-right: 5px;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
`;
|