diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b51e8ef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ + +FROM ubuntu:20.04 + +RUN apt-get update +RUN apt-get --force-yes upgrade -y +RUN apt-get dist-upgrade +RUN apt-get install -y build-essential +RUN apt-get install sudo + +RUN apt-get install --yes curl +RUN apt update +RUN curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - +RUN apt-get --force-yes install -y nodejs +RUN apt-get install --yes build-essential +RUN apt-get install --only-upgrade bash +RUN node -v + +RUN mkdir app +RUN chown -Rh $user:$user /app +USER $user + +ADD . /app +WORKDIR /app + +RUN find . -name "node_modules" -exec rm -rf '{}' + +RUN sudo chmod -R 777 /app +RUN ls && npm install +EXPOSE 3000 + +CMD [ "npm", "run", "dev"] \ No newline at end of file diff --git a/components/chart/aggregate-chart.tsx b/components/chart/aggregate-chart.tsx new file mode 100644 index 0000000..74d1cec --- /dev/null +++ b/components/chart/aggregate-chart.tsx @@ -0,0 +1,35 @@ +import { + ChartDonut, Chart, + ChartAxis, + ChartGroup, + ChartLine, + ChartVoronoiContainer, +} from "@patternfly/react-charts"; +import React from "react"; +import { AnyProps } from "../models/props"; + +const AggregateChart = ({ type, props }: AnyProps) => { + if (type === "donut") + return (<>>); + else if (type === "month") + return (<> `${datum.name}: ${datum.y}`} + constrainToVisibleArea + /> + } > + + + + {props.chartData.map((data: AnyProps) => ( + + ))} + + >); + return (<>>); +} + +export default AggregateChart; diff --git a/components/chart/deployment-week.tsx b/components/chart/deployment-week.tsx new file mode 100644 index 0000000..d4a6104 --- /dev/null +++ b/components/chart/deployment-week.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import { + Chart, + ChartAxis, + ChartGroup, + ChartLine, + ChartVoronoiContainer, +} from "@patternfly/react-charts"; +import { + Text, TextContent, TextVariants +} from "@patternfly/react-core"; +import styled from 'styled-components'; +import { AnyProps, Properties } from "../models/props"; +import AggregateChart from "./aggregate-chart"; + +const ChartBorder = styled.div({ + height: "250px", + width: "600px", + background: "white", + border: "1px solid #D2D2D2;", + opacity: "1;", +}); + +const chartAxisTickValues = [50, 150, 250]; +const dependentAxisTickValues = [50, 150, 250]; + +const DeploymentWeek = ({ webprop }: Properties) => { + const chartData = webprop.processedMonthlyDeployments; + const legendData = webprop.legendData; + const chartType = "month"; + const padding = { + bottom: 50, + left: 50, + right: 200, + top: 50, + }; + const areaConfig = { + ariaDesc: "Deployment/Week", + ariaTitle: "Deployment/Week", + legendOrientation: "vertical", + legendPosition: "right", + maxDomain: { y: 300 }, + minDomain: { y: 0 }, + height: 250, + width: 600 + }; + const chartConfig = { + ariaDesc: areaConfig.ariaDesc, + ariaTitle: areaConfig.ariaTitle, + legendData: legendData, + legendOrientation: areaConfig.legendOrientation, + legendPosition: areaConfig.legendPosition, + height: areaConfig.height, + maxDomain: areaConfig.maxDomain, + minDomain: areaConfig.minDomain, + padding: padding, + width: areaConfig.width, + chartData: chartData, + chartAxisTickValues: chartAxisTickValues, + dependentAxisTickValues: dependentAxisTickValues + }; + return ( + <> + + Deployment/Week + + + + + + + > + ); +} + +export default DeploymentWeek; \ No newline at end of file diff --git a/components/chart/total-deployment.tsx b/components/chart/total-deployment.tsx new file mode 100644 index 0000000..a12ec59 --- /dev/null +++ b/components/chart/total-deployment.tsx @@ -0,0 +1,62 @@ +import { + ChartThemeColor +} from "@patternfly/react-charts"; +import { + Text, TextContent, TextVariants +} from "@patternfly/react-core"; +import React from "react"; +import styled from 'styled-components'; +import { Properties } from "../models/props"; +import AggregateChart from "./aggregate-chart"; + +const ChartBorder = styled.div({ + left: "600px", + height: "250px", + width: "350px", + border: "1px solid #D2D2D2;", + opacity: "1;", +}); + +const TotalDeployment = ({ webprop }: Properties) => { + const chartType = "donut"; + const padding = { + bottom: 20, + left: 20, + right: 140, + top: 20, + }; + const areaConfig = { + ariaDesc: "Number of Deployment", + ariaTitle: "Number of Deployment", + legendOrientation: "vertical", + subTitle: "Deployments", + constrainToVisibleArea: true, + width: 350 + }; + const chartConfig = { + ariaDesc: areaConfig.ariaDesc, + ariaTitle: areaConfig.ariaTitle, + constrainToVisibleArea: areaConfig.constrainToVisibleArea, + labels: ({ datum }: any) => `${datum.x}: ${datum.y}%`, + data: webprop.chartData, + legendData: webprop.labelData, + legendOrientation: areaConfig.legendOrientation, + padding: padding, + subTitle: areaConfig.subTitle, + title: webprop.count, + themeColor: ChartThemeColor.multiOrdered, + width: areaConfig.width, + }; + return ( + <> + + Total Deployment + + + + + > + ); +} + +export default TotalDeployment; diff --git a/components/models/chart.ts b/components/models/chart.ts new file mode 100644 index 0000000..7163ef3 --- /dev/null +++ b/components/models/chart.ts @@ -0,0 +1,11 @@ +import { AnyProps } from "./props"; + + +export interface DataPoint { + count?: AnyProps; + spaName: AnyProps; + envs: AnyProps; + contextPath: AnyProps; + propertyName: AnyProps; + createdAt: AnyProps; +} diff --git a/components/models/props.ts b/components/models/props.ts new file mode 100644 index 0000000..b2f8156 --- /dev/null +++ b/components/models/props.ts @@ -0,0 +1,39 @@ +export interface Properties { + webprop: AnyProps; +} + +export interface ContextProps { + contex: AnyProps; +} + +export interface WebProps { + id: string; + webPropertyName: string; + count: string; +} + +export interface SPAProps { + id: string; + count: string; + spaName: string; + propertyName: string; + contextPath: string; +} + +export interface ActivityProps { + id: string; + spaName: string; + code: string; + envs: string; + branch: string; + createdAt: string; + propertyName: string; +} + +export interface SPAIndexProps { + activites: Properties, + totalDeployments: Properties, + monthlyDeployments: Properties, +} + +export type AnyProps = PropsType \ No newline at end of file diff --git a/components/settings/apiKey.tsx b/components/settings/apiKey.tsx new file mode 100644 index 0000000..6f37b17 --- /dev/null +++ b/components/settings/apiKey.tsx @@ -0,0 +1,146 @@ +import { + Button, ClipboardCopy, Form, + FormGroup, Modal, + ModalVariant, Popover, + TextInput +} from "@patternfly/react-core"; +import { + Caption, TableComposable, Tbody, + Td, Tr +} from "@patternfly/react-table"; +import React, { useEffect, useRef, useState } from "react"; +import styled from "styled-components"; +import { post } from "../../utils/api.utils"; +import { getHost } from "../../utils/config.utils"; +import { AnyProps } from "../models/props"; + +const ApiKeyBox = styled.div({ + top: "40px", + width: "1041px;", + height: "55px;", + border: "1px solid #D2D2D2;", + borderRadius: "8px;", + opacity: "1;", +}); + +const ClipboardBox = styled.div({ + width: "500px", + height: "40px", +}); + +const ApiKey = () => { + const [isModalOpen, setModalOpen] = useState(false); + const [env, setEnv] = useState(""); + const [apiKey, setApiKey] = useState(""); + const nameInputRef = useRef(); + + async function handleModalToggle() { + setModalOpen(!isModalOpen); + } + + async function onClickMethod() { + setModalOpen(!isModalOpen); + + if (isModalOpen == true) { + try { + const host = getHost(); + const url = `${host}/applications/validate`; + const payload = {}; + const response = await post(url, payload); + setApiKey(response.token); + } catch (e) { } + } + } + + const handleNameInputChange = (value: string) => { + setEnv(value); + }; + + useEffect(() => { + }, [isModalOpen]); + + return ( + <> + + + Do you want to create an API Key ! + + + + + + + Create API Key + + + Generate API Key + , + ]} + > + + + e.preventDefault()} + aria-describedby="modal-with-form-form-name" + className="pf-c-form__group-label-help" + > + + + } + isRequired + fieldId="modal-with-form-form-name" + > + + + + + + + + + {apiKey} + + + + + + + + > + ); +}; + +export default ApiKey; diff --git a/components/settings/deleteSpa.tsx b/components/settings/deleteSpa.tsx new file mode 100644 index 0000000..9e9dbf8 --- /dev/null +++ b/components/settings/deleteSpa.tsx @@ -0,0 +1,111 @@ +import { + Button, + Form, + FormGroup, Modal, + ModalVariant, Popover, + TextInput +} from "@patternfly/react-core"; +import { + Caption, TableComposable, Tbody, + Td, Tr +} from "@patternfly/react-table"; +import React, { useState } from "react"; +import styled from 'styled-components'; + +const DeleteBox = styled.div({ + top: "40px", + width: "1041px;", + height: "55px;", + border: "1px solid #D2D2D2;", + borderRadius: "8px;", + opacity: "1;", +}); + +const DeleteSpa = () => { + const [isModalOpen, setModalOpen] = useState(false); + const [env, setEnv] = useState(""); + const tableVariant = "compact"; + const tableBorders = false; + const handleModalToggle = async () => { + setModalOpen(!isModalOpen); + } + const onClickMethod = async () => { + setModalOpen(!isModalOpen); + }; + const handleNameInputChange = (value: string) => { + setEnv(value); + }; + return ( + <> + + + Do you want to create an API Key ! + + + + + + + Delete Web Property + + + Yes, Delete + , + + Cancel + , + ]} + > + + + e.preventDefault()} + aria-describedby="modal-with-form-form-name" + className="pf-c-form__group-label-help" + > + + + } + isRequired + fieldId="modal-with-form-form-name" + > + + + + + + + + + + > + ); +}; + +export default DeleteSpa; diff --git a/components/settings/manageSpa.tsx b/components/settings/manageSpa.tsx new file mode 100644 index 0000000..aa38482 --- /dev/null +++ b/components/settings/manageSpa.tsx @@ -0,0 +1,67 @@ +import { + Skeleton, + Switch +} from "@patternfly/react-core"; +import { + Caption, TableComposable, Tbody, + Td, Tr, Th +} from "@patternfly/react-table"; +import React, { useState } from "react"; +import styled from "styled-components"; +import { SPAProps, Properties } from "../models/props"; + +const ListBox = styled.div({ + width: "1041px;", + height: "180px;", + background: "var(---ffffff) 0% 0% no-repeat padding-box;", + border: "1px solid #D2D2D2;", + borderRadius: "8px;", + opacity: "1;", +}); + +const ManageSpa = ({ webprop }: Properties) => { + const [switchState, setSwitchState] = useState(true); + const handleChange = () => { + }; + const tableVariant = "compact"; + const tableBorders = false; + return ( + <> + + + + Manage SPAs + + + + Name + Path + Action + + + + {webprop.map((spa: SPAProps) => ( + + {spa.spaName} + /{spa.contextPath} + + + + + ))} + + + + > + ); +}; + +export default ManageSpa; diff --git a/components/web-property/activityStream.tsx b/components/web-property/activityStream.tsx new file mode 100644 index 0000000..cebf0a8 --- /dev/null +++ b/components/web-property/activityStream.tsx @@ -0,0 +1,49 @@ +import { + Label, List, + ListItem, Text, TextContent, TextVariants +} from "@patternfly/react-core"; +import { KeyIcon } from "@patternfly/react-icons"; +import styled from 'styled-components'; +import { ActivityProps, Properties } from "../models/props"; + +const DivStyle = styled.div({ + height: "590px", + width: "1017px", + border: "1px solid #D2D2D2;", + opacity: "1;", +}); + +const BulletStyle = styled.text({ + content: "•", + color: 'red', +}); + +const ActivityStream = ({ webprop }: Properties) => { + return ( + <> + + Activity Stream + + + + + + {webprop.map((activity: ActivityProps) => ( + + + + + {activity.spaName} has been deployed over + {activity.propertyName} on {activity.envs} at {activity.createdAt} + + + + ))} + + + + > + ); +}; + +export default ActivityStream; diff --git a/components/web-property/addProperty.tsx b/components/web-property/addProperty.tsx new file mode 100644 index 0000000..cc70a1a --- /dev/null +++ b/components/web-property/addProperty.tsx @@ -0,0 +1,38 @@ +import { + Card, EmptyState, + EmptyStateIcon, + EmptyStateVariant, Title +} from "@patternfly/react-core"; +import { PlusCircleIcon } from "@patternfly/react-icons"; +import styled from "styled-components"; + +const AddPropertyBox = styled(Card)({ + borderRadius: "8px", + opacity: 1, + height: "199px;", +}); + +const AddProperty = () => { + return ( + <> + + + + + + + + + + New Web Property + + + + + > + ); +}; + +export default AddProperty; diff --git a/components/web-property/spaProperty.tsx b/components/web-property/spaProperty.tsx new file mode 100644 index 0000000..09d7586 --- /dev/null +++ b/components/web-property/spaProperty.tsx @@ -0,0 +1,46 @@ +import { + Card, CardBody, + CardFooter, CardTitle, + Gallery, + PageSection +} from "@patternfly/react-core"; +import { useRouter } from "next/router"; +import styled from 'styled-components'; +import { Properties, SPAProps } from "../models/props"; + +const CardStyle = styled(Card)({ + background: " #F0F0F0 0% 0% no-repeat padding-box", + opacity: 1, + borderRadius: "8px", + height: "199px;", +}); + +const SPAProperty = ({ webprop }: Properties) => { + const router = useRouter(); + return ( + <> + + + + {webprop.map((prop: SPAProps) => ( + router.push(`${prop.propertyName}/spa/${prop.spaName}`)} + > + + {prop.spaName} + {prop.propertyName} + {prop.count} Deployments + + + ))} + + + > + ); +}; + +export default SPAProperty; diff --git a/components/web-property/webProperty.tsx b/components/web-property/webProperty.tsx new file mode 100644 index 0000000..4a12967 --- /dev/null +++ b/components/web-property/webProperty.tsx @@ -0,0 +1,39 @@ +import { + Card, CardBody, + CardFooter, CardTitle +} from "@patternfly/react-core"; +import { useRouter } from "next/router"; +import styled from 'styled-components'; +import { Properties, WebProps } from "../models/props"; + +const CardStyle = styled(Card)({ + background: " #F0F0F0 0% 0% no-repeat padding-box", + opacity: 1, + borderRadius: "8px", + height: "199px;", +}); + +const WebProperty = ({ webprop }: Properties) => { + const router = useRouter(); + return ( + <> + {webprop.map((prop: WebProps) => ( + router.push(`properties/${prop.webPropertyName}`)} + > + + {prop.webPropertyName} + Deployed + {prop.count} Deployments + + + ))} + > + ); +}; + +export default WebProperty; diff --git a/pages/properties/[propertyName]/index.tsx b/pages/properties/[propertyName]/index.tsx new file mode 100644 index 0000000..57801c0 --- /dev/null +++ b/pages/properties/[propertyName]/index.tsx @@ -0,0 +1,113 @@ +import { PageSection } from "@patternfly/react-core"; +import { useRouter } from "next/router"; +import React from "react"; +import styled from 'styled-components'; +import Header from "../../../components/layout/header"; +import { DataPoint } from "../../../components/models/chart"; +import { AnyProps, ContextProps, Properties } from "../../../components/models/props"; +import ActivityStream from "../../../components/web-property/activityStream"; +import SPAProperty from "../../../components/web-property/spaProperty"; +import { get, post } from "../../../utils/api.utils"; +import { getHost } from "../../../utils/config.utils"; + +export const DividerComp = styled.hr` + border-top: 1px solid var(--spaship-global--Color--bright-gray); + width: 60vw; +`; + +export const getStaticPaths = async () => { + const host = getHost(); + const url = `${host}/webproperty/list`; + const propertyListResponse = await get(url); + const paths = propertyListResponse.map((property: AnyProps) => ({ + params: { propertyName: property.webPropertyName }, + })) + return { paths, fallback: false } +} + +export const getStaticProps = async (context: ContextProps) => { + const propertyReq = getPropertyRequest(context); + const host = getHost(); + const urlList = `${host}/webproperty/getspalist/${propertyReq}`; + const urlEvent = `${host}/event/fetch/analytics/filter`; + const payloadActivites = { + "activities": { + "propertyName": propertyReq + } + }; + const payloadCount = { + "count": { + "propertyName": propertyReq + } + }; + const response = await Promise.all([await get(urlList), await post(urlEvent, payloadActivites), await post(urlEvent, payloadCount)]); + const [listResponse, activitesResponse, countResponse]: AnyProps = response; + let processedListResponse: DataPoint[] = []; + const checkSpa = new Set(); + if (listResponse) { + const data = await listResponse; + processedListResponse = processProperties(data, checkSpa, processedListResponse); + } + for (let i in processedListResponse) { + let obj = countResponse.find((prop: AnyProps) => prop.spaName === processedListResponse[i].spaName); + processedListResponse[i].count = obj?.count || 0; + } + return { + props: { webprop: processedListResponse, activites: activitesResponse }, + }; +}; + +const WebPropertyPage = ({ webprop, activites }: AnyProps) => { + const router = useRouter(); + const propertyName = router.query.propertyName; + return ( + <> + + + + + + + + + + + + > + ); +}; + +export default WebPropertyPage; + +function getPropertyRequest(context: AnyProps) { + return context.params.propertyName; +} + +function getSpaName(eachSpa: AnyProps): string { + if (!eachSpa.spaName) return ''; + return eachSpa?.spaName?.trim().replace(/^\/|\/$/g, '') || null; +} + +function processProperties(data: AnyProps, checkSpa: Set, response: AnyProps) { + for (let prop of data) { + let spas = prop?.spa; + for (let eachSpa of spas) { + const reqSpaName = getSpaName(eachSpa); + if (eachSpa?.spaName && reqSpaName.length > 0 && !checkSpa.has(reqSpaName)) { + checkSpa.add(reqSpaName); + response.push({ + spaName: reqSpaName, + envs: eachSpa.envs, + contextPath: eachSpa.contextPath, + propertyName: prop.webPropertyName, + createdAt: prop.createdAt + }); + } + } + } + return response; +} diff --git a/pages/properties/[propertyName]/settings/index.tsx b/pages/properties/[propertyName]/settings/index.tsx new file mode 100644 index 0000000..1a370af --- /dev/null +++ b/pages/properties/[propertyName]/settings/index.tsx @@ -0,0 +1,91 @@ +import { useRouter } from "next/router"; +import React from "react"; +import Header from "../../../../components/layout/header"; +import { DataPoint } from "../../../../components/models/chart"; +import { AnyProps, ContextProps, Properties } from "../../../../components/models/props"; +import ApiKey from "../../../../components/settings/apiKey"; +import DeleteSpa from "../../../../components/settings/deleteSpa"; +import ManageSpa from "../../../../components/settings/manageSpa"; +import { get } from "../../../../utils/api.utils"; +import { getHost } from "../../../../utils/config.utils"; + +export const getStaticPaths = async () => { + const host = getHost(); + const url = `${host}/webproperty/list`; + const propertyListResponse = await get(url); + const paths = propertyListResponse.map((property: AnyProps) => ({ + params: { propertyName: property.webPropertyName }, + })) + return { paths, fallback: false } +} + +export const getStaticProps = async (context: ContextProps) => { + const propertyReq = getPropertyRequest(context); + const host = getHost(); + const urlList = `${host}/webproperty/getspalist/${propertyReq}`; + const response = await get(urlList); + let listResponse: DataPoint[] = []; + const checkSpa = new Set(); + if (response) { + const data = await response; + listResponse = processProperties(data, checkSpa, listResponse); + } + return { + props: { webprop: listResponse }, + }; +}; + +const SettingsPage = ({ webprop }: Properties) => { + const router = useRouter(); + const propertyName = router.query.propertyName; + return ( + <> + + + + + + + + + > + ); +}; + +export default SettingsPage; + +function getPropertyRequest(context: AnyProps) { + return context.params.propertyName; +} + +function getSpaName(eachSpa: AnyProps): string { + if (!eachSpa.spaName) return ''; + return eachSpa?.spaName.trim().replace(/^\/|\/$/g, '') || null; +} + +function processProperties(data: AnyProps, checkSpa: Set, response: AnyProps) { + for (let item of data) { + let spas = item?.spa; + for (let eachSpa of spas) { + const reqSpaName = getSpaName(eachSpa); + if (eachSpa?.spaName && reqSpaName.length > 0 && !checkSpa.has(reqSpaName)) { + checkSpa.add(reqSpaName); + response.push({ + spaName: reqSpaName, + envs: eachSpa.envs, + contextPath: eachSpa.contextPath, + propertyName: item.webPropertyName, + createdAt: item.createdAt + }); + } + } + } + return response; +} + + diff --git a/pages/properties/[propertyName]/spa/[spaName].tsx b/pages/properties/[propertyName]/spa/[spaName].tsx new file mode 100644 index 0000000..68724c4 --- /dev/null +++ b/pages/properties/[propertyName]/spa/[spaName].tsx @@ -0,0 +1,156 @@ +import { + Divider, PageSection, Gallery, GalleryItem +} from "@patternfly/react-core"; +import React from "react"; +import DeploymentWeek from "../../../../components/chart/deployment-week"; +import TotalDeployment from "../../../../components/chart/total-deployment"; +import ActivityStream from "../../../../components/web-property/activityStream"; +import { post } from "../../../../utils/api.utils"; +import styled from 'styled-components'; +import { SPAIndexProps, Properties, AnyProps, ContextProps } from "../../../../components/models/props"; +import { getHost } from "../../../../utils/config.utils"; +import { useRouter } from "next/router"; +import Header from "../../../../components/layout/header"; + +export const DividerComp = styled.hr` + border-top: 1px solid var(--spaship-global--Color--bright-gray); + width: 60vw; +`; + +export const getStaticPaths = async () => { + const host = getHost(); + const url = `${host}/event/fetch/analytics/all`; + const payload = { + "count": { + "spa": true + } + } + const response = await post(url, payload); + const paths: AnyProps = []; + for (let prop of response) { + if (prop?.propertyName && prop?.spaName) + paths.push({ params: { propertyName: prop?.propertyName, spaName: prop?.spaName } }); + } + // response.map((property: AnyProps) => ({ + // params: { propertyName: property?.propertyName, spaName: property?.spaName }, + // })) + return { paths, fallback: false } +} + +export const getStaticProps = async (context: ContextProps) => { + const propertyReq = getPropertyReq(context); + const spaReq = getSpaReq(context); + const host = getHost(); + const url = `${host}/event/fetch/analytics/filter`; + const payloadActivites = { + "activities": { + "propertyName": propertyReq, + "spaName": spaReq, + } + }; + const payloadTotalDeploymenets = { + "count": { + "propertyName": propertyReq, + "spaName": spaReq, + } + }; + const payloadMonthlyDeploymenets = { + "chart": { + "month": true, + "propertyName": propertyReq, + "spaName": spaReq, + } + }; + const response = await Promise.all([await post(url, payloadActivites), await post(url, payloadTotalDeploymenets), await post(url, payloadMonthlyDeploymenets)]); + const [activitesResponse, totalDeploymentsResponse, monthlyDeploymentResponse]: AnyProps = response; + let chartData: AnyProps = []; + let labelData: AnyProps = []; + let count = 0; + if (totalDeploymentsResponse) { + for (let item of totalDeploymentsResponse) { + count = processTotalDeployments(item, count, chartData, labelData); + } + } + const processedMonthlyDeployments = []; + const legendData = []; + let tempLegendData: AnyProps = new Set; + for (const item in monthlyDeploymentResponse) { + const data = monthlyDeploymentResponse[item]; + const temp = []; + let i = 1; + for (const prop of data) { + tempLegendData.add(prop.envs); + temp.push({ name: prop.envs, x: `week ${i++}`, y: prop?.count }) + } + processedMonthlyDeployments.push(temp); + } + for (let env of tempLegendData) { + legendData.push({ name: env }) + } + return { + props: { activites: activitesResponse, totalDeployments: { chartData: chartData, labelData: labelData, count: count }, monthlyDeployments: { processedMonthlyDeployments: processedMonthlyDeployments, legendData: legendData } }, + }; +}; + + + +const SPAProperties = ({ activites, totalDeployments, monthlyDeployments }: SPAIndexProps) => { + const maxWidths = { + 'md': '780px', + 'lg': '380px', + '2xl': '400px' + }; + const router = useRouter(); + const propertyName = router.query.propertyName; + const spaName = router.query.spaName; + return ( + <> + + + + + + + + + + + + + + + > + ); +}; + +export default SPAProperties; + +function getSpaReq(context: AnyProps) { + return context.params.spaName; +} + +function getPropertyReq(context: AnyProps) { + return context.params.propertyName; +} + +function processTotalDeployments(item: AnyProps, count: number, chartData: AnyProps, labelData: AnyProps) { + const value = JSON.parse(JSON.stringify(item)); + count += value.count; + const dataPoint = { + x: value.env, + y: value.count + }; + chartData.push(dataPoint); + const label = { + name: value.env + " : " + value.count + }; + labelData.push(label); + return count; +} + diff --git a/pages/properties/index.tsx b/pages/properties/index.tsx new file mode 100644 index 0000000..217ff8a --- /dev/null +++ b/pages/properties/index.tsx @@ -0,0 +1,67 @@ +import { + Gallery, + PageSection +} from "@patternfly/react-core"; +import styled from 'styled-components'; +import Header from "../../components/layout/header"; +import { AnyProps, Properties } from "../../components/models/props"; +import AddProperty from "../../components/web-property/addProperty"; +import WebProperty from "../../components/web-property/webProperty"; +import { get, post } from "../../utils/api.utils"; +import { getHost } from "../../utils/config.utils"; + +export const payload = { + "count": { + "all": true + } +}; + +export const getStaticProps = async () => { + const host = getHost(); + const urlList = `${host}/webproperty/list`; + const urlCount = `${host}/event/fetch/analytics/all`; + const response = await Promise.all([await get(urlList), await post(urlCount, payload)]); + const [propertyListResponse, deploymentCountResponse]: AnyProps = response; + getPropertyListResponse(propertyListResponse, deploymentCountResponse); + return { + props: { webprop: propertyListResponse }, + }; +}; + +export const DividerComp = styled.hr` + border-top: 1px solid var(--spaship-global--Color--bright-gray); + width: 60vw; +`; + + +export const GalleryStyle = styled(Gallery)` + padding : 0 10vw +`; + +const HomePage = ({ webprop }: Properties) => { + return ( + <> + + + + + + + + + + + + > + ); +}; + +HomePage.authenticationEnabled = true; +export default HomePage; + +function getPropertyListResponse(propertyListResponse: AnyProps, deploymentCountResponse: AnyProps) { + for (let index in propertyListResponse) { + let data = deploymentCountResponse.find((property: AnyProps) => property.propertyName === propertyListResponse[index].webPropertyName); + propertyListResponse[index].count = data?.count || 0; + } +} \ No newline at end of file diff --git a/styles/globals.css b/styles/globals.css index 9ffbc6b..fdb59bf 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,13 +1,13 @@ :root { - --spaship-global--Color--spaship-gray: #333333; - --spaship-global--Color--bright-gray: #EAEAEA; - --spaship-global--Color--sonic-silver: #72767B; - --spaship-global--Color--solar-orange: #FDB716; - --spaship-global--Color--amarillo-flare: #FED402; - --spaship-global--Color--munsell-yellow: #F1C904; + --spaship-global--Color--bright-gray: #eaeaea; + --spaship-global--Color--sonic-silver: #72767b; + --spaship-global--Color--solar-orange: #fdb716; + --spaship-global--Color--amarillo-flare: #fed402; + --spaship-global--Color--munsell-yellow: #f1c904; --spaship-global--Color--text-black: #151515; - --spaship-global--Color--ui-blue: #0066CC; - --spaship-global--Font-Family: RedHatDisplay, Overpass, Roboto, Cantarell, helvetica, arial, sans-serif; + --spaship-global--Color--ui-blue: #0066cc; + --spaship-global--Font-Family: RedHatDisplay, Overpass, Roboto, Cantarell, + helvetica, arial, sans-serif; } .spaship-gray { @@ -42,7 +42,7 @@ html, body { padding: 0; margin: 0; - font-family: var(--spaship-global--Font-Family); + font-family: var(--spaship-global--Font-Family); } a { @@ -51,23 +51,71 @@ a { } button.spaship_btn { - --pf-c-button--m-primary--BackgroundColor: var(--spaship-global--Color--amarillo-flare); - --pf-c-button--m-primary--Color: var(--spaship-global--Color--text-black) !important; + --pf-c-button--m-primary--BackgroundColor: var( + --spaship-global--Color--amarillo-flare + ); + --pf-c-button--m-primary--Color: var( + --spaship-global--Color--text-black + ) !important; outline: none; } .spaship_btn:hover { - --pf-c-button--m-primary--hover--BackgroundColor: var(--spaship-global--Color--solar-orange) !important; + --pf-c-button--m-primary--hover--BackgroundColor: var( + --spaship-global--Color--solar-orange + ) !important; } .spaship_btn:focus { - --pf-c-button--m-primary--focus--BackgroundColor: var(--spaship-global--Color--solar-orange) !important; + --pf-c-button--m-primary--focus--BackgroundColor: var( + --spaship-global--Color--solar-orange + ) !important; } .spaship_btn:active { - --pf-c-button--m-primary--active--BackgroundColor: var(--spaship-global--Color--munsell-yellow) !important; + --pf-c-button--m-primary--active--BackgroundColor: var( + --spaship-global--Color--munsell-yellow + ) !important; } * { box-sizing: border-box; } + +.page_section { + padding: 1vw 10vw; + width: 60vw; +} + +.spaship-circle { + -webkit-border-radius: 999px; + -moz-border-radius: 999px; + border-radius: 999px; + width: 70px; + height: 70px; + margin-top: 25px; + margin-left: 25px; + background: hsl(42, 98%, 54%); + color: black; + text-align: center; + -webkit-transition: background 0.2s linear; + -moz-transition: background 0.2s linear; + -ms-transition: background 0.2s linear; + -o-transition: background 0.2s linear; + transition: background 0.2s linear; + transition: color 0.2s linear; + font: 70px arial, sans-serif; +} + +.spaship-circle:hover { + background: #333333; + cursor: pointer; +} + +.spaship-plus { + line-height: 1.1em; +} + +.spaship-plus:hover { + color: white; +} diff --git a/utils/api.utils.ts b/utils/api.utils.ts new file mode 100644 index 0000000..71b9303 --- /dev/null +++ b/utils/api.utils.ts @@ -0,0 +1,85 @@ +import { getToken } from "./config.utils"; + +const getDefaultHeader = async (useJSON = true) => { + const headers = new Headers(); + const token: string = getToken(); + headers.append("Accept", "application/json"); + headers.append("Authorization", token); + headers.append("rejectUnauthorized", "false") + if (useJSON) { + headers.append("Content-Type", "application/json"); + } + return headers; +}; + +function handleResponse(res: Response): Promise { + return new Promise((resolve, reject) => { + if (res.status === 200 || res.status === 201) { + res + .json() + .then((json) => resolve(json.data)) + .catch((err) => reject(err)); + } else { + res + .json() + .then((json) => resolve(json.message)) + .catch((err) => reject(err)); + } + }); +} + +export async function get(url: string): Promise { + const headers = await getDefaultHeader(); + const options: RequestInit = { + method: "GET", + headers, + }; + const res = await fetch(url, options); + return handleResponse(res); +} + +export async function post(url: string, data: object): Promise { + const headers = await getDefaultHeader(); + const options: RequestInit = { + method: "POST", + headers, + body: JSON.stringify(data), + }; + const res = await fetch(url, options); + return handleResponse(res); +} + +export async function upload(url: string, data: FormData): Promise { + const headers = await getDefaultHeader(false); + const options: RequestInit = { + method: "POST", + headers, + body: data, + }; + const res = await fetch(url, options); + return handleResponse(res); +} + +export async function put(url: string, data: object): Promise { + const headers = await getDefaultHeader(); + const options: RequestInit = { + method: "PUT", + headers, + body: JSON.stringify(data), + }; + const res = await fetch(url, options); + return handleResponse(res); +} + +export async function del(url: string, data?: object) { + const headers = await getDefaultHeader(); + const options: RequestInit = { + method: "DELETE", + headers, + }; + if (data) { + options.body = JSON.stringify(data); + } + const res = await fetch(url, options); + return handleResponse(res); +} diff --git a/utils/config.utils.ts b/utils/config.utils.ts new file mode 100644 index 0000000..04fb405 --- /dev/null +++ b/utils/config.utils.ts @@ -0,0 +1,17 @@ +interface Config { + host: string; + token: string; +} + +const configs: Config = { + host: process.env.HOST || '', + token: process.env.AUTHENTICATION_TOKEN || '', +} + +export function getHost() { + return configs.host; +} + +export function getToken() { + return configs.token; +} \ No newline at end of file