modify nav..
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-13 16:55:40 +09:00
parent 09480540ec
commit be6509a4f6
8 changed files with 184 additions and 60 deletions
-24
View File
@@ -60,10 +60,6 @@ import {
AgentInstallStatusPage as AgentInstallStatusRestoredPage,
AgentRegistrationPage as AgentRegistrationRestoredPage,
AgentSshKeysPage as AgentSshKeysRestoredPage,
AgentWorkspacePage as AgentWorkspaceRestoredPage,
GatewayAgentsPage as GatewayAgentsRestoredPage,
GatewayDevicesPage as GatewayDevicesRestoredPage,
GatewayStatusPage as GatewayStatusRestoredPage,
TailscaleAclPage as TailscaleAclRestoredPage,
TailscaleRoutesPage as TailscaleRoutesRestoredPage,
TailscaleStatusPage as TailscaleStatusRestoredPage
@@ -90,9 +86,6 @@ type ViewKey =
| 'appReleases'
| 'appDeployments'
| 'appMigrations'
| 'gatewayStatus'
| 'gatewayAgents'
| 'gatewayDevices'
| 'tailscaleStatus'
| 'tailscaleRoutes'
| 'tailscaleAcl'
@@ -104,7 +97,6 @@ type ViewKey =
| 'agentRegistration'
| 'agentInstallStatus'
| 'agentSshKeys'
| 'agentWorkspace'
| 'agentCommands'
| 'runtimeConfig'
| 'runtimeLogs'
@@ -129,9 +121,6 @@ const viewByLabel: Record<string, ViewKey> = {
'실행 설정': 'runtimeConfig',
'배포 관리': 'appDeployments',
마이그레이션: 'appMigrations',
'연결 상태': 'gatewayStatus',
'Agent 관리': 'gatewayAgents',
'장비 동기화': 'gatewayDevices',
'노드 현황': 'tailscaleStatus',
라우트: 'tailscaleRoutes',
ACL: 'tailscaleAcl',
@@ -143,7 +132,6 @@ const viewByLabel: Record<string, ViewKey> = {
'장비 등록': 'agentRegistration',
'설치 상태': 'agentInstallStatus',
'SSH 키 배포': 'agentSshKeys',
'Workspace 설정': 'agentWorkspace',
'원격 명령': 'agentCommands',
'로그/이벤트': 'runtimeLogs',
'장비 제어': 'runtimeDeviceControl',
@@ -174,9 +162,6 @@ const pathByView: Record<ViewKey, string> = {
appReleases: '/applications/releases',
appDeployments: '/applications/deployments',
appMigrations: '/applications/migrations',
gatewayStatus: '/gateway/status',
gatewayAgents: '/gateway/agents',
gatewayDevices: '/gateway/devices',
tailscaleStatus: '/tailscale/status',
tailscaleRoutes: '/tailscale/routes',
tailscaleAcl: '/tailscale/acl',
@@ -188,7 +173,6 @@ const pathByView: Record<ViewKey, string> = {
agentRegistration: '/agents/registration',
agentInstallStatus: '/agents/install-status',
agentSshKeys: '/agents/ssh-keys',
agentWorkspace: '/agents/workspace',
agentCommands: '/agents/commands',
runtimeConfig: '/applications/runtime-settings',
runtimeLogs: '/runtime/logs',
@@ -2325,12 +2309,6 @@ function App() {
return <DeploymentManagementPage />;
case 'appMigrations':
return <MigrationManagementPage />;
case 'gatewayStatus':
return <GatewayStatusRestoredPage />;
case 'gatewayAgents':
return <GatewayAgentsRestoredPage />;
case 'gatewayDevices':
return <GatewayDevicesRestoredPage />;
case 'tailscaleStatus':
return <TailscaleStatusRestoredPage />;
case 'tailscaleRoutes':
@@ -2353,8 +2331,6 @@ function App() {
return <AgentInstallStatusRestoredPage />;
case 'agentSshKeys':
return <AgentSshKeysRestoredPage />;
case 'agentWorkspace':
return <AgentWorkspaceRestoredPage />;
case 'agentCommands':
return <AgentCommandsRestoredPage />;
case 'runtimeConfig':
-10
View File
@@ -75,19 +75,9 @@ export const navGroups = [
{ label: '장비 제어' },
{ label: '설치 상태' },
{ label: 'SSH 키 배포' },
{ label: 'Workspace 설정' },
{ label: '원격 명령' }
]
},
{
label: '플랫폼 게이트웨이',
icon: ServerCog,
children: [
{ label: '연결 상태' },
{ label: 'Agent 관리' },
{ label: '장비 동기화' }
]
},
{
label: '인프라',
icon: HardDrive,
-11
View File
@@ -7,7 +7,6 @@ import {
Megaphone,
Network,
Rocket,
ServerCog,
ShieldCheck,
Users
} from 'lucide-react';
@@ -80,19 +79,9 @@ export const navGroups: NavGroup[] = [
{ label: '장비 제어', path: '/runtime/devices' },
{ label: '설치 상태', path: '/agents/install-status' },
{ label: 'SSH 키 배포', path: '/agents/ssh-keys' },
{ label: 'Workspace 설정', path: '/agents/workspace' },
{ label: '원격 명령', path: '/agents/commands' }
]
},
{
label: '플랫폼 게이트웨이',
icon: ServerCog,
children: [
{ label: '연결 상태', path: '/gateway/status' },
{ label: 'Agent 관리', path: '/gateway/agents' },
{ label: '장비 동기화', path: '/gateway/devices' }
]
},
{
label: '인프라',
icon: HardDrive,
-9
View File
@@ -879,15 +879,6 @@ export function AgentSshKeysPage() {
);
}
export function AgentWorkspacePage() {
return (
<>
<PageTitle eyebrow="Agent" title="Workspace 설정" description="장비별 작업 디렉터리와 동기화 정책을 관리합니다." />
<AgentPlaceholder title="Workspace 설정" description="계정별 workspace 경로와 동기화 상태입니다." />
</>
);
}
export function AgentCommandsPage() {
const columns = useMemo<Array<DataTableColumn<(typeof runtimeCommands)[number]>>>(
() => [
@@ -77,6 +77,11 @@ export function DeploymentManagementPage() {
const [releases, setReleases] = useState<AppRelease[]>([]);
const [releaseId, setReleaseId] = useState('');
const [namespace, setNamespace] = useState('');
const [replicas, setReplicas] = useState('1');
const [cpuRequest, setCpuRequest] = useState('100m');
const [cpuLimit, setCpuLimit] = useState('500m');
const [memoryRequest, setMemoryRequest] = useState('128Mi');
const [memoryLimit, setMemoryLimit] = useState('256Mi');
const [updateLauncherAppVersion, setUpdateLauncherAppVersion] = useState(true);
const [deployModalOpen, setDeployModalOpen] = useState(false);
const [selectedDeployment, setSelectedDeployment] = useState<DeploymentJob | null>(null);
@@ -158,6 +163,12 @@ export function DeploymentManagementPage() {
setReleaseId(release.id);
setNamespace(release.app.namespace || '');
}
if (!release && !releaseId && !releases[0]?.app?.namespace) setNamespace('');
setReplicas('1');
setCpuRequest('100m');
setCpuLimit('500m');
setMemoryRequest('128Mi');
setMemoryLimit('256Mi');
setUpdateLauncherAppVersion(true);
setDeployModalOpen(true);
};
@@ -165,8 +176,22 @@ export function DeploymentManagementPage() {
const deploy = async () => {
if (deploying) return;
setDeploying(true);
const requestedReplicas = Number.parseInt(replicas, 10);
const resources = {
...(Number.isFinite(requestedReplicas) && requestedReplicas > 0 ? { replicas: requestedReplicas } : {}),
...(cpuRequest.trim() ? { cpuRequest: cpuRequest.trim() } : {}),
...(cpuLimit.trim() ? { cpuLimit: cpuLimit.trim() } : {}),
...(memoryRequest.trim() ? { memoryRequest: memoryRequest.trim() } : {}),
...(memoryLimit.trim() ? { memoryLimit: memoryLimit.trim() } : {})
};
try {
const result = await createDeployment({ releaseId, environment: 'prod', namespace, updateLauncherAppVersion });
const result = await createDeployment({
releaseId,
environment: 'prod',
namespace,
updateLauncherAppVersion,
resources: Object.keys(resources).length ? resources : undefined
});
toast({ type: 'success', message: '배포 작업이 실행되었습니다.' });
setSelectedDeployment(result.deploymentJob);
setDeployModalOpen(false);
@@ -357,6 +382,60 @@ export function DeploymentManagementPage() {
{deployableReleases.map((release) => <option key={release.id} value={release.id}>{release.app.name} {release.version}</option>)}
</select>
<input className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none" value={namespace} disabled={deploying} onChange={(event) => setNamespace(event.target.value)} placeholder="stdo-lms" />
<div className="grid gap-3 md:grid-cols-2">
<label className="block">
<span className="text-xs font-medium text-muted-foreground">Replicas</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={replicas}
disabled={deploying}
onChange={(event) => setReplicas(event.target.value)}
placeholder="1"
/>
</label>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">CPU 요청</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={cpuRequest}
disabled={deploying}
onChange={(event) => setCpuRequest(event.target.value)}
placeholder="예: 100m"
/>
</label>
</div>
<div className="grid gap-3 md:grid-cols-2">
<label className="block">
<span className="text-xs font-medium text-muted-foreground">CPU 상한</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={cpuLimit}
disabled={deploying}
onChange={(event) => setCpuLimit(event.target.value)}
placeholder="예: 500m"
/>
</label>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">메모리 요청</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={memoryRequest}
disabled={deploying}
onChange={(event) => setMemoryRequest(event.target.value)}
placeholder="예: 256Mi"
/>
</label>
</div>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">메모리 상한</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={memoryLimit}
disabled={deploying}
onChange={(event) => setMemoryLimit(event.target.value)}
placeholder="예: 512Mi"
/>
</label>
<label className="flex items-center gap-2 rounded-md border border-border bg-muted/20 px-3 py-2 text-sm text-foreground">
<input
type="checkbox"
+15 -1
View File
@@ -12,6 +12,14 @@ import type {
} from '../../types/application';
import type { ApiAppComponent } from '../../types/runtime';
export type DeploymentResourceLimits = {
replicas?: number;
cpuRequest?: string;
cpuLimit?: string;
memoryRequest?: string;
memoryLimit?: string;
};
export type GiteaCommit = {
sha: string;
commit?: {
@@ -230,7 +238,13 @@ export function listDeploymentsPage(options: { appKey?: string; page: number; li
return apiRequest<{ deployments: DeploymentJob[]; total: number; page: number; limit: number; deployedReleaseIds?: string[] }>(`/apps/deployments?${params.toString()}`);
}
export function createDeployment(input: { releaseId: string; environment: string; namespace: string; updateLauncherAppVersion?: boolean }) {
export function createDeployment(input: {
releaseId: string;
environment: string;
namespace: string;
updateLauncherAppVersion?: boolean;
resources?: DeploymentResourceLimits;
}) {
return apiRequest<{ deploymentJob: DeploymentJob }>('/apps/deployments', {
method: 'POST',
body: JSON.stringify(input)
+79 -2
View File
@@ -36,6 +36,11 @@ export function LauncherVersionsPage() {
const [deployOpen, setDeployOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [deploying, setDeploying] = useState(false);
const [deployReplicas, setDeployReplicas] = useState('1');
const [deployCpuRequest, setDeployCpuRequest] = useState('100m');
const [deployCpuLimit, setDeployCpuLimit] = useState('500m');
const [deployMemoryRequest, setDeployMemoryRequest] = useState('128Mi');
const [deployMemoryLimit, setDeployMemoryLimit] = useState('256Mi');
const [releaseForm, setReleaseForm] = useState({
buildJobId: '',
version: '',
@@ -128,7 +133,16 @@ export function LauncherVersionsPage() {
const deployBackend = () => {
if (!latestBackendBuild?.downloadUrl) return;
setDeploying(true);
deployLauncherBackend(latestBackendBuild.downloadUrl)
const requestedReplicas = Number.parseInt(deployReplicas, 10);
const resources = {
...(Number.isFinite(requestedReplicas) && requestedReplicas > 0 ? { replicas: requestedReplicas } : {}),
...(deployCpuRequest.trim() ? { cpuRequest: deployCpuRequest.trim() } : {}),
...(deployCpuLimit.trim() ? { cpuLimit: deployCpuLimit.trim() } : {}),
...(deployMemoryRequest.trim() ? { memoryRequest: deployMemoryRequest.trim() } : {}),
...(deployMemoryLimit.trim() ? { memoryLimit: deployMemoryLimit.trim() } : {})
};
deployLauncherBackend(latestBackendBuild.downloadUrl, Object.keys(resources).length ? resources : undefined)
.then((result) => {
setDeployResult(result);
toast({ type: 'success', message: '런처 백엔드를 배포했습니다.' });
@@ -141,6 +155,15 @@ export function LauncherVersionsPage() {
.finally(() => setDeploying(false));
};
const openBackendDeploy = () => {
setDeployReplicas('1');
setDeployCpuRequest('100m');
setDeployCpuLimit('500m');
setDeployMemoryRequest('128Mi');
setDeployMemoryLimit('256Mi');
setDeployOpen(true);
};
return (
<>
<PageTitle
@@ -159,7 +182,7 @@ export function LauncherVersionsPage() {
릴리즈 등록
</Button>
) : (
<Button className="h-8 px-3 text-[13px]" onClick={() => setDeployOpen(true)} disabled={!latestBackendBuild?.downloadUrl}>
<Button className="h-8 px-3 text-[13px]" onClick={openBackendDeploy} disabled={!latestBackendBuild?.downloadUrl}>
<Play className="size-4" />
백엔드 배포
</Button>
@@ -345,6 +368,60 @@ export function LauncherVersionsPage() {
<p className="font-medium">최신 성공 빌드 이미지로 런처 백엔드를 배포합니다.</p>
<p className="mt-1 text-xs leading-5 text-muted-foreground">Deployment 이미지가 교체되고 rollout 완료까지 대기합니다.</p>
</div>
<div className="grid gap-3 md:grid-cols-2">
<label className="block">
<span className="text-xs font-medium text-muted-foreground">Replicas</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={deployReplicas}
disabled={deploying}
onChange={(event) => setDeployReplicas(event.target.value)}
placeholder="1"
/>
</label>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">CPU 요청</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={deployCpuRequest}
disabled={deploying}
onChange={(event) => setDeployCpuRequest(event.target.value)}
placeholder="예: 100m"
/>
</label>
</div>
<div className="grid gap-3 md:grid-cols-2">
<label className="block">
<span className="text-xs font-medium text-muted-foreground">CPU 상한</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={deployCpuLimit}
disabled={deploying}
onChange={(event) => setDeployCpuLimit(event.target.value)}
placeholder="예: 500m"
/>
</label>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">메모리 요청</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={deployMemoryRequest}
disabled={deploying}
onChange={(event) => setDeployMemoryRequest(event.target.value)}
placeholder="예: 256Mi"
/>
</label>
</div>
<label className="block">
<span className="text-xs font-medium text-muted-foreground">메모리 상한</span>
<input
className="mt-2 h-9 w-full rounded-md border border-input bg-background px-3 text-sm outline-none"
value={deployMemoryLimit}
disabled={deploying}
onChange={(event) => setDeployMemoryLimit(event.target.value)}
placeholder="예: 512Mi"
/>
</label>
<div className="rounded-md border border-border bg-background p-4">
<p className="text-xs text-muted-foreground">배포 이미지</p>
<p className="mt-2 break-all font-mono text-xs font-medium">{latestBackendBuild?.downloadUrl || '-'}</p>
+10 -2
View File
@@ -63,6 +63,14 @@ export type LauncherBackendDeployResult = {
logs: string;
};
export type DeploymentResourceLimits = {
replicas?: number;
cpuRequest?: string;
cpuLimit?: string;
memoryRequest?: string;
memoryLimit?: string;
};
export type LauncherBuildEnvVariable = {
key: string;
value: string;
@@ -281,10 +289,10 @@ export function getLauncherBackendDeployment() {
return apiRequest<LauncherBackendDeployment>('/launcher/backend-deployment');
}
export function deployLauncherBackend(image: string) {
export function deployLauncherBackend(image: string, resources?: DeploymentResourceLimits) {
return apiRequest<LauncherBackendDeployResult>('/launcher/backend-deployment', {
method: 'POST',
body: JSON.stringify({ image })
body: JSON.stringify({ image, resources: resources && Object.keys(resources).length ? resources : undefined })
});
}