This commit is contained in:
typescreep
2025-11-11 05:29:33 +03:00
parent dfd83addb6
commit 67d5b09bab
50 changed files with 680 additions and 1466 deletions

View File

@@ -18,20 +18,19 @@ import {
ListInsideApiPage,
ListInsideCrdByApiGroupPage,
ListInsideApiByApiGroupPage,
TableCrdPage,
// TableCrdPage,
TableApiPage,
TableBuiltinPage,
FormBuiltinPage,
FormApiPage,
FormCrdPage,
// FormCrdPage,
FactoryPage,
FactoryAdminPage,
// FactoryAdminPage,
SearchPage,
ListThenWatchPage,
} from 'pages'
import { getBasePrefix } from 'utils/getBaseprefix'
import { colorsLight, colorsDark, sizes } from 'constants/colors'
import { MainLayout } from 'templates/MainLayout'
import { MainLayout, AppShell } from 'templates'
type TAppProps = {
isFederation?: boolean
@@ -62,6 +61,25 @@ export const App: FC<TAppProps> = ({ isFederation, forcedTheme }) => {
<Route path={`${prefix}/clusters/:clusterName`} element={<ListProjectsPage />} />
<Route path={`${prefix}/inside/`} element={<MainPage />} />
<Route path={`${prefix}/clusters/:clusterName/projects/:namespace`} element={<ProjectInfoPage />} />
<Route path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/*`} element={<AppShell />}>
{/* <Route path="crd-table/:apiGroup/:apiVersion/:apiExtensionVersion/:crdName" element={<TableCrdPage />} /> */}
<Route path="api-table/:apiGroup/:apiVersion/:typeName" element={<TableApiPage />} />
<Route path="builtin-table/:typeName" element={<TableBuiltinPage />} />
{/* <Route path="forms/crds/:apiGroup/:apiVersion/:typeName/:entryName?/"" element={<FormCrdPage />} /> */}
<Route path="forms/builtin/:apiVersion/:typeName/:entryName?/" element={<FormBuiltinPage />} />
<Route path="forms/apis/:apiGroup/:apiVersion/:typeName/:entryName?/" element={<FormApiPage />} />
<Route path="factory/:key/*" element={<FactoryPage />} />
</Route>
<Route path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/*`} element={<AppShell inside />}>
{/* <Route path="crd-table/:apiGroup/:apiVersion/:apiExtensionVersion/:crdName" element={<TableCrdPage inside />} /> */}
<Route path="api-table/:apiGroup/:apiVersion/:typeName" element={<TableApiPage inside />} />
<Route path="builtin-table/:typeName" element={<TableBuiltinPage inside />} />
{/* <Route path="forms/crds/:apiGroup/:apiVersion/:typeName/:entryName?/"" element={<FormCrdPage />} /> */}
<Route path="forms/builtin/:apiVersion/:typeName/:entryName?/" element={<FormBuiltinPage />} />
<Route path="forms/apis/:apiGroup/:apiVersion/:typeName/:entryName?/" element={<FormApiPage />} />
</Route>
<Route path={`${prefix}/inside/clusters`} element={<ListInsideClustersAndNsPage inside />} />
<Route path={`${prefix}/inside/:clusterName/:namespace?/apis`} element={<ListInsideApiPage inside />} />
<Route
@@ -72,64 +90,8 @@ export const App: FC<TAppProps> = ({ isFederation, forcedTheme }) => {
path={`${prefix}/inside/:clusterName/:namespace?/apis-by-api/:apiGroup/:apiVersion/`}
element={<ListInsideApiByApiGroupPage inside />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/crd-table/:apiGroup/:apiVersion/:apiExtensionVersion/:crdName`}
element={<TableCrdPage />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/api-table/:apiGroup/:apiVersion/:typeName`}
element={<TableApiPage />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/builtin-table/:typeName`}
element={<TableBuiltinPage />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/forms/builtin/:apiVersion/:typeName/:entryName?/`}
element={<FormBuiltinPage />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/forms/apis/:apiGroup/:apiVersion/:typeName/:entryName?/`}
element={<FormApiPage />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/forms/crds/:apiGroup/:apiVersion/:typeName/:entryName?/`}
element={<FormCrdPage />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/crd-table/:apiGroup/:apiVersion/:apiExtensionVersion/:crdName`}
element={<TableCrdPage inside />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/api-table/:apiGroup/:apiVersion/:typeName`}
element={<TableApiPage inside />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/builtin-table/:typeName`}
element={<TableBuiltinPage inside />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/forms/builtin/:apiVersion/:typeName/:entryName?/`}
element={<FormBuiltinPage inside />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/forms/apis/:apiGroup/:apiVersion/:typeName/:entryName?/`}
element={<FormApiPage inside />}
/>
<Route
path={`${prefix}/inside/:clusterName/:namespace?/:syntheticProject?/forms/crds/:apiGroup/:apiVersion/:typeName/:entryName?/`}
element={<FormCrdPage inside />}
/>
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/factory/:key/*`}
element={<FactoryPage />}
/>
<Route path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/search/*`} element={<SearchPage />} />
<Route path={`${prefix}/factory-admin/*`} element={<FactoryAdminPage />} />
<Route
path={`${prefix}/:clusterName/:namespace?/:syntheticProject?/list-then-watch/*`}
element={<ListThenWatchPage />}
/>
</Route>
</Routes>
)

View File

@@ -1,52 +0,0 @@
import React, { useState } from 'react'
import { Modal, Button, Form, Select } from 'antd'
import { ComponentType } from './types'
export const componentTypes: ComponentType[] = [
'antdText',
'antdCard',
'antdFlex',
'antdRow',
'antdCol',
'partsOfUrl',
'multiQuery',
'parsedText',
]
interface AddComponentModalProps {
onAdd: (type: ComponentType) => void
title?: string
}
export const AddComponentModal: React.FC<AddComponentModalProps> = ({ onAdd, title = 'Add Component' }) => {
const [visible, setVisible] = useState(false)
const [form] = Form.useForm()
const handleSubmit = (values: { type: ComponentType }) => {
onAdd(values.type)
setVisible(false)
form.resetFields()
}
return (
<>
<Button onClick={() => setVisible(true)}>Add</Button>
<Modal title={title} visible={visible} onCancel={() => setVisible(false)} footer={null}>
<Form form={form} onFinish={handleSubmit}>
<Form.Item label="Component Type" name="type" rules={[{ required: true }]}>
<Select>
{componentTypes.map(type => (
<Select.Option key={type} value={type}>
{type}
</Select.Option>
))}
</Select>
</Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form>
</Modal>
</>
)
}

View File

@@ -1,73 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect } from 'react'
import { Form, Input, Select, Button, Switch } from 'antd'
const { TextArea } = Input
const { Option } = Select
interface AntdCardFormProps {
initialValues: any // This would be `TDynamicComponentsAppTypeMap['antdCard']`
onSave: (data: any) => void
}
export const AntdCardForm: React.FC<AntdCardFormProps> = ({ initialValues, onSave }) => {
const [form] = Form.useForm()
useEffect(() => {
form.setFieldsValue(initialValues)
}, [initialValues, form])
const handleSubmit = (values: any) => {
try {
if (values.style) {
values.style = JSON.parse(values.style)
}
} catch (e) {
console.log('Invalid JSON for style', e)
return
}
onSave(values)
}
return (
<Form form={form} onFinish={handleSubmit} layout="vertical" initialValues={initialValues}>
<Form.Item label="ID" name="id" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
<Form.Item label="Title" name="title">
<Input placeholder="Card title" />
</Form.Item>
<Form.Item label="Bordered" name="bordered" valuePropName="checked">
<Switch defaultChecked />
</Form.Item>
<Form.Item label="Cover Image URL" name="cover">
<Input placeholder="https://example.com/image.jpg" />
</Form.Item>
<Form.Item label="Style (JSON)" name="style">
<TextArea rows={4} placeholder='e.g. {"width": "300px", "margin": "10px"}' />
</Form.Item>
<Form.Item label="Size" name="size">
<Select defaultValue="default">
<Option value="default">Default</Option>
<Option value="small">Small</Option>
</Select>
</Form.Item>
<Form.Item label="Extra Content (Right Side)" name="extra">
<Input placeholder="Extra content like links or buttons" />
</Form.Item>
<Button type="primary" htmlType="submit">
Save
</Button>
</Form>
)
}

View File

@@ -1,55 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect } from 'react'
import { Form, Input, Select, Button } from 'antd'
interface AntdTextFormProps {
initialValues: any
onSave: (data: any) => void
}
export const AntdTextForm: React.FC<AntdTextFormProps> = ({ initialValues, onSave }) => {
const [form] = Form.useForm()
useEffect(() => {
form.setFieldsValue(initialValues)
}, [initialValues, form])
const handleSubmit = (values: any) => {
try {
if (values.style) {
values.style = JSON.parse(values.style)
}
} catch (e) {
console.log('Invalid JSON for style', e)
}
onSave(values)
}
return (
<Form form={form} onFinish={handleSubmit} layout="vertical" initialValues={initialValues}>
<Form.Item label="ID" name="id" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
<Form.Item label="Text" name="text" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="Type" name="type">
<Select allowClear>
<Select.Option value="">Default</Select.Option>
<Select.Option value="secondary">Secondary</Select.Option>
<Select.Option value="success">Success</Select.Option>
<Select.Option value="warning">Warning</Select.Option>
<Select.Option value="danger">Danger</Select.Option>
</Select>
</Form.Item>
<Form.Item label="Style (JSON)" name="style">
<Input.TextArea rows={4} placeholder='e.g. {"color": "red"}' />
</Form.Item>
<Button type="primary" htmlType="submit">
Save
</Button>
</Form>
)
}

View File

@@ -1,128 +0,0 @@
/* eslint-disable no-console */
/* TODO: REFACTOR */
import React, { useState } from 'react'
import { Button, Typography, Modal, Input } from 'antd'
import { ComponentNode } from './ComponentNode'
import { AddComponentModal } from './AddComponentModal'
import { Component, ComponentType, TDynamicComponentsAppTypeMap } from './types'
const { Title } = Typography
const { TextArea } = Input
export const AppComponentAdmin: React.FC = () => {
const [components, setComponents] = useState<Component[]>([])
const [maxId, setMaxId] = useState(1)
const [jsonOutput, setJsonOutput] = useState<string>('')
const [isModalVisible, setIsModalVisible] = useState(false)
const getNewId = () => {
const newId = maxId + 1
setMaxId(newId)
return newId
}
const generateDefaultData = (type: ComponentType): TDynamicComponentsAppTypeMap[ComponentType] => {
const base = { id: getNewId() }
switch (type) {
case 'antdText':
return { ...base, text: '' }
case 'antdCard':
return { ...base, title: '' }
case 'antdFlex':
return { ...base, justify: 'start', align: 'start' }
case 'antdRow':
return { ...base }
case 'antdCol':
return { ...base, span: 12 }
case 'partsOfUrl':
case 'multiQuery':
case 'parsedText':
return { ...base, text: '' }
default:
return base
}
}
const addComponent = (type: ComponentType) => {
const newComponent: Component = {
type,
data: generateDefaultData(type),
children: [],
}
setComponents(prev => [...prev, newComponent])
}
const updateComponent = (index: number, updated: Component) => {
setComponents(prev => {
const newComponents = [...prev]
newComponents[index] = updated
return newComponents
})
}
const deleteComponent = (index: number) => {
setComponents(prev => {
const newComponents = [...prev]
newComponents.splice(index, 1)
return newComponents
})
}
const handleSaveAll = () => {
const output = JSON.stringify(
{
data: components,
},
null,
2,
)
setJsonOutput(output)
setIsModalVisible(true)
}
const handleCopyToClipboard = () => {
navigator.clipboard.writeText(jsonOutput)
console.log('Copied to clipboard!')
}
return (
<div style={{ padding: 20 }}>
<Title level={3}>Component Admin Panel</Title>
<AddComponentModal onAdd={addComponent} title="Add Root Component" />
<div style={{ marginTop: 20 }}>
{components.map((component, index) => (
<ComponentNode
key={component.data.id}
component={component}
onUpdate={updated => updateComponent(index, updated)}
onDelete={() => deleteComponent(index)}
/>
))}
</div>
{/* Save Button */}
<Button type="primary" onClick={handleSaveAll} style={{ marginTop: 20 }}>
Save All as JSON
</Button>
{/* JSON Output Modal */}
<Modal
title="Final JSON Output"
open={isModalVisible}
onCancel={() => setIsModalVisible(false)}
footer={[
<Button key="copy" onClick={handleCopyToClipboard}>
Copy to Clipboard
</Button>,
<Button key="close" onClick={() => setIsModalVisible(false)}>
Close
</Button>,
]}
>
<TextArea value={jsonOutput} readOnly rows={20} style={{ fontFamily: 'monospace' }} />
</Modal>
</div>
)
}

View File

@@ -1,15 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// src/ComponentForm.tsx
import React from 'react'
import { DynamicComponentForm } from './DynamicComponentForm'
import type { ComponentType } from './types'
interface ComponentFormProps {
type: ComponentType
data: any
onSave: (data: any) => void
}
export const ComponentForm: React.FC<ComponentFormProps> = ({ type, data, onSave }) => {
return <DynamicComponentForm type={type} initialValues={data} onSave={onSave} />
}

View File

@@ -1,27 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react'
import { ComponentType } from './types'
import { AntdTextForm } from './AntdTextForm'
import { AntdCardForm } from './AntdCardForm'
import { TextWithIdForm } from './TextWithIdForm'
interface ComponentFormProps {
type: ComponentType
data: any
onSave: (data: any) => void
}
export const ComponentForm: React.FC<ComponentFormProps> = ({ type, data, onSave }) => {
switch (type) {
case 'antdText':
return <AntdTextForm initialValues={data} onSave={onSave} />
case 'antdCard':
return <AntdCardForm initialValues={data} onSave={onSave} />
case 'partsOfUrl':
case 'multiQuery':
case 'parsedText':
return <TextWithIdForm initialValues={data} onSave={onSave} />
default:
return <div>Unsupported component type</div>
}
}

View File

@@ -1,64 +0,0 @@
import React from 'react'
import { Button } from 'antd'
import { Component, ComponentType } from './types'
import { EditComponentModal } from './EditComponentModal'
import { AddComponentModal } from './AddComponentModal'
import { generateDefaultData } from './utils'
interface ComponentNodeProps {
component: Component
onUpdate: (component: Component) => void
onDelete: () => void
}
export const ComponentNode: React.FC<ComponentNodeProps> = ({ component, onUpdate, onDelete }) => {
const handleAddChild = (type: ComponentType) => {
const newChild: Component = {
type,
data: generateDefaultData(type),
children: [],
}
const updated = { ...component }
updated.children = updated.children ? [...updated.children, newChild] : [newChild]
onUpdate(updated)
}
const handleUpdateChild = (index: number, updatedChild: Component) => {
const updated = { ...component }
updated.children![index] = updatedChild
onUpdate(updated)
}
const handleDeleteChild = (index: number) => {
const updated = { ...component }
updated.children!.splice(index, 1)
onUpdate(updated)
}
return (
<div style={{ border: '1px solid #ccc', padding: 10, margin: '10px 0' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
<strong>{component.type}</strong> (ID: {component.data.id})
</span>
<div>
<EditComponentModal component={component} onSave={onUpdate} />
<AddComponentModal onAdd={handleAddChild} title="Add Child" />
<Button danger size="small" onClick={onDelete} style={{ marginLeft: 8 }}>
Delete
</Button>
</div>
</div>
<div style={{ marginLeft: 20, marginTop: 10 }}>
{component.children?.map((child, idx) => (
<ComponentNode
key={child.data.id}
component={child}
onUpdate={updated => handleUpdateChild(idx, updated)}
onDelete={() => handleDeleteChild(idx)}
/>
))}
</div>
</div>
)
}

View File

@@ -1,111 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
// src/DynamicComponentForm.tsx
import React, { useEffect } from 'react'
import { Form, Input, Select, Switch, Button, Typography } from 'antd'
import { componentMetaMap } from './utils'
import type { ComponentType } from './types'
const { TextArea } = Input
const { Option } = Select
interface DynamicComponentFormProps {
type: ComponentType
initialValues: any
onSave: (data: any) => void
}
export const DynamicComponentForm: React.FC<DynamicComponentFormProps> = ({ type, initialValues, onSave }) => {
const [form] = Form.useForm()
useEffect(() => {
form.setFieldsValue(initialValues)
}, [initialValues, form])
const handleSubmit = (values: any) => {
try {
const parsedValues = Object.entries(values).reduce((acc, [key, value]) => {
const meta = componentMetaMap[type]?.[key]
if (meta?.inputType === 'json') {
if (typeof value === 'string') {
try {
acc[key] = JSON.parse(value)
} catch (e) {
console.warn(`Invalid JSON for ${key}:`, value)
acc[key] = value // Keep original string
}
} else {
acc[key] = value // Already parsed or non-string
}
} else {
acc[key] = value
}
return acc
}, {} as any)
onSave(parsedValues)
} catch (e) {
console.log('Invalid JSON input', e)
}
}
const renderFormItem = (prop: string, meta: any) => {
const value = initialValues[prop]
switch (meta.inputType) {
case 'text':
return <Input placeholder={meta.label} />
case 'textarea':
return <TextArea rows={4} placeholder={meta.label} />
case 'json':
return (
<TextArea
rows={4}
placeholder={`e.g. {"color": "red"}`}
defaultValue={value ? JSON.stringify(value, null, 2) : ''}
/>
)
case 'boolean':
return <Switch defaultChecked={value} />
case 'select':
return (
<Select defaultValue={value}>
{meta.options?.map((opt: { label: string; value: string }) => (
<Option key={opt.value} value={opt.value}>
{opt.label}
</Option>
))}
</Select>
)
case 'number':
return <Input type="number" placeholder={meta.label} />
default:
return <Input />
}
}
return (
<Form form={form} onFinish={handleSubmit} layout="vertical">
{/* ID (always present and read-only) */}
<Form.Item label="ID" name="id" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
{/* Render dynamic props */}
{Object.entries(componentMetaMap[type] || {}).map(([prop, meta]) => (
<Form.Item key={prop} label={meta.label} name={prop}>
{renderFormItem(prop, meta)}
</Form.Item>
))}
{/* Submit Button */}
<Button type="primary" htmlType="submit" style={{ marginTop: 16 }}>
Save
</Button>
</Form>
)
}

View File

@@ -1,34 +0,0 @@
/* eslint-disable react/button-has-type */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState } from 'react'
import { Modal } from 'antd'
import { ComponentForm } from './ComponentForm'
import { Component } from './types'
interface EditComponentModalProps {
component: Component
onSave: (component: Component) => void
}
export const EditComponentModal: React.FC<EditComponentModalProps> = ({ component, onSave }) => {
const [open, setOpen] = useState(false)
const handleSave = (data: any) => {
onSave({ ...component, data })
setOpen(false)
}
return (
<>
<button onClick={() => setOpen(true)}>Edit</button>
<Modal
title={`Edit ${component.type}`}
open={open} // ✅ Use open instead of visible
onCancel={() => setOpen(false)}
footer={null}
>
<ComponentForm type={component.type} data={component.data} onSave={handleSave} />
</Modal>
</>
)
}

View File

@@ -1,39 +0,0 @@
import React, { useEffect } from 'react'
import { Form, Input, Button } from 'antd'
interface TextWithIdFormProps {
initialValues: {
id: number
text: string
}
onSave: (data: { id: number; text: string }) => void
}
export const TextWithIdForm: React.FC<TextWithIdFormProps> = ({ initialValues, onSave }) => {
const [form] = Form.useForm()
useEffect(() => {
form.setFieldsValue(initialValues)
}, [initialValues, form])
const handleSubmit = (values: { id: number; text: string }) => {
onSave(values)
}
return (
<Form form={form} onFinish={handleSubmit} layout="vertical" initialValues={initialValues}>
<Form.Item label="ID" name="id" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
<Form.Item label="Text" name="text" rules={[{ required: true }]}>
<Input.TextArea
rows={4}
placeholder="Enter text (you can use variables like {5}, {reqs[0]['metadata']} etc.)"
/>
</Form.Item>
<Button type="primary" htmlType="submit">
Save
</Button>
</Form>
)
}

View File

@@ -1 +0,0 @@
export { AppComponentAdmin as FactoryAdminPage } from './AppComponentAdmin'

View File

@@ -1,26 +0,0 @@
import type {
CardProps as AntdCardProps,
FlexProps as AntdFlexProps,
RowProps as AntdRowProps,
ColProps as AntdColProps,
} from 'antd'
import { TextProps as AntdTextProps } from 'antd/es/typography/Text'
export type TDynamicComponentsAppTypeMap = {
antdText: { id: number; text: string } & Omit<AntdTextProps, 'id'>
antdCard: { id: number } & Omit<AntdCardProps, 'id'>
antdFlex: { id: number } & Omit<AntdFlexProps, 'id' | 'children'>
antdRow: { id: number } & Omit<AntdRowProps, 'id' | 'children'>
antdCol: { id: number } & Omit<AntdColProps, 'id' | 'children'>
partsOfUrl: { id: number; text: string }
multiQuery: { id: number; text: string }
parsedText: { id: number; text: string }
}
export type ComponentType = keyof TDynamicComponentsAppTypeMap
export type Component = {
type: ComponentType
data: TDynamicComponentsAppTypeMap[ComponentType]
children?: Component[]
}

View File

@@ -1,111 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// src/utils.ts
import type { ComponentType, TDynamicComponentsAppTypeMap } from './types'
export const generateDefaultData = (type: ComponentType): TDynamicComponentsAppTypeMap[ComponentType] => {
const base = { id: Math.floor(Math.random() * 100000) } // Simplified ID generation
switch (type) {
case 'antdText':
return { ...base, text: '' }
case 'antdCard':
return { ...base, title: '' }
case 'antdFlex':
return { ...base, justify: 'start', align: 'start' }
case 'antdRow':
return { ...base }
case 'antdCol':
return { ...base, span: 12 }
case 'partsOfUrl':
case 'multiQuery':
case 'parsedText':
return { ...base, text: '' }
default:
return base
}
}
// Define property metadata for each component
type PropertyMeta = {
label: string
inputType: 'text' | 'textarea' | 'json' | 'boolean' | 'select' | 'number'
options?: { label: string; value: any }[]
}
type ComponentMetaMap = {
[key in ComponentType]: Record<string, PropertyMeta>
}
export const componentMetaMap: ComponentMetaMap = {
antdText: {
text: { label: 'Text', inputType: 'textarea' },
type: {
label: 'Type',
inputType: 'select',
options: [
{ label: 'Default', value: '' },
{ label: 'Secondary', value: 'secondary' },
{ label: 'Success', value: 'success' },
{ label: 'Warning', value: 'warning' },
{ label: 'Danger', value: 'danger' },
],
},
style: { label: 'Style (JSON)', inputType: 'json' },
},
antdCard: {
title: { label: 'Title', inputType: 'text' },
bordered: { label: 'Bordered', inputType: 'boolean' },
cover: { label: 'Cover Image URL', inputType: 'text' },
size: {
label: 'Size',
inputType: 'select',
options: [
{ label: 'Default', value: 'default' },
{ label: 'Small', value: 'small' },
],
},
extra: { label: 'Extra Content', inputType: 'text' },
style: { label: 'Style (JSON)', inputType: 'json' },
},
antdFlex: {
justify: {
label: 'Justify',
inputType: 'select',
options: [
{ label: 'Start', value: 'start' },
{ label: 'End', value: 'end' },
{ label: 'Center', value: 'center' },
{ label: 'Space Around', value: 'space-around' },
{ label: 'Space Between', value: 'space-between' },
],
},
align: {
label: 'Align',
inputType: 'select',
options: [
{ label: 'Start', value: 'start' },
{ label: 'End', value: 'end' },
{ label: 'Center', value: 'center' },
{ label: 'Baseline', value: 'baseline' },
{ label: 'Stretch', value: 'stretch' },
],
},
gap: { label: 'Gap (number)', inputType: 'number' },
},
antdRow: {
gutter: { label: 'Gutter', inputType: 'number' },
style: { label: 'Style (JSON)', inputType: 'json' },
},
antdCol: {
span: { label: 'Span (1-24)', inputType: 'number' },
offset: { label: 'Offset', inputType: 'number' },
},
partsOfUrl: {
text: { label: 'Text', inputType: 'textarea' },
},
multiQuery: {
text: { label: 'Text', inputType: 'textarea' },
},
parsedText: {
text: { label: 'Text', inputType: 'textarea' },
},
}

View File

@@ -1,44 +1,23 @@
import React, { FC, useState } from 'react'
import { useParams } from 'react-router-dom'
import { ManageableBreadcrumbs, ManageableSidebar, Factory, NavigationContainer } from 'components'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { BaseTemplate } from 'templates'
import React, { FC, useEffect } from 'react'
import { useParams, useOutletContext } from 'react-router-dom'
import { Factory } from 'components'
import { TChromeCtx } from 'templates'
export const FactoryPage: FC = () => {
const { namespace, syntheticProject, key } = useParams()
const { key } = useParams()
const [currentTags, setCurrentTags] = useState<string[]>()
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix } = useOutletContext<TChromeCtx>()
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
useEffect(() => {
setSidebarSuffix(`factory-${key}`)
setBreadcrumbsSuffix(`factory-${key}`)
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
})}factory-${key}`
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
}
}, [key, setSidebarSuffix, setBreadcrumbsSuffix, setCurrentTags])
const sidebarId = `${getSidebarIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
})}factory-${key}`
return (
<BaseTemplate
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={currentTags}
/>
}
// withNoCluster
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} />
</NavigationContainer>
<Factory setSidebarTags={setCurrentTags} key={key} />
</BaseTemplate>
)
return <Factory setSidebarTags={setCurrentTags} key={key} />
}

View File

@@ -1,97 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import {
CreateApisForm,
UpdateApisForm,
BackLink,
ManageableBreadcrumbs,
ManageableSidebar,
NavigationContainer,
} from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { BaseTemplate } from 'templates'
type TFormApiPageProps = {
inside?: boolean
}
export const FormApiPage: FC<TFormApiPageProps> = ({ inside }) => {
const { clusterName, syntheticProject, namespace, apiGroup, apiVersion, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
if (!typeName || !apiGroup || !apiVersion) {
return null
}
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}api-form`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}api-form${entryName ? '-edit' : ''}`
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={[`${apiGroup}/${apiVersion}/${typeName}`]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
<BackLink
to={preparedBacklink}
title={`${entryName ? 'Update' : 'Create'} ${apiGroup}/${apiVersion}/${typeName}${
entryName ? `/${entryName}` : ''
}`}
/>
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateApisForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
entryName={entryName}
backLink={backLink}
/>
) : (
<CreateApisForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
backLink={backLink}
/>
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -1,82 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import {
CreateBuiltinForm,
UpdateBuiltinForm,
BackLink,
ManageableBreadcrumbs,
ManageableSidebar,
NavigationContainer,
} from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { BaseTemplate } from 'templates'
type TFormBuiltinPageProps = {
inside?: boolean
}
export const FormBuiltinPage: FC<TFormBuiltinPageProps> = ({ inside }) => {
const { clusterName, syntheticProject, namespace, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
if (!typeName) {
return null
}
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}builtin-form`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}builtin-form${entryName ? '-edit' : ''}`
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={[typeName]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
<BackLink
to={preparedBacklink}
title={`${entryName ? 'Update' : 'Create'} ${typeName}${entryName ? `/${entryName}` : ''}`}
/>
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateBuiltinForm namespace={namespace} typeName={typeName} entryName={entryName} backLink={backLink} />
) : (
<CreateBuiltinForm namespace={namespace} typeName={typeName} backLink={backLink} />
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -1,97 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import {
CreateCrdsForm,
UpdateCrdsForm,
BackLink,
ManageableBreadcrumbs,
ManageableSidebar,
NavigationContainer,
} from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { BaseTemplate } from 'templates'
type TFormCrdPageProps = {
inside?: boolean
}
export const FormCrdPage: FC<TFormCrdPageProps> = ({ inside }) => {
const { clusterName, syntheticProject, apiGroup, apiVersion, namespace, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
if (!typeName || !apiGroup || !apiVersion) {
return null
}
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}crd-form`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}crd-form${entryName ? '-edit' : ''}`
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={[`${apiGroup}/${apiVersion}/${typeName}`]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
<BackLink
to={preparedBacklink}
title={`${entryName ? 'Update' : 'Create'} ${apiGroup}/${apiVersion}/${typeName}${
entryName ? `/${entryName}` : ''
}`}
/>
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateCrdsForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
entryName={entryName}
backLink={backLink}
/>
) : (
<CreateCrdsForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
backLink={backLink}
/>
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -0,0 +1,87 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { CreateApisForm, UpdateApisForm } from 'components'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { TChromeCtx } from 'templates'
export const FormApiPage: FC = () => {
const { clusterName, syntheticProject, namespace, apiGroup, apiVersion, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('api-form')
setBreadcrumbsSuffix(`api-form${entryName ? '-edit' : ''}`)
setCurrentTags([`${apiGroup}/${apiVersion}/${typeName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(
`${entryName ? 'Update' : 'Create'} ${apiGroup}/${apiVersion}/${typeName}${entryName ? `/${entryName}` : ''}`,
)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(undefined)
}
}, [
apiGroup,
apiVersion,
typeName,
entryName,
preparedBacklink,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
if (!typeName || !apiGroup || !apiVersion) {
return null
}
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateApisForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
entryName={entryName}
backLink={backLink}
/>
) : (
<CreateApisForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
backLink={backLink}
/>
)}
</ContentCard>
)
}

View File

@@ -0,0 +1,71 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { CreateBuiltinForm, UpdateBuiltinForm } from 'components'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { TChromeCtx } from 'templates'
export const FormBuiltinPage: FC = () => {
const { clusterName, syntheticProject, namespace, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('builtin-form')
setBreadcrumbsSuffix(`builtin-form${entryName ? '-edit' : ''}`)
setCurrentTags([`${typeName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(`${entryName ? 'Update' : 'Create'} ${typeName}${entryName ? `/${entryName}` : ''}`)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(undefined)
}
}, [
typeName,
entryName,
preparedBacklink,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
if (!typeName) {
return null
}
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateBuiltinForm namespace={namespace} typeName={typeName} entryName={entryName} backLink={backLink} />
) : (
<CreateBuiltinForm namespace={namespace} typeName={typeName} backLink={backLink} />
)}
</ContentCard>
)
}

View File

@@ -0,0 +1,87 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { CreateCrdsForm, UpdateCrdsForm } from 'components'
import { getFormsBackLink } from 'utils/getBacklink'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { TChromeCtx } from 'templates'
export const FormCrdPage: FC = () => {
const { clusterName, syntheticProject, apiGroup, apiVersion, namespace, typeName, entryName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backLink = searchParams.get('backlink')?.startsWith('/') ? searchParams.get('backlink') : undefined
const preparedBacklink = getFormsBackLink({
backLink,
clusterName,
possibleProject,
possibleInstance,
baseprefix,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('crd-form')
setBreadcrumbsSuffix(`crd-form${entryName ? '-edit' : ''}`)
setCurrentTags([`${apiGroup}/${apiVersion}/${typeName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(
`${entryName ? 'Update' : 'Create'} ${apiGroup}/${apiVersion}/${typeName}${entryName ? `/${entryName}` : ''}`,
)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(undefined)
}
}, [
apiGroup,
apiVersion,
typeName,
entryName,
preparedBacklink,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
if (!typeName || !apiGroup || !apiVersion) {
return null
}
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{entryName ? (
<UpdateCrdsForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
entryName={entryName}
backLink={backLink}
/>
) : (
<CreateCrdsForm
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
backLink={backLink}
/>
)}
</ContentCard>
)
}

3
src/pages/Forms/index.ts Normal file
View File

@@ -0,0 +1,3 @@
// export { FormCrdPage } from './FormCrdPage'
export { FormApiPage } from './FormApiPage'
export { FormBuiltinPage } from './FormBuiltinPage'

View File

@@ -0,0 +1,4 @@
export { ListInsideClustersAndNsPage } from './ListInsideClustersAndNsPage'
export { ListInsideApiPage } from './ListInsideApiPage'
export { ListInsideCrdByApiGroupPage } from './ListInsideCrdByApiGroupPage'
export { ListInsideApiByApiGroupPage } from './ListInsideApiByApiGroupPage'

View File

@@ -1,81 +0,0 @@
/* eslint-disable no-void */
/* eslint-disable no-console */
/* eslint-disable react/button-has-type */
import { useState, FC } from 'react'
import { useListWatch, TUseListWatchQuery } from '@prorobotech/openapi-k8s-toolkit'
export const ListThenWatchPage: FC = () => {
const [queryState, setQueryState] = useState<TUseListWatchQuery>({
apiGroup: '',
apiVersion: 'v1',
plural: 'pods',
namespace: 'incloud-web',
})
const { state, total, status, lastError, reconnect } = useListWatch({
wsUrl: '/api/clusters/default/openapi-bff-ws/listThenWatch/listWatchWs',
paused: false,
ignoreRemove: false,
autoDrain: true,
preserveStateOnUrlChange: true,
isEnabled: true,
query: queryState,
})
const switchNamespace = (ns: string) => {
setQueryState(prev => ({
...prev,
namespace: ns,
initialContinue: undefined,
}))
}
if (status === 'connecting') {
return <div>🔄 Connecting to API server</div>
}
if (status === 'closed' && lastError) {
return <div className="text-red-600"> Error: {lastError}</div>
}
if (status === 'closed') {
return (
<div>
Connection closed. <button onClick={reconnect}>Retry</button>
</div>
)
}
return (
<div className="p-4">
<header className="mb-3 flex gap-2 items-center">
<span>
Status: <b>{status}</b>
</span>
{lastError && <span className="text-red-600"> {lastError}</span>}
<button onClick={() => reconnect()} className="px-2 py-1 border rounded">
Reconnect
</button>
<button onClick={() => switchNamespace('kube-system')} className="px-2 py-1 border rounded">
kube-system
</button>
<span className="ml-auto">Total: {total}</span>
</header>
<ul className="space-y-1">
{state.order.map(key => {
const res = state.byKey[key]
const name = res.metadata?.name ?? key
const ns = res.metadata?.namespace ?? ''
return (
<li key={key} className="border rounded p-2">
<div className="text-sm">
{ns}/{name}
</div>
</li>
)
})}
</ul>
</div>
)
}

View File

@@ -1 +0,0 @@
export { ListThenWatchPage } from './ListThenWatchPage'

View File

@@ -1,97 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableApiBuiltin, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { BaseTemplate } from 'templates'
import {
BASE_USE_NAMESPACE_NAV,
BASE_PROJECTS_API_GROUP,
BASE_PROJECTS_VERSION,
BASE_PROJECTS_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
type TTableApiPageProps = {
inside?: boolean
}
export const TableApiPage: FC<TTableApiPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, apiGroup, apiVersion, typeName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backlink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}api-table`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}api-table`
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
const isProjectList =
!namespace &&
apiGroup === BASE_PROJECTS_API_GROUP &&
apiVersion === BASE_PROJECTS_VERSION &&
typeName === BASE_PROJECTS_RESOURCE_NAME
const sidebarIdProjectList = `${getSidebarIdPrefix({})}projects-list`
const breadcrumbsIdProjectList = `${getBreadcrumbsIdPrefix({})}projects-list`
const limitSp = searchParams.get('limit')
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={isProjectList ? sidebarIdProjectList : sidebarId}
currentTags={[`${apiGroup}/${apiVersion}/${typeName}`]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={isProjectList ? breadcrumbsIdProjectList : breadcrumbsId} inside={inside} />
<BackLink to={backlink} title={`${apiGroup}/${apiVersion}/${typeName}`} />
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && apiGroup && apiVersion && (
<TableApiBuiltin
resourceType="api"
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
key={`${apiGroup}-${apiVersion}-${namespace}-${typeName}`}
limit={limitSp && limitSp.length > 0 ? Number(limitSp) : undefined}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -1,83 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableApiBuiltin, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { BaseTemplate } from 'templates'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
type TTableBuiltinPageProps = {
inside?: boolean
}
export const TableBuiltinPage: FC<TTableBuiltinPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, typeName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backlink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}builtin-table`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}builtin-table`
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
const limitSp = searchParams.get('limit')
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={[`${typeName}`]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
<BackLink to={backlink} title={typeName} />
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && (
<TableApiBuiltin
resourceType="builtin"
apiVersion="v1"
key={`${namespace}-${typeName}`}
namespace={namespace}
typeName={typeName}
limit={limitSp && limitSp.length > 0 ? Number(limitSp) : undefined}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -1,79 +0,0 @@
import React, { FC } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableCrdInfo, BackLink, ManageableBreadcrumbs, ManageableSidebar, NavigationContainer } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { BaseTemplate } from 'templates'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
type TTableCrdPageProps = {
inside?: boolean
}
export const TableCrdPage: FC<TTableCrdPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, apiGroup, apiVersion, apiExtensionVersion, crdName } = useParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const backlink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const sidebarId = `${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}crd-table`
const breadcrumbsId = `${getBreadcrumbsIdPrefix({
instance: !!syntheticProject,
project: !!namespace,
inside,
})}crd-table`
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
return (
<BaseTemplate
inside={inside}
sidebar={
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={[`${apiGroup}/${apiVersion}/${crdName}`]}
/>
}
>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
<BackLink to={backlink} title={`${apiGroup}/${apiVersion}/${crdName}`} />
</NavigationContainer>
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{crdName && apiGroup && apiVersion && apiExtensionVersion && (
<TableCrdInfo
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
crdName={crdName}
apiExtensionVersion={apiExtensionVersion}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
</BaseTemplate>
)
}

View File

@@ -0,0 +1,103 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableApiBuiltin } from 'components'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { TChromeCtx } from 'templates'
import {
BASE_USE_NAMESPACE_NAV,
BASE_PROJECTS_API_GROUP,
BASE_PROJECTS_VERSION,
BASE_PROJECTS_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
type TTableApiPageProps = {
inside?: boolean
}
export const TableApiPage: FC<TTableApiPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, apiGroup, apiVersion, typeName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const isProjectList =
!namespace &&
apiGroup === BASE_PROJECTS_API_GROUP &&
apiVersion === BASE_PROJECTS_VERSION &&
typeName === BASE_PROJECTS_RESOURCE_NAME
const breadcrumbsIdProjectList = 'projects-list'
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const preparedBacklink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('builtin-table')
setBreadcrumbsSuffix(isProjectList ? breadcrumbsIdProjectList : 'api-table')
setCurrentTags([`${typeName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(undefined)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(`${apiGroup}/${apiVersion}/${typeName}`)
}
}, [
typeName,
isProjectList,
breadcrumbsIdProjectList,
preparedBacklink,
apiGroup,
apiVersion,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
const limitSp = searchParams.get('limit')
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && apiGroup && apiVersion && (
<TableApiBuiltin
resourceType="api"
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
typeName={typeName}
key={`${apiGroup}-${apiVersion}-${namespace}-${typeName}`}
limit={limitSp && limitSp.length > 0 ? Number(limitSp) : undefined}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
)
}

View File

@@ -0,0 +1,86 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useSearchParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableApiBuiltin } from 'components'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { TChromeCtx } from 'templates'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
type TTableBuiltinPageProps = {
inside?: boolean
}
export const TableBuiltinPage: FC<TTableBuiltinPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, typeName } = useParams()
const [searchParams] = useSearchParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const preparedBacklink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('builtin-table')
setBreadcrumbsSuffix('builtin-table')
setCurrentTags([`${typeName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(typeName)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(undefined)
}
}, [
typeName,
preparedBacklink,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
const limitSp = searchParams.get('limit')
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{typeName && (
<TableApiBuiltin
resourceType="builtin"
apiVersion="v1"
key={`${namespace}-${typeName}`}
namespace={namespace}
typeName={typeName}
limit={limitSp && limitSp.length > 0 ? Number(limitSp) : undefined}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
)
}

View File

@@ -0,0 +1,84 @@
import React, { FC, useEffect } from 'react'
import { ContentCard } from '@prorobotech/openapi-k8s-toolkit'
import { useParams, useOutletContext } from 'react-router-dom'
import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { TableCrdInfo } from 'components'
import { getTableCustomizationIdPrefix } from 'utils/getTableCustomizationIdPrefix'
import { getTablesBackLink } from 'utils/getBacklink'
import { TChromeCtx } from 'templates'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
type TTableCrdPageProps = {
inside?: boolean
}
export const TableCrdPage: FC<TTableCrdPageProps> = ({ inside }) => {
const { clusterName, namespace, syntheticProject, apiGroup, apiVersion, apiExtensionVersion, crdName } = useParams()
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const preparedBacklink = getTablesBackLink({
clusterName,
possibleProject,
possibleInstance,
namespace,
baseprefix,
inside,
namespacesMode: BASE_USE_NAMESPACE_NAV === 'true',
})
const { setCurrentTags, setSidebarSuffix, setBreadcrumbsSuffix, setBacklinkTo, setBacklinkTitle } =
useOutletContext<TChromeCtx>()
useEffect(() => {
setSidebarSuffix('crd-table')
setBreadcrumbsSuffix('crd-table')
setCurrentTags([`${apiGroup}/${apiVersion}/${crdName}`])
setBacklinkTo(preparedBacklink)
setBacklinkTitle(`${apiGroup}/${apiVersion}/${crdName}`)
return () => {
setCurrentTags(undefined)
setSidebarSuffix(undefined)
setBreadcrumbsSuffix(undefined)
setBacklinkTo(undefined)
setBacklinkTitle(undefined)
}
}, [
apiGroup,
apiVersion,
crdName,
preparedBacklink,
setSidebarSuffix,
setBreadcrumbsSuffix,
setCurrentTags,
setBacklinkTo,
setBacklinkTitle,
])
const tableCustomizationIdPrefix = getTableCustomizationIdPrefix({
instance: !!syntheticProject,
project: BASE_USE_NAMESPACE_NAV !== 'true' && !!namespace,
namespace: !!namespace,
inside,
})
return (
<ContentCard flexGrow={1} displayFlex flexFlow="column">
{crdName && apiGroup && apiVersion && apiExtensionVersion && (
<TableCrdInfo
namespace={namespace}
apiGroup={apiGroup}
apiVersion={apiVersion}
crdName={crdName}
apiExtensionVersion={apiExtensionVersion}
inside={inside}
customizationIdPrefix={tableCustomizationIdPrefix}
/>
)}
</ContentCard>
)
}

View File

@@ -0,0 +1,3 @@
// export { TableCrdPage } from './TableCrdPage'
export { TableApiPage } from './TableApiPage'
export { TableBuiltinPage } from './TableBuiltinPage'

View File

@@ -4,20 +4,25 @@ export { ListClustersPage } from './ListClustersPage'
export { ListProjectsPage } from './ListProjectsPage'
export { ProjectInfoPage } from './ProjectInfoPage'
/* inside routing */
export { ListInsideClustersAndNsPage } from './ListInsideClustersAndNsPage'
export { ListInsideApiPage } from './ListInsideApiPage'
export { ListInsideCrdByApiGroupPage } from './ListInsideCrdByApiGroupPage'
export { ListInsideApiByApiGroupPage } from './ListInsideApiByApiGroupPage'
/* common routing */
export { TableCrdPage } from './TableCrdPage'
export { TableApiPage } from './TableApiPage'
export { TableBuiltinPage } from './TableBuiltinPage'
export { FormCrdPage } from './FormCrdPage'
export { FormApiPage } from './FormApiPage'
export { FormBuiltinPage } from './FormBuiltinPage'
export {
ListInsideClustersAndNsPage,
ListInsideApiPage,
ListInsideCrdByApiGroupPage,
ListInsideApiByApiGroupPage,
} from './Insides'
/* tables */
export {
// TableCrdPage,
TableApiPage,
TableBuiltinPage,
} from './Tables'
/* forms */
export {
// FormCrdPage,
FormApiPage,
FormBuiltinPage,
} from './Forms'
/* factory */
export { FactoryPage } from './FactoryPage'
export { FactoryAdminPage } from './FactoryAdminPage'
/* search */
export { SearchPage } from './SearchPage'
/* list-then-watch */
export { ListThenWatchPage } from './ListThenWatchPage'

View File

@@ -0,0 +1,88 @@
// AppShell.tsx
import React, { FC, useState, useMemo } from 'react'
import { Outlet, useParams } from 'react-router-dom'
import { BaseTemplate } from 'templates'
import { ManageableSidebar, NavigationContainer, ManageableBreadcrumbs, BackLink } from 'components'
import { getSidebarIdPrefix } from 'utils/getSidebarIdPrefix'
import { getBreadcrumbsIdPrefix } from 'utils/getBreadcrumbsIdPrefix'
export type TChromeCtx = {
setCurrentTags: (tags?: string[]) => void
setSidebarSuffix: (suffix?: string) => void
setBreadcrumbsSuffix: (suffix?: string) => void
setBacklinkTo: (backlinkTo?: string) => void
setBacklinkTitle: (backlinkTitle?: string) => void
}
export const AppShell: FC<{ inside?: boolean }> = ({ inside }) => {
const { namespace, syntheticProject } = useParams()
const possibleProject = syntheticProject && namespace ? syntheticProject : namespace
const possibleInstance = syntheticProject && namespace ? namespace : undefined
const [currentTags, setCurrentTagsState] = useState<string[] | undefined>()
const [sidebarSuffix, setSidebarSuffix] = useState<string | undefined>()
const [breadcrumbsSuffix, setBreadcrumbsSuffix] = useState<string | undefined>()
const [backlinkTo, setBacklinkTo] = useState<string | undefined>()
const [backlinkTitle, setBacklinkTitle] = useState<string | undefined>()
// Commit tags only on actual content change to avoid sidebar refetch
const setCurrentTags = React.useCallback((next?: string[]) => {
setCurrentTagsState(prev => {
// eslint-disable-next-line one-var
const a = prev ?? [],
b = next ?? []
if (a.length === b.length && a.every((v, i) => v === b[i])) return prev
return next
})
}, [])
const sidebarId = useMemo(
() =>
`${getSidebarIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}${
sidebarSuffix ?? 'app-shell'
}`,
[syntheticProject, namespace, inside, sidebarSuffix],
)
const breadcrumbsId = useMemo(
() =>
`${getBreadcrumbsIdPrefix({ instance: !!syntheticProject, project: !!namespace, inside })}${
breadcrumbsSuffix ?? 'app-shell'
}`,
[syntheticProject, namespace, inside, breadcrumbsSuffix],
)
const sidebarEl = React.useMemo(
() => (
<ManageableSidebar
instanceName={possibleInstance}
projectName={possibleProject}
idToCompare={sidebarId}
currentTags={currentTags}
/>
),
[possibleInstance, possibleProject, sidebarId, currentTags],
)
const ctx = useMemo<TChromeCtx>(
() => ({
setCurrentTags,
setSidebarSuffix,
setBreadcrumbsSuffix,
setBacklinkTo,
setBacklinkTitle,
}),
[setCurrentTags],
)
return (
<BaseTemplate inside={inside} sidebar={sidebarEl}>
<NavigationContainer>
<ManageableBreadcrumbs idToCompare={breadcrumbsId} inside={inside} />
{backlinkTo && backlinkTitle && <BackLink to={backlinkTo} title={backlinkTitle} />}
</NavigationContainer>
<Outlet context={ctx} />
</BaseTemplate>
)
}

View File

@@ -0,0 +1,2 @@
export { AppShell } from './AppShell'
export type { TChromeCtx } from './AppShell'

View File

@@ -1 +1,4 @@
export { BaseTemplate } from './BaseTemplate'
export { MainLayout } from './MainLayout'
export { AppShell } from './AppShell'
export type { TChromeCtx } from './AppShell'