|
|
@@ -1,19 +1,14 @@
|
|
|
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
|
-import semver from "semver";
|
|
|
import { StacksLaunchContext } from "./Store";
|
|
|
import InputRow from "components/form-components/InputRow";
|
|
|
import Selector from "components/Selector";
|
|
|
import api from "shared/api";
|
|
|
import { Context } from "shared/Context";
|
|
|
-import { ClusterType, PorterTemplate } from "shared/types";
|
|
|
import useAuth from "shared/auth/useAuth";
|
|
|
-import DynamicLink from "components/DynamicLink";
|
|
|
-import styled from "styled-components";
|
|
|
-import { useOutsideAlerter } from "shared/hooks/useOutsideAlerter";
|
|
|
-import { capitalize } from "shared/string_utils";
|
|
|
-import SaveButton from "components/SaveButton";
|
|
|
import { useRouting } from "shared/routing";
|
|
|
-import Loading from "components/Loading";
|
|
|
+import { CardGrid, SubmitButton } from "./components/styles";
|
|
|
+import { AppCard } from "./components/AppCard";
|
|
|
+import { AddResourceButton } from "./components/AddResourceButton";
|
|
|
|
|
|
const Overview = () => {
|
|
|
const {
|
|
|
@@ -125,7 +120,7 @@ const Overview = () => {
|
|
|
<br />
|
|
|
<CardGrid>
|
|
|
{newStack.app_resources.map((app) => (
|
|
|
- <Card key={app.name}>{app.name}</Card>
|
|
|
+ <AppCard key={app.name} app={app} />
|
|
|
))}
|
|
|
|
|
|
<AddResourceButton />
|
|
|
@@ -146,319 +141,3 @@ const Overview = () => {
|
|
|
};
|
|
|
|
|
|
export default Overview;
|
|
|
-
|
|
|
-const AddResourceButton = () => {
|
|
|
- const [templates, setTemplates] = useState<PorterTemplate[]>([]);
|
|
|
- const [currentTemplate, setCurrentTemplate] = useState<PorterTemplate>();
|
|
|
- const [currentVersion, setCurrentVersion] = useState("");
|
|
|
-
|
|
|
- const getTemplates = async () => {
|
|
|
- try {
|
|
|
- const res = await api.getTemplates<PorterTemplate[]>(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- repo_url: process.env.APPLICATION_CHART_REPO_URL,
|
|
|
- },
|
|
|
- {}
|
|
|
- );
|
|
|
- let sortedVersionData = res.data
|
|
|
- .map((template: PorterTemplate) => {
|
|
|
- let versions = template.versions.reverse();
|
|
|
-
|
|
|
- versions = template.versions.sort(semver.rcompare);
|
|
|
-
|
|
|
- return {
|
|
|
- ...template,
|
|
|
- versions,
|
|
|
- currentVersion: versions[0],
|
|
|
- };
|
|
|
- })
|
|
|
- .sort((a, b) => {
|
|
|
- if (a.name < b.name) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
- if (a.name > b.name) {
|
|
|
- return 1;
|
|
|
- }
|
|
|
- return 0;
|
|
|
- });
|
|
|
-
|
|
|
- return sortedVersionData;
|
|
|
- } catch (err) {}
|
|
|
- };
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- getTemplates().then((templates) => {
|
|
|
- setTemplates(templates);
|
|
|
- setCurrentTemplate(templates[0]);
|
|
|
- setCurrentVersion(templates[0].currentVersion);
|
|
|
- });
|
|
|
- }, []);
|
|
|
-
|
|
|
- return (
|
|
|
- <AddResourceButtonStyles.Wrapper>
|
|
|
- <AddResourceButtonStyles.Flex>
|
|
|
- Add a new{" "}
|
|
|
- <TemplateSelector
|
|
|
- options={templates}
|
|
|
- value={currentTemplate}
|
|
|
- onChange={(template) => {
|
|
|
- setCurrentTemplate(template);
|
|
|
- setCurrentVersion(template.currentVersion);
|
|
|
- }}
|
|
|
- />
|
|
|
- <VersionSelector
|
|
|
- options={currentTemplate?.versions || []}
|
|
|
- value={currentVersion}
|
|
|
- onChange={setCurrentVersion}
|
|
|
- />
|
|
|
- </AddResourceButtonStyles.Flex>
|
|
|
-
|
|
|
- <DynamicLink
|
|
|
- to={`/stacks/launch/new-app/${currentTemplate?.name}/${currentVersion}`}
|
|
|
- >
|
|
|
- Create
|
|
|
- </DynamicLink>
|
|
|
- </AddResourceButtonStyles.Wrapper>
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-const TemplateSelector = ({
|
|
|
- value,
|
|
|
- options,
|
|
|
- onChange,
|
|
|
-}: {
|
|
|
- value: PorterTemplate;
|
|
|
- options: PorterTemplate[];
|
|
|
- onChange: (newValue: PorterTemplate) => void;
|
|
|
-}) => {
|
|
|
- const wrapperRef = useRef();
|
|
|
-
|
|
|
- const [isExpanded, setIsExpanded] = useState(false);
|
|
|
-
|
|
|
- useOutsideAlerter(wrapperRef, () => setIsExpanded(false));
|
|
|
-
|
|
|
- const getName = (template: PorterTemplate) => {
|
|
|
- if (template?.name === "web") {
|
|
|
- return "Web Application";
|
|
|
- }
|
|
|
- return capitalize(template?.name || "");
|
|
|
- };
|
|
|
-
|
|
|
- if (!Array.isArray(options) || options.length === 0) {
|
|
|
- return (
|
|
|
- <SelectorStyles.Wrapper>
|
|
|
- <SelectorStyles.Button expanded={false}>
|
|
|
- <Loading />
|
|
|
- </SelectorStyles.Button>
|
|
|
- </SelectorStyles.Wrapper>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <>
|
|
|
- <SelectorStyles.Wrapper ref={wrapperRef}>
|
|
|
- <SelectorStyles.Button
|
|
|
- expanded={isExpanded}
|
|
|
- onClick={() => setIsExpanded((prev) => !prev)}
|
|
|
- >
|
|
|
- {getName(value)}
|
|
|
- <i className="material-icons">arrow_drop_down</i>
|
|
|
- </SelectorStyles.Button>
|
|
|
-
|
|
|
- {isExpanded ? (
|
|
|
- <SelectorStyles.Dropdown>
|
|
|
- {options.map((template) => (
|
|
|
- <SelectorStyles.Option
|
|
|
- className={template.name === value.name ? "active" : ""}
|
|
|
- onClick={() => {
|
|
|
- onChange(template);
|
|
|
- setIsExpanded(false);
|
|
|
- }}
|
|
|
- >
|
|
|
- <SelectorStyles.OptionText>
|
|
|
- {getName(template)}
|
|
|
- </SelectorStyles.OptionText>
|
|
|
- </SelectorStyles.Option>
|
|
|
- ))}
|
|
|
- </SelectorStyles.Dropdown>
|
|
|
- ) : null}
|
|
|
- </SelectorStyles.Wrapper>
|
|
|
- </>
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-const VersionSelector = ({
|
|
|
- value,
|
|
|
- options,
|
|
|
- onChange,
|
|
|
-}: {
|
|
|
- value: string;
|
|
|
- options: string[];
|
|
|
- onChange: (newValue: string) => void;
|
|
|
-}) => {
|
|
|
- const wrapperRef = useRef();
|
|
|
-
|
|
|
- const [isExpanded, setIsExpanded] = useState(false);
|
|
|
-
|
|
|
- useOutsideAlerter(wrapperRef, () => setIsExpanded(false));
|
|
|
-
|
|
|
- if (!Array.isArray(options) || options.length === 0) {
|
|
|
- return (
|
|
|
- <SelectorStyles.Wrapper>
|
|
|
- <SelectorStyles.Button expanded={false}>
|
|
|
- <Loading />
|
|
|
- </SelectorStyles.Button>
|
|
|
- </SelectorStyles.Wrapper>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <>
|
|
|
- <SelectorStyles.Wrapper ref={wrapperRef}>
|
|
|
- <SelectorStyles.Button
|
|
|
- expanded={isExpanded}
|
|
|
- onClick={() => setIsExpanded((prev) => !prev)}
|
|
|
- >
|
|
|
- {capitalize(value)}
|
|
|
- <i className="material-icons">arrow_drop_down</i>
|
|
|
- </SelectorStyles.Button>
|
|
|
-
|
|
|
- {isExpanded ? (
|
|
|
- <SelectorStyles.Dropdown>
|
|
|
- {options.map((version) => (
|
|
|
- <SelectorStyles.Option
|
|
|
- className={version === value ? "active" : ""}
|
|
|
- onClick={() => {
|
|
|
- onChange(version);
|
|
|
- setIsExpanded(false);
|
|
|
- }}
|
|
|
- >
|
|
|
- {capitalize(version)}
|
|
|
- </SelectorStyles.Option>
|
|
|
- ))}
|
|
|
- </SelectorStyles.Dropdown>
|
|
|
- ) : null}
|
|
|
- </SelectorStyles.Wrapper>
|
|
|
- </>
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-const CardGrid = styled.div`
|
|
|
- margin-top: 32px;
|
|
|
- margin-bottom: 32px;
|
|
|
- display: grid;
|
|
|
- grid-row-gap: 25px;
|
|
|
-`;
|
|
|
-
|
|
|
-const Card = styled.div`
|
|
|
- display: flex;
|
|
|
- color: #ffffff;
|
|
|
- background: #2b2e3699;
|
|
|
- justify-content: space-between;
|
|
|
- border-radius: 5px;
|
|
|
- cursor: pointer;
|
|
|
- height: 75px;
|
|
|
- padding: 12px;
|
|
|
- padding-left: 14px;
|
|
|
- border: 1px solid #ffffff0f;
|
|
|
-
|
|
|
- :hover {
|
|
|
- border: 1px solid #ffffff3c;
|
|
|
- }
|
|
|
- animation: fadeIn 0.5s;
|
|
|
- @keyframes fadeIn {
|
|
|
- from {
|
|
|
- opacity: 0;
|
|
|
- }
|
|
|
- to {
|
|
|
- opacity: 1;
|
|
|
- }
|
|
|
- }
|
|
|
-`;
|
|
|
-
|
|
|
-const SubmitButton = styled(SaveButton)`
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
-`;
|
|
|
-
|
|
|
-const AddResourceButtonStyles = {
|
|
|
- Wrapper: styled(Card)`
|
|
|
- align-items: center;
|
|
|
- `,
|
|
|
- Text: styled.span`
|
|
|
- font-size: 20px;
|
|
|
- `,
|
|
|
- Flex: styled.div`
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- `,
|
|
|
-};
|
|
|
-
|
|
|
-const SelectorStyles = {
|
|
|
- Wrapper: styled.div`
|
|
|
- max-width: 200px;
|
|
|
- position: relative;
|
|
|
- font-size: 13px;
|
|
|
-
|
|
|
- margin-left: 10px;
|
|
|
- `,
|
|
|
- Button: styled.div`
|
|
|
- background-color: #ffffff11;
|
|
|
- border: 1px solid #ffffff22;
|
|
|
- border-radius: 5px;
|
|
|
- min-width: 115px;
|
|
|
- min-height: 30px;
|
|
|
- padding: 0 15px;
|
|
|
-
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
-
|
|
|
- white-space: nowrap;
|
|
|
- overflow-y: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- > i {
|
|
|
- font-size: 20px;
|
|
|
- transform: ${(props: { expanded: boolean }) =>
|
|
|
- props.expanded ? "rotate(180deg)" : ""};
|
|
|
- }
|
|
|
- `,
|
|
|
- Dropdown: styled.div`
|
|
|
- position: absolute;
|
|
|
- background-color: #26282f;
|
|
|
- width: 100%;
|
|
|
- max-height: 200px;
|
|
|
- overflow-y: auto;
|
|
|
- `,
|
|
|
- Option: styled.div`
|
|
|
- min-height: 35px;
|
|
|
- padding: 0 15px;
|
|
|
-
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &.active {
|
|
|
- background-color: #32343c;
|
|
|
- }
|
|
|
-
|
|
|
- :hover {
|
|
|
- background-color: #32343c;
|
|
|
- }
|
|
|
-
|
|
|
- :not(:last-child) {
|
|
|
- border-bottom: 1px solid #ffffff15;
|
|
|
- }
|
|
|
- `,
|
|
|
- OptionText: styled.span`
|
|
|
- max-width: 115px;
|
|
|
- overflow-x: hidden;
|
|
|
- white-space: nowrap;
|
|
|
- text-overflow: ellipsis;
|
|
|
- `,
|
|
|
-};
|