improve app registration and component service ports
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -15,6 +15,7 @@ type ComponentPortForm = {
|
||||
name: string;
|
||||
port: string;
|
||||
targetPort: string;
|
||||
nodePort: string;
|
||||
protocol: string;
|
||||
};
|
||||
|
||||
@@ -29,6 +30,7 @@ const emptyComponentPort = (): ComponentPortForm => ({
|
||||
name: '',
|
||||
port: '',
|
||||
targetPort: '',
|
||||
nodePort: '',
|
||||
protocol: 'TCP'
|
||||
});
|
||||
|
||||
@@ -252,6 +254,7 @@ function componentPortForms(component: ApiAppComponent): ComponentPortForm[] {
|
||||
name: port.name ?? '',
|
||||
port: port.port ? String(port.port) : '',
|
||||
targetPort: port.targetPort ? String(port.targetPort) : '',
|
||||
nodePort: port.nodePort ? String(port.nodePort) : '',
|
||||
protocol: port.protocol ?? 'TCP'
|
||||
}));
|
||||
}
|
||||
@@ -261,6 +264,7 @@ function componentPortForms(component: ApiAppComponent): ComponentPortForm[] {
|
||||
name: 'http',
|
||||
port: String(component.port),
|
||||
targetPort: String(component.targetPort ?? component.port),
|
||||
nodePort: '',
|
||||
protocol: 'TCP'
|
||||
}];
|
||||
}
|
||||
@@ -271,7 +275,20 @@ function componentPortForms(component: ApiAppComponent): ComponentPortForm[] {
|
||||
function formatComponentPorts(component: ApiAppComponent) {
|
||||
const ports = componentPortForms(component).filter((port) => port.port);
|
||||
if (ports.length === 0) return '-';
|
||||
return ports.map((port) => `${port.name || 'port'} ${port.port}:${port.targetPort || port.port}`).join(', ');
|
||||
return ports.map((port) => {
|
||||
const nodePort = port.nodePort ? ` / node ${port.nodePort}` : '';
|
||||
return `${port.name || 'port'} ${port.port}:${port.targetPort || port.port}${nodePort}`;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
function appKeyFromName(name: string) {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
}
|
||||
|
||||
export function AppManagementPage() {
|
||||
@@ -508,6 +525,13 @@ export function AppManagementPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const changeNewAppName = (name: string) => {
|
||||
const appKey = appKeyFromName(name);
|
||||
setNewAppName(name);
|
||||
setNewAppKey(appKey);
|
||||
setNewAppNamespace(appKey ? `stdo-${appKey}` : '');
|
||||
};
|
||||
|
||||
const createApp = () => {
|
||||
if (!newAppKey.trim() || !newAppName.trim()) return;
|
||||
|
||||
@@ -603,6 +627,7 @@ export function AppManagementPage() {
|
||||
name: item.name.trim() || undefined,
|
||||
port: item.port ? Number(item.port) : undefined,
|
||||
targetPort: item.targetPort ? Number(item.targetPort) : item.port ? Number(item.port) : undefined,
|
||||
nodePort: componentServiceType === 'NodePort' && item.nodePort ? Number(item.nodePort) : undefined,
|
||||
protocol: item.protocol || 'TCP'
|
||||
}))
|
||||
.filter((item) => item.port);
|
||||
@@ -981,7 +1006,7 @@ export function AppManagementPage() {
|
||||
title="앱 구성요소"
|
||||
description="앱을 구성하는 실행 요소와 관련 리소스를 관리합니다."
|
||||
action={
|
||||
<Button variant="outline" className="h-8 px-3 text-[13px]" disabled={!selectedApp} onClick={() => setIsComponentOpen(true)}>
|
||||
<Button className="h-8 px-3 text-[13px]" disabled={!selectedApp} onClick={() => setIsComponentOpen(true)}>
|
||||
<Plus className="size-4" />
|
||||
추가
|
||||
</Button>
|
||||
@@ -1170,20 +1195,20 @@ export function AppManagementPage() {
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/55 px-4 backdrop-blur-[2px]">
|
||||
<div className="w-full max-w-lg rounded-lg border border-border bg-card shadow-xl">
|
||||
<div className="border-b border-border px-5 py-3">
|
||||
<h2 className="text-sm font-semibold">앱 그룹 등록</h2>
|
||||
<h2 className="text-sm font-semibold">앱 등록</h2>
|
||||
</div>
|
||||
<div className="space-y-4 px-5 py-5">
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">앱 Key</span>
|
||||
<input value={newAppKey} onChange={(event) => setNewAppKey(event.target.value)} className="mt-2 h-9 w-full rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary" />
|
||||
<span className="text-xs font-medium text-muted-foreground">앱 이름 <b className="text-foreground">*</b></span>
|
||||
<input value={newAppName} onChange={(event) => changeNewAppName(event.target.value)} className="mt-2 h-9 w-full rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary" />
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">앱 이름</span>
|
||||
<input value={newAppName} onChange={(event) => setNewAppName(event.target.value)} className="mt-2 h-9 w-full rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary" />
|
||||
<span className="text-xs font-medium text-muted-foreground">앱 Key</span>
|
||||
<input value={newAppKey} readOnly className="mt-2 h-9 w-full rounded-md border border-border bg-muted/40 px-3 font-mono text-sm text-muted-foreground outline-none" />
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">네임스페이스</span>
|
||||
<input value={newAppNamespace} onChange={(event) => setNewAppNamespace(event.target.value)} className="mt-2 h-9 w-full rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary" />
|
||||
<input value={newAppNamespace} readOnly className="mt-2 h-9 w-full rounded-md border border-border bg-muted/40 px-3 font-mono text-sm text-muted-foreground outline-none" />
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">설명</span>
|
||||
@@ -1215,11 +1240,11 @@ export function AppManagementPage() {
|
||||
/>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">구성요소명</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">구성요소명 <b className="text-foreground">*</b></span>
|
||||
<input value={componentName} onChange={(event) => setComponentName(event.target.value)} className="mt-2 h-9 w-full rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary" />
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">유형</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">유형 <b className="text-foreground">*</b></span>
|
||||
<select
|
||||
value={componentType}
|
||||
onChange={(event) => {
|
||||
@@ -1246,7 +1271,7 @@ export function AppManagementPage() {
|
||||
</label>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">이미지 소스</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">이미지 소스 <b className="text-foreground">*</b></span>
|
||||
<select
|
||||
value={componentImageSource}
|
||||
onChange={(event) => {
|
||||
@@ -1270,7 +1295,7 @@ export function AppManagementPage() {
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">워크로드</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">워크로드 <b className="text-foreground">*</b></span>
|
||||
<select
|
||||
value={componentWorkloadType}
|
||||
onChange={(event) => {
|
||||
@@ -1311,7 +1336,7 @@ export function AppManagementPage() {
|
||||
</>
|
||||
) : (
|
||||
<label className="block">
|
||||
<span className="text-xs font-medium text-muted-foreground">이미지명</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">이미지명 <b className="text-foreground">*</b></span>
|
||||
<input
|
||||
value={componentExternalImage}
|
||||
onChange={(event) => setComponentExternalImage(event.target.value)}
|
||||
@@ -1343,7 +1368,15 @@ export function AppManagementPage() {
|
||||
</div>
|
||||
<div className="mt-3 space-y-2">
|
||||
{componentPorts.map((item, index) => (
|
||||
<div key={index} className="grid min-w-0 gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,0.85fr)_minmax(0,0.85fr)_minmax(0,0.7fr)_2.5rem]">
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
'grid min-w-0 gap-2',
|
||||
componentServiceType === 'NodePort'
|
||||
? 'sm:grid-cols-[minmax(0,1fr)_minmax(0,0.8fr)_minmax(0,0.8fr)_minmax(0,0.8fr)_minmax(0,0.65fr)_2.5rem]'
|
||||
: 'sm:grid-cols-[minmax(0,1fr)_minmax(0,0.85fr)_minmax(0,0.85fr)_minmax(0,0.7fr)_2.5rem]'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
value={item.name}
|
||||
onChange={(event) => setComponentPorts((prev) => prev.map((port, portIndex) => portIndex === index ? { ...port, name: event.target.value } : port))}
|
||||
@@ -1366,6 +1399,17 @@ export function AppManagementPage() {
|
||||
className="h-9 min-w-0 rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary"
|
||||
placeholder="컨테이너 포트"
|
||||
/>
|
||||
{componentServiceType === 'NodePort' ? (
|
||||
<input
|
||||
type="number"
|
||||
min="30000"
|
||||
max="32767"
|
||||
value={item.nodePort}
|
||||
onChange={(event) => setComponentPorts((prev) => prev.map((port, portIndex) => portIndex === index ? { ...port, nodePort: event.target.value } : port))}
|
||||
className="h-9 min-w-0 rounded-md border border-border bg-background px-3 text-sm outline-none focus:border-primary"
|
||||
placeholder="노드 포트"
|
||||
/>
|
||||
) : null}
|
||||
<select
|
||||
value={item.protocol}
|
||||
onChange={(event) => setComponentPorts((prev) => prev.map((port, portIndex) => portIndex === index ? { ...port, protocol: event.target.value } : port))}
|
||||
|
||||
Reference in New Issue
Block a user