mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-27 18:19:50 +00:00
outlets
This commit is contained in:
86
src/App.tsx
86
src/App.tsx
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { AppComponentAdmin as FactoryAdminPage } from './AppComponentAdmin'
|
||||
@@ -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[]
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
87
src/pages/Forms/FormApiPage/FormApiPage.tsx
Normal file
87
src/pages/Forms/FormApiPage/FormApiPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
71
src/pages/Forms/FormBuiltinPage/FormBuiltinPage.tsx
Normal file
71
src/pages/Forms/FormBuiltinPage/FormBuiltinPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
87
src/pages/Forms/FormCrdPage/FormCrdPage.tsx
Normal file
87
src/pages/Forms/FormCrdPage/FormCrdPage.tsx
Normal 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
3
src/pages/Forms/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// export { FormCrdPage } from './FormCrdPage'
|
||||
export { FormApiPage } from './FormApiPage'
|
||||
export { FormBuiltinPage } from './FormBuiltinPage'
|
||||
4
src/pages/Insides/index.ts
Normal file
4
src/pages/Insides/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { ListInsideClustersAndNsPage } from './ListInsideClustersAndNsPage'
|
||||
export { ListInsideApiPage } from './ListInsideApiPage'
|
||||
export { ListInsideCrdByApiGroupPage } from './ListInsideCrdByApiGroupPage'
|
||||
export { ListInsideApiByApiGroupPage } from './ListInsideApiByApiGroupPage'
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { ListThenWatchPage } from './ListThenWatchPage'
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
103
src/pages/Tables/TableApiPage/TableApiPage.tsx
Normal file
103
src/pages/Tables/TableApiPage/TableApiPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
86
src/pages/Tables/TableBuiltinPage/TableBuiltinPage.tsx
Normal file
86
src/pages/Tables/TableBuiltinPage/TableBuiltinPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
84
src/pages/Tables/TableCrdPage/TableCrdPage.tsx
Normal file
84
src/pages/Tables/TableCrdPage/TableCrdPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
3
src/pages/Tables/index.ts
Normal file
3
src/pages/Tables/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// export { TableCrdPage } from './TableCrdPage'
|
||||
export { TableApiPage } from './TableApiPage'
|
||||
export { TableBuiltinPage } from './TableBuiltinPage'
|
||||
@@ -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'
|
||||
|
||||
88
src/templates/AppShell/AppShell.tsx
Normal file
88
src/templates/AppShell/AppShell.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
2
src/templates/AppShell/index.ts
Normal file
2
src/templates/AppShell/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { AppShell } from './AppShell'
|
||||
export type { TChromeCtx } from './AppShell'
|
||||
@@ -1 +1,4 @@
|
||||
export { BaseTemplate } from './BaseTemplate'
|
||||
export { MainLayout } from './MainLayout'
|
||||
export { AppShell } from './AppShell'
|
||||
export type { TChromeCtx } from './AppShell'
|
||||
|
||||
Reference in New Issue
Block a user