This commit is contained in:
typescreep
2025-06-05 22:36:31 +03:00
parent 750e7cbb03
commit fb5509d82d
15 changed files with 301 additions and 214 deletions

View File

@@ -5,9 +5,9 @@ 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, CardInProject, SearchTextInput } from './molecules'
import { TMarketPlacePanelResponse, TMarketPlacePanelResource, TMarketPlacePanel } from './types'
import { AddEditFormModal, MarketplaceCard, SearchTextInput } from './molecules'
import { Styled } from './styled'
export const MarketPlace: FC = () => {
@@ -187,7 +187,7 @@ export const MarketPlace: FC = () => {
namespace &&
filteredAndSortedData.map(
({ name, description, icon, type, pathToNav, typeName, apiGroup, apiVersion, tags, disabled }) => (
<CardInProject
<MarketplaceCard
key={name}
description={description}
disabled={disabled}

View File

@@ -1 +1,2 @@
export * from './MarketPlace'
export * from './molecules/MarketplaceCard'

View File

@@ -5,7 +5,7 @@ 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 '../../types'
import { TMarketPlacePanel, TMarketPlacePanelResource } from 'localTypes/marketplace'
type TAddEditFormModalProps = {
isOpen: boolean | TMarketPlacePanelResource

View File

@@ -1 +0,0 @@
export * from './CardInProject'

View File

@@ -4,19 +4,20 @@ import { useNavigate } from 'react-router-dom'
import { Typography, Flex, theme } from 'antd'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { TMarketPlacePanel } from '../../types'
import { TMarketPlacePanel } from 'localTypes/marketplace'
import { getPathToNav } from './utils'
import { Styled } from './styled'
type TCardInProjectProps = {
type TMarketplaceCardProps = {
clusterName: string
namespace: string
isEditMode?: boolean
onDeleteClick: () => void
onEditClick: () => void
onDeleteClick?: () => void
onEditClick?: () => void
addedMode?: boolean
} & Omit<TMarketPlacePanel, 'hidden'>
export const CardInProject: FC<TCardInProjectProps> = ({
export const MarketplaceCard: FC<TMarketplaceCardProps> = ({
description,
name,
icon,
@@ -71,7 +72,9 @@ export const CardInProject: FC<TCardInProjectProps> = ({
onClick={e => {
e.preventDefault()
e.stopPropagation()
onDeleteClick()
if (onDeleteClick) {
onDeleteClick()
}
}}
>
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -100,7 +103,9 @@ export const CardInProject: FC<TCardInProjectProps> = ({
onClick={e => {
e.preventDefault()
e.stopPropagation()
onEditClick()
if (onEditClick) {
onEditClick()
}
}}
>
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -0,0 +1 @@
export * from './MarketplaceCard'

View File

@@ -1,3 +1,3 @@
export * from './AddEditFormModal'
export * from './CardInProject'
export * from './MarketplaceCard'
export * from './SearchTextInput'

View File

@@ -1,207 +1,21 @@
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, Row, Col, Spin, Button } from 'antd'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { BASE_API_GROUP, BASE_RPROJECTS_VERSION } from 'constants/customizationApiGroupAndVersion'
import React, { FC } from 'react'
import { Row, Col } from 'antd'
import { ContentCard, MarketPlace } from 'components'
import { DropdownActions, DropdownAccessGroups } from './molecules'
import { Styled } from './styled'
import { ProjectInfoCard } from './organisms'
export const ProjectInfo: FC = () => {
const navigate = useNavigate()
const { clusterName, namespace } = useParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
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')
const readyCondition = project.status.conditions.find(({ type }) => type !== 'Ready')
return (
<>
<Row gutter={[24, 24]} style={{ flexGrow: 1 }}>
<Col span={13}>
<ContentCard flexGrow={1}>
<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>
</ContentCard>
</Col>
<Col span={11}>
<ContentCard flexGrow={1}>
<MarketPlace />
</ContentCard>
</Col>
</Row>
{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}`}
/>
)}
</>
<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>
)
}

View File

@@ -0,0 +1,238 @@
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')
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}>
{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}`}
/>
)}
</>
)
}

View File

@@ -0,0 +1 @@
export * from './ProjectInfoCard'

View File

@@ -0,0 +1,27 @@
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,
}

View File

@@ -0,0 +1 @@
export * from './ProjectInfoCard'