mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-27 18:19:50 +00:00
project & marketplace via factory
This commit is contained in:
3
.env
3
.env
@@ -1,4 +1,7 @@
|
||||
VITE_CUSTOMIZATION_API_GROUP=incloud.io
|
||||
VITE_CUSTOMIZATION_API_VERSION=v1alpha
|
||||
VITE_PROJECTS_VERSION=v1alpha
|
||||
VITE_PROJECTS_RESOURCE_NAME=projects
|
||||
VITE_MARKETPLACE_RESOURCE_NAME=marketplacepanels
|
||||
VITE_MARKETPLACE_KIND=MarketplacePanel
|
||||
VITE_INSTANCES_VERSION=v1alpha1
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"@ant-design/icons": "5.6.0",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@originjs/vite-plugin-federation": "1.3.6",
|
||||
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.22",
|
||||
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.23",
|
||||
"@readme/openapi-parser": "4.0.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
@@ -2752,9 +2752,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prorobotech/openapi-k8s-toolkit": {
|
||||
"version": "0.0.1-alpha.22",
|
||||
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.22.tgz",
|
||||
"integrity": "sha512-907Jubl1g7CcSK/YMakSTgbz/+FF4mDnL5hT2khQAUusW/7sPD0B8f2VEYa8Ace511q/XuLNo7STUDfmXEEcMQ==",
|
||||
"version": "0.0.1-alpha.23",
|
||||
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.23.tgz",
|
||||
"integrity": "sha512-3lxhPTGbqca3emK5zkQRUuiNc424TpotmExno7YFMN66DpXDT42MsCQQ0VF7iPbVPwVH6bWmOMtqe6M5hP6j3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@ant-design/icons": "5.6.0",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@originjs/vite-plugin-federation": "1.3.6",
|
||||
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.22",
|
||||
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.23",
|
||||
"@readme/openapi-parser": "4.0.0",
|
||||
"@reduxjs/toolkit": "2.2.5",
|
||||
"@tanstack/react-query": "5.62.2",
|
||||
|
||||
@@ -127,7 +127,10 @@ export const App: FC<TAppProps> = ({ isFederation, forcedTheme }) => {
|
||||
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/forms/crds/:apiGroup/:apiVersion/:typeName/:entryName?/`}
|
||||
element={<FormCrdPage forcedTheme={forcedTheme} />}
|
||||
/>
|
||||
<Route path={`${prefix}/:clusterName/factory/:key/*`} element={<FactoryPage forcedTheme={forcedTheme} />} />
|
||||
<Route
|
||||
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/factory/:key/*`}
|
||||
element={<FactoryPage forcedTheme={forcedTheme} />}
|
||||
/>
|
||||
<Route path={`${prefix}/factory-admin/*`} element={<FactoryAdminPage />} />
|
||||
</Routes>
|
||||
)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React, { FC, ReactNode } from 'react'
|
||||
import { theme } from 'antd'
|
||||
import { Styled } from './styled'
|
||||
|
||||
type TContentCardProps = {
|
||||
children?: ReactNode
|
||||
flexGrow?: number
|
||||
displayFlex?: boolean
|
||||
flexFlow?: string
|
||||
}
|
||||
|
||||
export const ContentCard: FC<TContentCardProps> = ({ children, flexGrow, displayFlex, flexFlow }) => {
|
||||
const { token } = theme.useToken()
|
||||
return (
|
||||
<Styled.ContentContainer
|
||||
$flexGrow={flexGrow}
|
||||
$bgColor={token.colorBgContainer}
|
||||
$borderColor={token.colorBorder}
|
||||
$displayFlex={displayFlex}
|
||||
$flexFlow={flexFlow}
|
||||
>
|
||||
{children}
|
||||
</Styled.ContentContainer>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ContentCard'
|
||||
@@ -1,25 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
type TContentContainerProps = {
|
||||
$bgColor: string
|
||||
$borderColor: string
|
||||
$flexGrow?: number
|
||||
$displayFlex?: boolean
|
||||
$flexFlow?: string
|
||||
}
|
||||
|
||||
export const ContentContainer = styled.div<TContentContainerProps>`
|
||||
border: 1px solid ${({ $borderColor }) => $borderColor};
|
||||
border-radius: 6px;
|
||||
background-color: ${({ $bgColor }) => $bgColor};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px;
|
||||
flex-grow: ${({ $flexGrow }) => $flexGrow};
|
||||
display: ${({ $displayFlex }) => ($displayFlex ? 'flex' : 'block')};
|
||||
flex-flow: ${({ $flexFlow }) => $flexFlow};
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
ContentContainer,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { theme } from 'antd'
|
||||
|
||||
export const DeleteIcon: FC = () => {
|
||||
const { token } = theme.useToken()
|
||||
|
||||
return (
|
||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M17.25 3.5H14.25V1.625C14.25 0.797656 13.5773 0.125 12.75 0.125H5.25C4.42266 0.125 3.75 0.797656 3.75 1.625V3.5H0.75C0.335156 3.5 0 3.83516 0 4.25V5C0 5.10313 0.084375 5.1875 0.1875 5.1875H1.60312L2.18203 17.4453C2.21953 18.2445 2.88047 18.875 3.67969 18.875H14.3203C15.1219 18.875 15.7805 18.2469 15.818 17.4453L16.3969 5.1875H17.8125C17.9156 5.1875 18 5.10313 18 5V4.25C18 3.83516 17.6648 3.5 17.25 3.5ZM12.5625 3.5H5.4375V1.8125H12.5625V3.5Z"
|
||||
fill={token.colorText}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { theme } from 'antd'
|
||||
|
||||
export const EditIcon: FC = () => {
|
||||
const { token } = theme.useToken()
|
||||
|
||||
return (
|
||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M17.28 16.79H0.72C0.32175 16.79 0 17.1117 0 17.51V18.32C0 18.419 0.081 18.5 0.18 18.5H17.82C17.919 18.5 18 18.419 18 18.32V17.51C18 17.1117 17.6783 16.79 17.28 16.79ZM3.27825 14.9C3.32325 14.9 3.36825 14.8955 3.41325 14.8888L7.19775 14.225C7.24275 14.216 7.2855 14.1958 7.317 14.162L16.8547 4.62425C16.8756 4.60343 16.8922 4.57871 16.9034 4.55149C16.9147 4.52427 16.9205 4.49509 16.9205 4.46562C16.9205 4.43616 16.9147 4.40698 16.9034 4.37976C16.8922 4.35254 16.8756 4.32782 16.8547 4.307L13.1153 0.56525C13.0725 0.5225 13.0163 0.5 12.9555 0.5C12.8948 0.5 12.8385 0.5225 12.7958 0.56525L3.258 10.103C3.22425 10.1367 3.204 10.1772 3.195 10.2222L2.53125 14.0067C2.50936 14.1273 2.51718 14.2513 2.55404 14.3682C2.59089 14.485 2.65566 14.5911 2.74275 14.6772C2.89125 14.8212 3.078 14.9 3.27825 14.9Z"
|
||||
fill={token.colorText}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DeleteIcon'
|
||||
export * from './EditIcon'
|
||||
@@ -0,0 +1,18 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
|
||||
export const RedirectProjectInfo: FC = () => {
|
||||
const { clusterName, namespace } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
|
||||
|
||||
navigate(`${baseprefix}/${clusterName}/${namespace}/factory/project/${namespace}`)
|
||||
|
||||
useEffect(() => {
|
||||
navigate(`${baseprefix}/${clusterName}/${namespace}/factory/project/${namespace}`)
|
||||
}, [clusterName, namespace, baseprefix, navigate])
|
||||
|
||||
return null
|
||||
}
|
||||
1
src/components/atoms/RedirectProjectInfo/index.ts
Normal file
1
src/components/atoms/RedirectProjectInfo/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './RedirectProjectInfo'
|
||||
@@ -4,6 +4,5 @@ export * from './TitleWithNoTopMargin'
|
||||
export * from './ThemeSelector'
|
||||
export * from './FlexEnd'
|
||||
export * from './BackLink'
|
||||
export * from './ContentCard'
|
||||
export * from './FlexGrow'
|
||||
export * from './Icons'
|
||||
export * from './RedirectProjectInfo'
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { AxiosError } from 'axios'
|
||||
import { usePermissions, useDirectUnknownResource, DeleteModal, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { notification, Typography, Flex, Switch, Spin, Alert } from 'antd'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { TMarketPlacePanelResponse, TMarketPlacePanelResource, TMarketPlacePanel } from 'localTypes/marketplace'
|
||||
import { AddCard } from './atoms'
|
||||
import { AddEditFormModal, MarketplaceCard, SearchTextInput } from './molecules'
|
||||
import { Styled } from './styled'
|
||||
|
||||
export const MarketPlace: FC = () => {
|
||||
const [api, contextHolder] = notification.useNotification()
|
||||
|
||||
const [isEditMode, setIsEditMode] = useState<boolean>(false)
|
||||
const [isAddEditOpen, setIsAddEditOpen] = useState<boolean | TMarketPlacePanelResource>(false)
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState<{ name: string } | boolean>(false)
|
||||
|
||||
const [createUpdateError, setCreateUpdateError] = useState<AxiosError | Error>()
|
||||
const [deleteError, setDeleteError] = useState<AxiosError | Error>()
|
||||
|
||||
const [initialData, setInitialData] = useState<TMarketPlacePanel[]>([])
|
||||
const [filteredAndSortedData, setFilterAndSortedData] = useState<TMarketPlacePanel[]>([])
|
||||
const [uniqueTags, setUniqueTags] = useState<string[]>([])
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
||||
|
||||
const { clusterName, namespace } = useParams()
|
||||
|
||||
const {
|
||||
data: marketplacePanels,
|
||||
isLoading,
|
||||
error,
|
||||
} = useDirectUnknownResource<TMarketPlacePanelResponse>({
|
||||
uri: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/marketplacepanels/`,
|
||||
refetchInterval: 5000,
|
||||
queryKey: ['marketplacePanels', clusterName || 'no-cluster'],
|
||||
isEnabled: clusterName !== undefined,
|
||||
})
|
||||
|
||||
const createPermission = usePermissions({
|
||||
apiGroup: BASE_API_GROUP,
|
||||
typeName: 'marketplacepanels',
|
||||
namespace: '',
|
||||
clusterName: clusterName || '',
|
||||
verb: 'create',
|
||||
refetchInterval: false,
|
||||
})
|
||||
|
||||
const updatePermission = usePermissions({
|
||||
apiGroup: BASE_API_GROUP,
|
||||
typeName: 'marketplacepanels',
|
||||
namespace: '',
|
||||
clusterName: clusterName || '',
|
||||
verb: 'update',
|
||||
refetchInterval: false,
|
||||
})
|
||||
|
||||
const deletePermission = usePermissions({
|
||||
apiGroup: BASE_API_GROUP,
|
||||
typeName: 'marketplacepanels',
|
||||
namespace: '',
|
||||
clusterName: clusterName || '',
|
||||
verb: 'delete',
|
||||
refetchInterval: false,
|
||||
})
|
||||
|
||||
const onCreateSuccess = () =>
|
||||
api.success({
|
||||
message: 'Card created',
|
||||
key: 'create-marketplace-success',
|
||||
})
|
||||
|
||||
const onUpdateSuccess = () =>
|
||||
api.success({
|
||||
message: 'Card modified',
|
||||
key: 'update-marketplace-success',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (marketplacePanels) {
|
||||
if (isEditMode) {
|
||||
setInitialData(marketplacePanels.items.map(({ spec }) => spec).sort())
|
||||
setUniqueTags(
|
||||
marketplacePanels.items
|
||||
.flatMap(({ spec }) => spec.tags)
|
||||
.filter((value, index, arr) => arr.indexOf(value) === index),
|
||||
)
|
||||
} else {
|
||||
setInitialData(
|
||||
marketplacePanels.items
|
||||
.map(({ spec }) => spec)
|
||||
.filter(({ hidden }) => hidden !== true)
|
||||
.sort(),
|
||||
)
|
||||
setUniqueTags(
|
||||
marketplacePanels.items
|
||||
.filter(({ spec }) => spec.hidden !== true)
|
||||
.flatMap(({ spec }) => spec.tags)
|
||||
.filter((value, index, arr) => arr.indexOf(value) === index),
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [marketplacePanels, isEditMode])
|
||||
|
||||
useEffect(() => {
|
||||
let newData: TMarketPlacePanel[] = []
|
||||
|
||||
if (selectedTags && selectedTags.length > 0) {
|
||||
newData = initialData
|
||||
.filter(
|
||||
({ name, description, tags }) =>
|
||||
selectedTags.some(tag => name.toLowerCase().includes(tag.toLowerCase())) ||
|
||||
selectedTags.some(tag => description.toLowerCase().includes(tag.toLowerCase())) ||
|
||||
selectedTags.some(tag => tags.some(el => el.toLowerCase().includes(tag.toLowerCase()))),
|
||||
)
|
||||
.sort()
|
||||
} else {
|
||||
newData = initialData.sort()
|
||||
}
|
||||
|
||||
setFilterAndSortedData(newData)
|
||||
}, [selectedTags, initialData])
|
||||
|
||||
if (error) {
|
||||
return <div>{JSON.stringify(error)}</div>
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Spin />
|
||||
}
|
||||
|
||||
if (!marketplacePanels) {
|
||||
return <div>No panels</div>
|
||||
}
|
||||
|
||||
const toggleEditMode = () => {
|
||||
setIsEditMode(!isEditMode)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<Flex justify="space-between">
|
||||
<div>
|
||||
<Flex gap={12} vertical>
|
||||
<div>
|
||||
<Typography.Text type="secondary">Available Products</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Styled.BigValue>Marketplace</Styled.BigValue>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
<div>
|
||||
<Flex gap={12} vertical>
|
||||
<SearchTextInput uniqueTags={uniqueTags} selectedTags={selectedTags} onSelectedTags={setSelectedTags} />
|
||||
{(createPermission.data?.status.allowed ||
|
||||
updatePermission.data?.status.allowed ||
|
||||
deletePermission.data?.status.allowed) && (
|
||||
<Flex align="center" gap={8}>
|
||||
<Switch defaultChecked checked={isEditMode} onClick={toggleEditMode} /> Edit Mode
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
<Spacer $space={20} $samespace />
|
||||
{createUpdateError && (
|
||||
<Alert
|
||||
description={JSON.stringify(createUpdateError)}
|
||||
message="Card was not created"
|
||||
onClose={() => setCreateUpdateError(undefined)}
|
||||
type="error"
|
||||
/>
|
||||
)}
|
||||
{deleteError && (
|
||||
<Alert
|
||||
description={JSON.stringify(deleteError)}
|
||||
message="Card was not deleted"
|
||||
onClose={() => setDeleteError(undefined)}
|
||||
type="error"
|
||||
/>
|
||||
)}
|
||||
<Flex gap={22} wrap>
|
||||
{clusterName &&
|
||||
namespace &&
|
||||
filteredAndSortedData.map(
|
||||
({ name, description, icon, type, pathToNav, typeName, apiGroup, apiVersion, tags, disabled }) => (
|
||||
<MarketplaceCard
|
||||
key={name}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
icon={icon}
|
||||
isEditMode={isEditMode}
|
||||
name={name}
|
||||
clusterName={clusterName}
|
||||
namespace={namespace}
|
||||
type={type}
|
||||
pathToNav={pathToNav}
|
||||
typeName={typeName}
|
||||
apiGroup={apiGroup}
|
||||
apiVersion={apiVersion}
|
||||
tags={tags}
|
||||
onDeleteClick={() => {
|
||||
const entry = marketplacePanels.items.find(({ spec }) => spec.name === name)
|
||||
setIsDeleteOpen(entry ? { name: entry.metadata.name } : false)
|
||||
}}
|
||||
onEditClick={() => {
|
||||
setIsAddEditOpen(marketplacePanels.items.find(({ spec }) => spec.name === name) || false)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{isEditMode && (
|
||||
<AddCard
|
||||
onAddClick={() => {
|
||||
setIsAddEditOpen(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{isAddEditOpen && (
|
||||
<AddEditFormModal
|
||||
isOpen={isAddEditOpen}
|
||||
setError={setCreateUpdateError}
|
||||
setIsOpen={setIsAddEditOpen}
|
||||
onCreateSuccess={onCreateSuccess}
|
||||
onUpdateSuccess={onUpdateSuccess}
|
||||
/>
|
||||
)}
|
||||
{typeof isDeleteOpen !== 'boolean' && (
|
||||
<DeleteModal
|
||||
name={isDeleteOpen.name}
|
||||
onClose={() => setIsDeleteOpen(false)}
|
||||
endpoint={`/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/marketplacepanels/${isDeleteOpen.name}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { Styled } from './styled'
|
||||
|
||||
type TAddCardProps = {
|
||||
onAddClick: () => void
|
||||
}
|
||||
|
||||
export const AddCard: FC<TAddCardProps> = ({ onAddClick }) => {
|
||||
return (
|
||||
<Styled.CustomCard onClick={onAddClick}>
|
||||
<PlusOutlined />
|
||||
</Styled.CustomCard>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './AddCard'
|
||||
@@ -1,27 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
import { Card } from 'antd'
|
||||
|
||||
const CustomCard = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 238px;
|
||||
box-shadow:
|
||||
0 6px 16px 0 #00000014,
|
||||
0 3px 6px -4px #0000001f,
|
||||
0 9px 28px 8px #0000000d;
|
||||
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 238px;
|
||||
overflow-x: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
CustomCard,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './AddCard'
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './MarketPlace'
|
||||
export * from './molecules/MarketplaceCard'
|
||||
@@ -1,164 +0,0 @@
|
||||
import React, { FC, Dispatch, SetStateAction } from 'react'
|
||||
import { isAxiosError, AxiosError } from 'axios'
|
||||
import { Form, Input, Select, Switch, Modal } from 'antd'
|
||||
import { createNewEntry, updateEntry } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { TMarketPlacePanel, TMarketPlacePanelResource } from 'localTypes/marketplace'
|
||||
|
||||
type TAddEditFormModalProps = {
|
||||
isOpen: boolean | TMarketPlacePanelResource
|
||||
setIsOpen: Dispatch<SetStateAction<boolean | TMarketPlacePanelResource>>
|
||||
setError: Dispatch<SetStateAction<AxiosError | Error | undefined>>
|
||||
onCreateSuccess: () => void
|
||||
onUpdateSuccess: () => void
|
||||
}
|
||||
|
||||
export const AddEditFormModal: FC<TAddEditFormModalProps> = ({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
setError,
|
||||
onCreateSuccess,
|
||||
onUpdateSuccess,
|
||||
}) => {
|
||||
const [form] = Form.useForm<TMarketPlacePanel>()
|
||||
const type = Form.useWatch<string | undefined>('type', form)
|
||||
const { clusterName } = useParams()
|
||||
|
||||
const defaultValues: TMarketPlacePanel =
|
||||
typeof isOpen === 'boolean'
|
||||
? {
|
||||
name: '',
|
||||
description: '',
|
||||
icon: '',
|
||||
type: 'direct',
|
||||
apiGroup: '',
|
||||
apiVersion: '',
|
||||
typeName: '',
|
||||
pathToNav: '',
|
||||
tags: [],
|
||||
disabled: false,
|
||||
hidden: false,
|
||||
}
|
||||
: isOpen.spec
|
||||
|
||||
const onSubmit = (values: TMarketPlacePanel) => {
|
||||
if (typeof isOpen === 'boolean') {
|
||||
createNewEntry({
|
||||
endpoint: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/{BASE_API_VERSION}/marketplacepanels`,
|
||||
body: {
|
||||
apiVersion: `${BASE_API_GROUP}/${BASE_API_VERSION}`,
|
||||
kind: 'MarketplacePanel',
|
||||
metadata: {
|
||||
name: uuidv4(),
|
||||
},
|
||||
spec: { ...values },
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
setIsOpen(false)
|
||||
onCreateSuccess()
|
||||
})
|
||||
.catch(err => {
|
||||
if (isAxiosError(err) || err instanceof Error) {
|
||||
setError(err)
|
||||
}
|
||||
})
|
||||
.finally(() => setIsOpen(false))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
updateEntry({
|
||||
endpoint: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/{BASE_API_VERSION}/marketplacepanels/${isOpen.metadata.name}`,
|
||||
body: {
|
||||
apiVersion: `${BASE_API_GROUP}/${BASE_API_VERSION}`,
|
||||
kind: 'MarketplacePanel',
|
||||
metadata: {
|
||||
name: isOpen.metadata.name,
|
||||
resourceVersion: isOpen.metadata.resourceVersion,
|
||||
},
|
||||
spec: { ...values },
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
setIsOpen(false)
|
||||
onUpdateSuccess()
|
||||
})
|
||||
.catch(err => {
|
||||
if (isAxiosError(err) || err instanceof Error) {
|
||||
setError(err)
|
||||
}
|
||||
})
|
||||
.finally(() => setIsOpen(false))
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
onSubmit(form.getFieldsValue())
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
.catch(() => console.log('Validating error'))
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={typeof isOpen === 'boolean' ? 'Add card' : 'Edit плитку'}
|
||||
open={isOpen !== false}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
onOk={() => submit}
|
||||
>
|
||||
<Form<TMarketPlacePanel> form={form} name="control-hooks" initialValues={{ ...defaultValues }}>
|
||||
<Form.Item label="Name" name="name">
|
||||
<Input required />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<Input required />
|
||||
</Form.Item>
|
||||
<Form.Item label="Icon" name="icon">
|
||||
<Input.TextArea placeholder="SVG-иконка, <svg /> -> base64" maxLength={undefined} required />
|
||||
</Form.Item>
|
||||
<Form.Item label="Resources type" name="type">
|
||||
<Select
|
||||
placeholder="Choose resource type"
|
||||
options={[
|
||||
{ value: 'direct', label: 'Direct link' },
|
||||
{ value: 'crd', label: 'CRD' },
|
||||
{ value: 'nonCrd', label: 'API' },
|
||||
{ value: 'built-in', label: 'Built-in' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Enter API group" name="apiGroup">
|
||||
<Input disabled={type === 'direct' || type === 'built-in'} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Enter API version" name="apiVersion">
|
||||
<Input disabled={type === 'direct' || type === 'built-in'} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Enter resource type" name="typeName">
|
||||
<Input disabled={type === 'direct'} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Enter path" name="pathToNav">
|
||||
<Input disabled={type !== 'direct'} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Tags" name="tags">
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Enter tags. Separators: comma and space"
|
||||
tokenSeparators={[',', ' ']}
|
||||
dropdownStyle={{ display: 'none' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Disabled" name="disabled">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="Hidden" name="hidden">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './AddEditFormModal'
|
||||
@@ -1,150 +0,0 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import React, { FC } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Typography, Flex, theme } from 'antd'
|
||||
import { useDirectUnknownResource } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { TMarketPlacePanel } from 'localTypes/marketplace'
|
||||
import { getPathToNav, getListPath } from './utils'
|
||||
import { Styled } from './styled'
|
||||
|
||||
type TMarketplaceCardProps = {
|
||||
clusterName: string
|
||||
namespace: string
|
||||
isEditMode?: boolean
|
||||
onDeleteClick?: () => void
|
||||
onEditClick?: () => void
|
||||
addedMode?: boolean
|
||||
} & Omit<TMarketPlacePanel, 'hidden'>
|
||||
|
||||
export const MarketplaceCard: FC<TMarketplaceCardProps> = ({
|
||||
description,
|
||||
name,
|
||||
icon,
|
||||
clusterName,
|
||||
namespace,
|
||||
type,
|
||||
pathToNav,
|
||||
typeName,
|
||||
apiGroup,
|
||||
apiVersion,
|
||||
tags,
|
||||
disabled,
|
||||
isEditMode,
|
||||
onDeleteClick,
|
||||
onEditClick,
|
||||
addedMode,
|
||||
}) => {
|
||||
const { useToken } = theme
|
||||
const { token } = useToken()
|
||||
const navigate = useNavigate()
|
||||
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
|
||||
|
||||
let decodedIcon = ''
|
||||
|
||||
try {
|
||||
decodedIcon = window.atob(icon)
|
||||
} catch {
|
||||
decodedIcon = "Can't decode"
|
||||
}
|
||||
|
||||
const navigateUrl = getPathToNav({
|
||||
clusterName,
|
||||
namespace,
|
||||
type,
|
||||
pathToNav,
|
||||
typeName,
|
||||
apiGroup,
|
||||
apiVersion,
|
||||
baseprefix,
|
||||
})
|
||||
|
||||
const listUrl: string | undefined =
|
||||
addedMode && type !== 'direct'
|
||||
? getListPath({
|
||||
clusterName,
|
||||
namespace,
|
||||
type,
|
||||
typeName,
|
||||
apiGroup,
|
||||
apiVersion,
|
||||
})
|
||||
: undefined
|
||||
|
||||
const { data: k8sList, error: k8sListError } = useDirectUnknownResource<{ items?: [] }>({
|
||||
uri: listUrl || '',
|
||||
queryKey: [listUrl || ''],
|
||||
refetchInterval: false,
|
||||
isEnabled: addedMode && listUrl !== undefined,
|
||||
})
|
||||
|
||||
if (addedMode && (k8sListError || type === 'direct')) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.CustomCard
|
||||
$isDisabled={disabled}
|
||||
$hoverColor={token.colorPrimary}
|
||||
onClick={() => (disabled ? null : navigate(navigateUrl))}
|
||||
>
|
||||
<Flex vertical style={{ width: '100%', height: '100%' }} justify="spaceBetween">
|
||||
<Flex justify="space-between">
|
||||
<Styled.ImageContainer dangerouslySetInnerHTML={{ __html: decodedIcon }} />
|
||||
{isEditMode && (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (onDeleteClick) {
|
||||
onDeleteClick()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.5 0C4.70156 0 0 4.70156 0 10.5C0 16.2984 4.70156 21 10.5 21C16.2984 21 21 16.2984 21 10.5C21 4.70156 16.2984 0 10.5 0ZM15 11.0625C15 11.1656 14.9156 11.25 14.8125 11.25H6.1875C6.08437 11.25 6 11.1656 6 11.0625V9.9375C6 9.83438 6.08437 9.75 6.1875 9.75H14.8125C14.9156 9.75 15 9.83438 15 9.9375V11.0625Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
<Styled.OverflowContainer>
|
||||
<Styled.TitleContainer>
|
||||
{name} {addedMode && <span>x{k8sList?.items?.length}</span>}
|
||||
</Styled.TitleContainer>
|
||||
<Styled.TagsContainer>
|
||||
{tags.map(tag => (
|
||||
<Styled.CustomTag key={tag}>{tag}</Styled.CustomTag>
|
||||
))}
|
||||
</Styled.TagsContainer>
|
||||
<Styled.DescriptionContainer>
|
||||
<Typography.Text type="secondary">{description}</Typography.Text>
|
||||
</Styled.DescriptionContainer>
|
||||
</Styled.OverflowContainer>
|
||||
<Styled.EditButtonContainer>
|
||||
{isEditMode && (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (onEditClick) {
|
||||
onEditClick()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M17.28 16.79H0.72C0.32175 16.79 0 17.1117 0 17.51V18.32C0 18.419 0.081 18.5 0.18 18.5H17.82C17.919 18.5 18 18.419 18 18.32V17.51C18 17.1117 17.6783 16.79 17.28 16.79ZM3.27825 14.9C3.32325 14.9 3.36825 14.8955 3.41325 14.8888L7.19775 14.225C7.24275 14.216 7.2855 14.1958 7.317 14.162L16.8547 4.62425C16.8756 4.60343 16.8922 4.57871 16.9034 4.55149C16.9147 4.52427 16.9205 4.49509 16.9205 4.46562C16.9205 4.43616 16.9147 4.40698 16.9034 4.37976C16.8922 4.35254 16.8756 4.32782 16.8547 4.307L13.1153 0.56525C13.0725 0.5225 13.0163 0.5 12.9555 0.5C12.8948 0.5 12.8385 0.5225 12.7958 0.56525L3.258 10.103C3.22425 10.1367 3.204 10.1772 3.195 10.2222L2.53125 14.0067C2.50936 14.1273 2.51718 14.2513 2.55404 14.3682C2.59089 14.485 2.65566 14.5911 2.74275 14.6772C2.89125 14.8212 3.078 14.9 3.27825 14.9Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</Styled.EditButtonContainer>
|
||||
</Flex>
|
||||
</Styled.CustomCard>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './MarketplaceCard'
|
||||
@@ -1,97 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
import { Card, Tag, Typography } from 'antd'
|
||||
|
||||
type TCustomCardProps = {
|
||||
$hoverColor: string
|
||||
$isDisabled?: boolean
|
||||
}
|
||||
|
||||
const CustomCard = styled(Card)<TCustomCardProps>`
|
||||
position: relative;
|
||||
width: 238px;
|
||||
overflow-x: auto;
|
||||
cursor: ${({ $isDisabled }) => ($isDisabled ? 'not-allowed' : 'pointer')};
|
||||
box-shadow:
|
||||
0 6px 16px 0 #00000014,
|
||||
0 3px 6px -4px #0000001f,
|
||||
0 9px 28px 8px #0000000d;
|
||||
|
||||
&:hover {
|
||||
border-color: ${({ $hoverColor, $isDisabled }) => !$isDisabled && $hoverColor};
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: 238px;
|
||||
overflow-x: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
min-width: 45px;
|
||||
min-height: 45px;
|
||||
padding: 6px;
|
||||
|
||||
svg {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
`
|
||||
|
||||
const OverflowContainer = styled.div`
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
margin-bottom: 20px;
|
||||
`
|
||||
|
||||
const TitleContainer = styled(Typography.Text)`
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
|
||||
const TagsContainer = styled.div`
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: flex-start;
|
||||
`
|
||||
|
||||
const CustomTag = styled(Tag)`
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const DescriptionContainer = styled.div`
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
`
|
||||
|
||||
const EditButtonContainer = styled.div`
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
CustomCard,
|
||||
ImageContainer,
|
||||
OverflowContainer,
|
||||
TitleContainer,
|
||||
TagsContainer,
|
||||
CustomTag,
|
||||
DescriptionContainer,
|
||||
EditButtonContainer,
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
export const getPathToNav = ({
|
||||
clusterName,
|
||||
namespace,
|
||||
type,
|
||||
pathToNav,
|
||||
typeName,
|
||||
apiGroup,
|
||||
apiVersion,
|
||||
baseprefix,
|
||||
}: {
|
||||
clusterName: string
|
||||
namespace: string
|
||||
type: string
|
||||
pathToNav?: string
|
||||
typeName?: string
|
||||
apiGroup?: string
|
||||
apiVersion?: string
|
||||
baseprefix?: string
|
||||
}): string => {
|
||||
const apiExtensionVersion = 'v1'
|
||||
|
||||
if (type === 'direct' && pathToNav) {
|
||||
return pathToNav
|
||||
}
|
||||
|
||||
if (type === 'crd') {
|
||||
return `${baseprefix}/${clusterName}/${namespace}/crd-table/${apiGroup}/${apiVersion}/${apiExtensionVersion}/${typeName}`
|
||||
}
|
||||
|
||||
if (type === 'nonCrd') {
|
||||
return `${baseprefix}/${clusterName}/${namespace}/api-table/${apiGroup}/${apiVersion}/${typeName}`
|
||||
}
|
||||
|
||||
return `${baseprefix}/${clusterName}/${namespace}/builtin-table/${typeName}`
|
||||
}
|
||||
|
||||
export const getListPath = ({
|
||||
clusterName,
|
||||
namespace,
|
||||
type,
|
||||
typeName,
|
||||
apiGroup,
|
||||
apiVersion,
|
||||
}: {
|
||||
clusterName: string
|
||||
namespace: string
|
||||
type: string
|
||||
typeName?: string
|
||||
apiGroup?: string
|
||||
apiVersion?: string
|
||||
}): string | undefined => {
|
||||
if (type === 'crd') {
|
||||
return `/api/clusters/${clusterName}/k8s/apis/${apiGroup}/${apiVersion}${
|
||||
namespace ? `/namespaces/${namespace}` : ''
|
||||
}/${typeName}`
|
||||
}
|
||||
|
||||
if (type === 'nonCrd') {
|
||||
return `/api/clusters/${clusterName}/k8s/apis/${apiGroup}/${apiVersion}${
|
||||
namespace ? `/namespaces/${namespace}` : ''
|
||||
}/${typeName}`
|
||||
}
|
||||
|
||||
return `/api/clusters/${clusterName}/k8s/api/v1${namespace ? `/namespaces/${namespace}` : ''}/${typeName}`
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Select } from 'antd'
|
||||
|
||||
type TSearchTextInputProps = {
|
||||
uniqueTags: string[]
|
||||
onSelectedTags: (tags: string[]) => void
|
||||
selectedTags: string[]
|
||||
}
|
||||
|
||||
export const SearchTextInput: FC<TSearchTextInputProps> = ({ uniqueTags, selectedTags, onSelectedTags }) => {
|
||||
const options = uniqueTags.map(el => ({ key: el, value: el }))
|
||||
|
||||
return (
|
||||
<Select<string[]>
|
||||
mode="tags"
|
||||
placeholder="Search"
|
||||
tokenSeparators={[',', ' ', ' ']}
|
||||
allowClear
|
||||
options={options}
|
||||
onClear={() => {
|
||||
onSelectedTags([])
|
||||
}}
|
||||
value={selectedTags}
|
||||
onChange={tags => onSelectedTags(tags)}
|
||||
style={{ width: '240px' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './SearchTextInput'
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './AddEditFormModal'
|
||||
export * from './MarketplaceCard'
|
||||
export * from './SearchTextInput'
|
||||
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const BigValue = styled(Typography.Text)`
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
font-weight: 700;
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-ms-line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
line-clamp: 1;
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
BigValue,
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './BlackholeForm'
|
||||
export * from './ManageableBreadcrumbs'
|
||||
export * from './MarketPlace'
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION, BASE_RPROJECTS_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION, BASE_PROJECTS_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { FlexGrow } from 'components'
|
||||
import { TABLE_PROPS } from 'constants/tableProps'
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ListProjects: FC = () => {
|
||||
const path = pathname
|
||||
const cluster = clusterName || ''
|
||||
const apiGroup = BASE_API_GROUP
|
||||
const apiVersion = BASE_RPROJECTS_VERSION
|
||||
const apiVersion = BASE_PROJECTS_VERSION
|
||||
const typeName = 'projects'
|
||||
const isNamespaced = false
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Row, Col } from 'antd'
|
||||
import { ContentCard, MarketPlace } from 'components'
|
||||
import { ProjectInfoCard } from './organisms'
|
||||
|
||||
export const ProjectInfo: FC = () => {
|
||||
return (
|
||||
<Row gutter={[24, 24]} style={{ flexGrow: 1 }}>
|
||||
<Col span={13}>
|
||||
<ContentCard flexGrow={1}>
|
||||
<ProjectInfoCard />
|
||||
</ContentCard>
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<ContentCard flexGrow={1}>
|
||||
<MarketPlace />
|
||||
</ContentCard>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ProjectInfo'
|
||||
@@ -1,44 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Button, Dropdown } from 'antd'
|
||||
|
||||
export const DropdownAccessGroups: FC = () => {
|
||||
const getDropdownItems = () => {
|
||||
return [
|
||||
'g-advanced-credit-factory',
|
||||
'g-advanced-credit-factory',
|
||||
'g-advanced-credit-factory',
|
||||
'g-advanced-credit-factory',
|
||||
].map((item, i) => ({
|
||||
key: `${item}-${i}`,
|
||||
label: (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown placement="bottomRight" menu={{ items: getDropdownItems() }} trigger={['click']}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
Access Groups
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.8127 3H11.6408C11.5611 3 11.4861 3.03906 11.4393 3.10313L7.0002 9.22188L2.56114 3.10313C2.51426 3.03906 2.43926 3 2.35957 3H1.1877C1.08614 3 1.02676 3.11563 1.08614 3.19844L6.59551 10.7937C6.79551 11.0687 7.20489 11.0687 7.40332 10.7937L12.9127 3.19844C12.9736 3.11563 12.9143 3 12.8127 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './DropdownAccessGroups'
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Button, Dropdown, Flex } from 'antd'
|
||||
import { EllipsisOutlined } from '@ant-design/icons'
|
||||
import { EditIcon, DeleteIcon } from 'components/atoms'
|
||||
|
||||
type TDropdownActionsProps = {
|
||||
onDelete?: () => void
|
||||
onUpdate?: () => void
|
||||
}
|
||||
|
||||
export const DropdownActions: FC<TDropdownActionsProps> = ({ onDelete, onUpdate }) => {
|
||||
const getDropdownItems = () => {
|
||||
const items = []
|
||||
if (onUpdate) {
|
||||
items.push({
|
||||
key: 'update',
|
||||
label: (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onUpdate()
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap={8}>
|
||||
<EditIcon />
|
||||
Edit
|
||||
</Flex>
|
||||
</div>
|
||||
),
|
||||
})
|
||||
}
|
||||
if (onDelete) {
|
||||
items.push({
|
||||
key: 'delete',
|
||||
label: (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
onDelete()
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap={8}>
|
||||
<DeleteIcon />
|
||||
Delete
|
||||
</Flex>
|
||||
</div>
|
||||
),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown placement="bottomRight" menu={{ items: getDropdownItems() }} trigger={['click']}>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<EllipsisOutlined />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './DropdownActions'
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DropdownActions'
|
||||
export * from './DropdownAccessGroups'
|
||||
@@ -1,238 +0,0 @@
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, { FC, useCallback, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Spacer, useDirectUnknownResource, DeleteModal, usePermissions } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { Typography, Flex, Spin, Button } from 'antd'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { BASE_API_GROUP, BASE_API_VERSION, BASE_RPROJECTS_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
import { TMarketPlacePanelResponse } from 'localTypes/marketplace'
|
||||
import { MarketplaceCard } from 'components/molecules'
|
||||
import { DropdownActions, DropdownAccessGroups } from '../../molecules'
|
||||
import { Styled } from './styled'
|
||||
|
||||
export const ProjectInfoCard: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { clusterName, namespace } = useParams()
|
||||
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
|
||||
|
||||
const {
|
||||
data: marketplacePanels,
|
||||
isLoading: marketplaceIsLoading,
|
||||
// error: marketplaceError,
|
||||
} = useDirectUnknownResource<TMarketPlacePanelResponse>({
|
||||
uri: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/marketplacepanels/`,
|
||||
refetchInterval: 5000,
|
||||
queryKey: ['marketplacePanels', clusterName || 'no-cluster'],
|
||||
isEnabled: clusterName !== undefined,
|
||||
})
|
||||
|
||||
const {
|
||||
data: project,
|
||||
isLoading,
|
||||
error,
|
||||
} = useDirectUnknownResource<{
|
||||
apiVersion: string
|
||||
kind: 'Project'
|
||||
metadata: {
|
||||
labels: {
|
||||
paas: string
|
||||
pj: string
|
||||
}
|
||||
name: string
|
||||
resourceVersion: string
|
||||
uid: string
|
||||
}
|
||||
spec: {
|
||||
businessName?: string
|
||||
description: string
|
||||
prefix: string
|
||||
}
|
||||
status: {
|
||||
conditions: {
|
||||
lastTransitionTime: string
|
||||
message: string
|
||||
reason: string
|
||||
status: string
|
||||
type: string
|
||||
}[]
|
||||
}
|
||||
}>({
|
||||
uri: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_RPROJECTS_VERSION}/projects/${namespace}`,
|
||||
refetchInterval: 5000,
|
||||
queryKey: ['projects', clusterName || 'no-cluster'],
|
||||
isEnabled: clusterName !== undefined,
|
||||
})
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false)
|
||||
|
||||
const updatePermission = usePermissions({
|
||||
apiGroup: BASE_API_GROUP,
|
||||
typeName: 'projects',
|
||||
namespace: '',
|
||||
clusterName: clusterName || '',
|
||||
verb: 'update',
|
||||
refetchInterval: false,
|
||||
})
|
||||
|
||||
const deletePermission = usePermissions({
|
||||
apiGroup: BASE_API_GROUP,
|
||||
typeName: 'projects',
|
||||
namespace: '',
|
||||
clusterName: clusterName || '',
|
||||
verb: 'delete',
|
||||
refetchInterval: false,
|
||||
})
|
||||
|
||||
const openUpdate = useCallback(() => {
|
||||
navigate(
|
||||
`${baseprefix}/${clusterName}/forms/apis/${BASE_API_GROUP}/${BASE_RPROJECTS_VERSION}/projects/${namespace}?backlink=${baseprefix}/clusters/${clusterName}`,
|
||||
)
|
||||
}, [baseprefix, clusterName, namespace, navigate])
|
||||
|
||||
if (isLoading) {
|
||||
return <Spin />
|
||||
}
|
||||
|
||||
if (!project || error) {
|
||||
return null
|
||||
}
|
||||
|
||||
const readyCondition = project.status.conditions.find(({ type }) => type === 'Ready')
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justify="space-between">
|
||||
<div>
|
||||
<Flex gap={20} vertical>
|
||||
<div>
|
||||
<Typography.Text type="secondary">Project Business Name</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Flex gap="small">
|
||||
<Styled.BigValue>{project.spec.businessName || '-'}</Styled.BigValue>
|
||||
{readyCondition && (
|
||||
<Flex align="center" gap="small">
|
||||
<Typography.Text type={readyCondition.status === 'True' ? 'success' : 'warning'}>
|
||||
{readyCondition.reason}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text>{project.spec.description}</Typography.Text>
|
||||
</div>
|
||||
</Flex>
|
||||
<Spacer $space={24} $samespace />
|
||||
<Flex gap={14} vertical>
|
||||
<div>
|
||||
<Typography.Text type="secondary">Developer Instruments</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Flex gap={14} wrap>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
<Button type="link">Test</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
<div>
|
||||
<Flex gap={24} vertical>
|
||||
<Flex justify="flex-end">
|
||||
{readyCondition?.status === 'True' &&
|
||||
(updatePermission.data?.status.allowed || deletePermission.data?.status.allowed) ? (
|
||||
<DropdownActions
|
||||
onDelete={
|
||||
deletePermission.data?.status.allowed
|
||||
? () => {
|
||||
setIsDeleteModalOpen(true)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onUpdate={updatePermission.data?.status.allowed ? openUpdate : undefined}
|
||||
/>
|
||||
) : (
|
||||
<Styled.ActionMenuPlaceholder />
|
||||
)}
|
||||
</Flex>
|
||||
<DropdownAccessGroups />
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
<Spacer $space={24} $samespace />
|
||||
<Typography.Text type="secondary">Added Products</Typography.Text>
|
||||
<Spacer $space={12} $samespace />
|
||||
<Flex gap={22} wrap>
|
||||
{marketplaceIsLoading && <Spin />}
|
||||
{clusterName &&
|
||||
namespace &&
|
||||
marketplacePanels?.items
|
||||
.map(({ spec }) => spec)
|
||||
.sort()
|
||||
.map(({ name, description, icon, type, pathToNav, typeName, apiGroup, apiVersion, tags, disabled }) => (
|
||||
<MarketplaceCard
|
||||
key={name}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
icon={icon}
|
||||
isEditMode={false}
|
||||
name={name}
|
||||
clusterName={clusterName}
|
||||
namespace={namespace}
|
||||
type={type}
|
||||
pathToNav={pathToNav}
|
||||
typeName={typeName}
|
||||
apiGroup={apiGroup}
|
||||
apiVersion={apiVersion}
|
||||
tags={tags}
|
||||
addedMode
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
{isDeleteModalOpen && (
|
||||
<DeleteModal
|
||||
name={project.metadata.name}
|
||||
onClose={() => {
|
||||
setIsDeleteModalOpen(false)
|
||||
navigate(`${baseprefix}/clusters/${clusterName}`)
|
||||
}}
|
||||
endpoint={`/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_RPROJECTS_VERSION}/projects/${project.metadata.name}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ProjectInfoCard'
|
||||
@@ -1,27 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const ActionMenuPlaceholder = styled.div`
|
||||
width: 45.33px;
|
||||
height: 1px;
|
||||
`
|
||||
|
||||
const BigValue = styled(Typography.Text)`
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
font-weight: 700;
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-ms-line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
line-clamp: 1;
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
ActionMenuPlaceholder,
|
||||
BigValue,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ProjectInfoCard'
|
||||
@@ -1,27 +0,0 @@
|
||||
import styled from 'styled-components'
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const ActionMenuPlaceholder = styled.div`
|
||||
width: 45.33px;
|
||||
height: 1px;
|
||||
`
|
||||
|
||||
const BigValue = styled(Typography.Text)`
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
font-weight: 700;
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-ms-line-clamp: 1;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
line-clamp: 1;
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
ActionMenuPlaceholder,
|
||||
BigValue,
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './ListClusters'
|
||||
export * from './ListProjects'
|
||||
export * from './ProjectInfo'
|
||||
export * from './ListInsideClusterAndNs'
|
||||
export * from './ListInsideAllResources'
|
||||
export * from './ListInsideCrdsByApiGroup'
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const BASE_API_GROUP = import.meta.env.VITE_CUSTOMIZATION_API_GROUP
|
||||
export const BASE_API_VERSION = import.meta.env.VITE_CUSTOMIZATION_API_VERSION
|
||||
export const BASE_RPROJECTS_VERSION = import.meta.env.VITE_PROJECTS_VERSION
|
||||
export const BASE_PROJECTS_VERSION = import.meta.env.VITE_PROJECTS_VERSION
|
||||
export const BASE_PROJECTS_RESOURCE_NAME = import.meta.env.VITE_PROJECTS_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_RESOURCE_NAME = import.meta.env.VITE_MARKETPLACE_RESOURCE_NAME
|
||||
export const BASE_MARKETPLACE_KIND = import.meta.env.VITE_MARKETPLACE_KIND
|
||||
export const BASE_INSTANCES_VERSION = import.meta.env.VITE_INSTANCES_VERSION
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { TEnrichedTableProps } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { EditIcon, DeleteIcon } from 'components/atoms'
|
||||
import { TEnrichedTableProps, EditIcon, DeleteIcon } from '@prorobotech/openapi-k8s-toolkit'
|
||||
|
||||
export const TABLE_PROPS: TEnrichedTableProps['tableProps'] = {
|
||||
borderless: true,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import {
|
||||
BASE_API_GROUP,
|
||||
BASE_RPROJECTS_VERSION,
|
||||
BASE_PROJECTS_VERSION,
|
||||
BASE_INSTANCES_VERSION,
|
||||
} from 'constants/customizationApiGroupAndVersion'
|
||||
|
||||
@@ -24,7 +24,7 @@ export const useNavSelector = (clusterName?: string, projectName?: string) => {
|
||||
clusterName: clusterName || '',
|
||||
namespace: '',
|
||||
apiGroup: BASE_API_GROUP,
|
||||
apiVersion: BASE_RPROJECTS_VERSION,
|
||||
apiVersion: BASE_PROJECTS_VERSION,
|
||||
typeName: 'projects',
|
||||
limit: null,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Factory } from 'components'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ManageableBreadcrumbs, Factory } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
|
||||
type TFactoryPageProps = {
|
||||
@@ -9,6 +10,8 @@ type TFactoryPageProps = {
|
||||
export const FactoryPage: FC<TFactoryPageProps> = ({ forcedTheme }) => {
|
||||
return (
|
||||
<BaseTemplate forcedTheme={forcedTheme} withNoCluster>
|
||||
<ManageableBreadcrumbs />
|
||||
<Spacer $space={20} $samespace />
|
||||
<Factory />
|
||||
</BaseTemplate>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { ContentCard, ListClusters } from 'components'
|
||||
import { ListClusters } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
|
||||
type TListClustersPageProps = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { ContentCard, ListInsideClusterAndNs } from 'components'
|
||||
import { ListInsideClusterAndNs } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
|
||||
type TListInsideClustersAndNsPageProps = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, ListProjects, ManageableBreadcrumbs } from 'components'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ListProjects, ManageableBreadcrumbs } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
|
||||
type TListProjectsPageProps = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ProjectInfo, ManageableBreadcrumbs } from 'components'
|
||||
import { RedirectProjectInfo } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
|
||||
type TProjectInfoPageProps = {
|
||||
@@ -10,9 +9,7 @@ type TProjectInfoPageProps = {
|
||||
export const ProjectInfoPage: FC<TProjectInfoPageProps> = ({ forcedTheme }) => {
|
||||
return (
|
||||
<BaseTemplate forcedTheme={forcedTheme}>
|
||||
<ManageableBreadcrumbs />
|
||||
<Spacer $space={20} $samespace />
|
||||
<ProjectInfo />
|
||||
<RedirectProjectInfo />
|
||||
</BaseTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useParams, useSearchParams } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { ContentCard, TableNonCrdInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { TableNonCrdInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
import { BASE_API_GROUP, BASE_INSTANCES_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useParams, useSearchParams } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { ContentCard, TableBuiltinInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { TableBuiltinInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
import { BASE_API_GROUP, BASE_INSTANCES_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { ContentCard, Spacer } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { RootState } from 'store/store'
|
||||
import { ContentCard, TableCrdInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { TableCrdInfo, BackLink, ManageableBreadcrumbs } from 'components'
|
||||
import { BaseTemplate } from 'templates'
|
||||
import { BASE_API_GROUP, BASE_INSTANCES_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user