@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user