Skip to content

Commit ac3704d

Browse files
yomybabyagatha197
authored andcommitted
feat: Mockup UI for Deployment and Revisions
1 parent 7591268 commit ac3704d

File tree

15 files changed

+2666
-0
lines changed

15 files changed

+2666
-0
lines changed

react/src/App.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ const ChatPage = React.lazy(() => import('./pages/ChatPage'));
8282

8383
const AIAgentPage = React.lazy(() => import('./pages/AIAgentPage'));
8484

85+
// Deployment pages
86+
const DeploymentListPage = React.lazy(
87+
() => import('./pages/Deployments/DeploymentListPage'),
88+
);
89+
const DeploymentDetailPage = React.lazy(
90+
() => import('./pages/Deployments/DeploymentDetailPage'),
91+
);
92+
const RevisionCreatePage = React.lazy(
93+
() => import('./pages/Deployments/RevisionCreatePage'),
94+
);
95+
const RevisionDetailPage = React.lazy(
96+
() => import('./pages/Deployments/RevisionDetailPage'),
97+
);
98+
8599
interface CustomHandle {
86100
title?: string;
87101
labelKey?: string;
@@ -298,6 +312,69 @@ const router = createBrowserRouter([
298312
},
299313
],
300314
},
315+
{
316+
path: '/deployment',
317+
handle: { labelKey: 'webui.menu.Deployment' },
318+
children: [
319+
{
320+
path: '',
321+
Component: () => {
322+
const { t } = useTranslation();
323+
useSuspendedBackendaiClient();
324+
return (
325+
<BAIErrorBoundary>
326+
<Suspense
327+
fallback={
328+
<BAICard title={t('webui.menu.Deployment')} loading />
329+
}
330+
>
331+
<DeploymentListPage />
332+
</Suspense>
333+
</BAIErrorBoundary>
334+
);
335+
},
336+
},
337+
{
338+
path: '/deployment/:deploymentId',
339+
handle: { labelKey: 'deployment.DeploymentDetail' },
340+
element: (
341+
<BAIErrorBoundary>
342+
<Suspense fallback={<Skeleton active />}>
343+
<DeploymentDetailPage />
344+
</Suspense>
345+
</BAIErrorBoundary>
346+
),
347+
},
348+
{
349+
path: '/deployment/:deploymentId/revision/create',
350+
handle: { labelKey: 'revision.CreateRevision' },
351+
element: (
352+
<BAIErrorBoundary>
353+
<Suspense
354+
fallback={
355+
<BAIFlex direction="column" style={{ maxWidth: 700 }}>
356+
<Skeleton active />
357+
</BAIFlex>
358+
}
359+
>
360+
<RevisionCreatePage />
361+
</Suspense>
362+
</BAIErrorBoundary>
363+
),
364+
},
365+
{
366+
path: '/deployment/:deploymentId/revision/:revisionId',
367+
handle: { labelKey: 'revision.RevisionDetail' },
368+
element: (
369+
<BAIErrorBoundary>
370+
<Suspense fallback={<Skeleton active />}>
371+
<RevisionDetailPage />
372+
</Suspense>
373+
</BAIErrorBoundary>
374+
),
375+
},
376+
],
377+
},
301378
{
302379
path: '/service',
303380
handle: { labelKey: 'webui.menu.Serving' },
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { useWebUINavigate } from '../../hooks';
2+
import { Form, Input, Button, Modal } from 'antd';
3+
import { BAIFlex } from 'backend.ai-ui';
4+
import React, { useState } from 'react';
5+
import { useTranslation } from 'react-i18next';
6+
7+
interface DeploymentCreateFormValues {
8+
name: string;
9+
domain?: string;
10+
description?: string;
11+
}
12+
13+
interface DeploymentCreateModalProps {
14+
open: boolean;
15+
onClose: () => void;
16+
onSuccess?: () => void;
17+
}
18+
19+
const DeploymentCreateModal: React.FC<DeploymentCreateModalProps> = ({
20+
open,
21+
onClose,
22+
onSuccess,
23+
}) => {
24+
const { t } = useTranslation();
25+
const [form] = Form.useForm<DeploymentCreateFormValues>();
26+
const webuiNavigate = useWebUINavigate();
27+
const [isSubmitting, setIsSubmitting] = useState(false);
28+
// const [isCheckingDomain, setIsCheckingDomain] = useState(false);
29+
const [domainCheckStatus, setDomainCheckStatus] = useState<
30+
'success' | 'error' | undefined
31+
>();
32+
33+
const handleSubmit = async (values: DeploymentCreateFormValues) => {
34+
setIsSubmitting(true);
35+
try {
36+
// Mock API call - replace with actual implementation
37+
console.log('Creating deployment:', values);
38+
39+
// Simulate API delay
40+
await new Promise((resolve) => setTimeout(resolve, 1000));
41+
42+
// Reset form and close modal
43+
form.resetFields();
44+
onClose();
45+
46+
// Call success callback if provided
47+
if (onSuccess) {
48+
onSuccess();
49+
}
50+
51+
// Navigate to deployment detail page after creation
52+
webuiNavigate(`/deployment/mock-id`);
53+
} catch (error) {
54+
console.error('Failed to create deployment:', error);
55+
} finally {
56+
setIsSubmitting(false);
57+
}
58+
};
59+
60+
// const handleDomainCheck = async () => {
61+
// const domain = form.getFieldValue('domain');
62+
// if (!domain) {
63+
// return;
64+
// }
65+
66+
// setIsCheckingDomain(true);
67+
// setDomainCheckStatus(undefined);
68+
69+
// try {
70+
// // Mock API call - replace with actual domain check implementation
71+
// console.log('Checking domain:', domain);
72+
73+
// // Simulate API delay
74+
// await new Promise((resolve) => setTimeout(resolve, 1000));
75+
76+
// // Mock logic: domains starting with 'test' are considered duplicates
77+
// const isDuplicate = domain.toLowerCase().startsWith('test');
78+
79+
// if (isDuplicate) {
80+
// setDomainCheckStatus('error');
81+
// form.setFields([
82+
// {
83+
// name: 'domain',
84+
// errors: [t('deployment.DomainAlreadyExists')],
85+
// },
86+
// ]);
87+
// } else {
88+
// setDomainCheckStatus('success');
89+
// form.setFields([
90+
// {
91+
// name: 'domain',
92+
// errors: [],
93+
// },
94+
// ]);
95+
// }
96+
// } catch (error) {
97+
// console.error('Failed to check domain:', error);
98+
// setDomainCheckStatus('error');
99+
// } finally {
100+
// setIsCheckingDomain(false);
101+
// }
102+
// };
103+
104+
const handleCancel = () => {
105+
form.resetFields();
106+
setDomainCheckStatus(undefined);
107+
onClose();
108+
};
109+
110+
return (
111+
<Modal
112+
title={t('deployment.CreateDeployment')}
113+
open={open}
114+
onCancel={handleCancel}
115+
footer={null}
116+
width={600}
117+
destroyOnClose
118+
>
119+
<Form form={form} layout="vertical" onFinish={handleSubmit}>
120+
<Form.Item
121+
label={t('deployment.DeploymentName')}
122+
name="name"
123+
rules={[
124+
{
125+
required: true,
126+
message: t('deployment.DeploymentNameRequired'),
127+
},
128+
{
129+
min: 3,
130+
message: t('deployment.DeploymentNameMinLength'),
131+
},
132+
{
133+
max: 50,
134+
message: t('deployment.DeploymentNameMaxLength'),
135+
},
136+
{
137+
pattern: /^[a-zA-Z0-9-_]+$/,
138+
message: t('deployment.DeploymentNamePattern'),
139+
},
140+
]}
141+
>
142+
<Input placeholder={t('deployment.DeploymentNamePlaceholder')} />
143+
</Form.Item>
144+
145+
<Form.Item
146+
label={t('deployment.Domain')}
147+
name="domain"
148+
validateStatus={domainCheckStatus}
149+
>
150+
<Input
151+
placeholder={t('deployment.DomainHelp')}
152+
addonAfter={'.backend.ai'}
153+
onChange={() => {
154+
// Reset domain check status when user types
155+
if (domainCheckStatus) {
156+
setDomainCheckStatus(undefined);
157+
form.setFields([
158+
{
159+
name: 'domain',
160+
errors: [],
161+
},
162+
]);
163+
}
164+
}}
165+
/>
166+
</Form.Item>
167+
168+
<Form.Item label={t('deployment.Description')} name="description">
169+
<Input.TextArea
170+
placeholder={t('deployment.DescriptionPlaceholder')}
171+
rows={3}
172+
maxLength={500}
173+
showCount
174+
/>
175+
</Form.Item>
176+
177+
<Form.Item>
178+
<BAIFlex justify="end" gap="sm">
179+
<Button onClick={handleCancel}>{t('button.Cancel')}</Button>
180+
<Button type="primary" htmlType="submit" loading={isSubmitting}>
181+
{t('button.Create')}
182+
</Button>
183+
</BAIFlex>
184+
</Form.Item>
185+
</Form>
186+
</Modal>
187+
);
188+
};
189+
190+
export default DeploymentCreateModal;

0 commit comments

Comments
 (0)