improve launcher history and runtime config UX
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-07 09:51:24 +09:00
parent a48055bf65
commit 88d9c25b43
4 changed files with 41 additions and 19 deletions
+2 -2
View File
@@ -48,7 +48,7 @@ function jobTime(job: LauncherBuildJob) {
return Number.isNaN(value) ? 0 : value;
}
const launcherBuildJobPageSize = 5;
const launcherBuildJobPageSize = 3;
type BuildTarget = 'launcher-client' | 'launcher-backend';
type BuildRange = 'week' | 'month' | 'all';
@@ -387,7 +387,7 @@ export function LauncherBuildPage() {
</div>
}
/>
<div className="min-h-[638px] space-y-3 p-4">
<div className="min-h-[410px] space-y-3 p-4">
{jobs.length === 0 ? (
<div className="flex min-h-44 items-center justify-center text-center text-sm text-muted-foreground">
빌드 작업이 없습니다.
@@ -1,4 +1,4 @@
import { MoreHorizontal, Plus, SlidersHorizontal, Trash2 } from 'lucide-react';
import { ChevronLeft, ChevronRight, MoreHorizontal, Plus, SlidersHorizontal, Trash2 } from 'lucide-react';
import { useEffect, useMemo, useState, type FormEvent } from 'react';
import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui';
import { useToast } from '../../components/toast';
@@ -33,6 +33,8 @@ const emptyForm: FormState = {
isSecret: false
};
const settingsPageSize = 10;
function maskValue(setting: LauncherCommonSetting) {
if (!setting.isSecret) return setting.value;
if (!setting.value) return '-';
@@ -61,8 +63,11 @@ export function LauncherCommonSettingsPage() {
const [editing, setEditing] = useState<LauncherCommonSetting | null>(null);
const [form, setForm] = useState<FormState>(emptyForm);
const [saving, setSaving] = useState(false);
const [page, setPage] = useState(1);
const loadingVisible = useMinimumLoading(loading);
const totalPages = Math.max(1, Math.ceil(settings.length / settingsPageSize));
const pagedSettings = settings.slice((page - 1) * settingsPageSize, page * settingsPageSize);
const summary = useMemo(() => ({
total: settings.length,
endpoint: settings.filter((item) => item.type === 'endpoint').length,
@@ -84,6 +89,10 @@ export function LauncherCommonSettingsPage() {
refresh();
}, []);
useEffect(() => {
setPage((currentPage) => Math.min(currentPage, totalPages));
}, [totalPages]);
const openCreate = () => {
setEditing(null);
setForm(emptyForm);
@@ -192,7 +201,7 @@ export function LauncherCommonSettingsPage() {
<td colSpan={5} className="px-5 py-12 text-center text-sm text-muted-foreground">등록된 설정이 없습니다.</td>
</tr>
) : null}
{settings.map((setting) => (
{pagedSettings.map((setting) => (
<tr className="hover:bg-muted/20" key={setting.id}>
<td className="px-5 py-3">
<p className="inline-flex items-center gap-2 font-medium">
@@ -214,6 +223,31 @@ export function LauncherCommonSettingsPage() {
</tbody>
</table>
</div>
<div className="flex items-center justify-between gap-4 border-t border-border px-5 py-3 text-sm">
<span className="text-xs text-muted-foreground">
{loading ? '불러오는 중' : `총 ${settings.length}건 · ${page} / ${totalPages}`}
</span>
<div className="flex gap-2">
<Button
variant="outline"
className="h-8 px-3 text-[13px]"
disabled={page <= 1}
onClick={() => setPage((currentPage) => Math.max(1, currentPage - 1))}
>
<ChevronLeft className="size-4" />
이전
</Button>
<Button
variant="outline"
className="h-8 px-3 text-[13px]"
disabled={page >= totalPages}
onClick={() => setPage((currentPage) => Math.min(totalPages, currentPage + 1))}
>
다음
<ChevronRight className="size-4" />
</Button>
</div>
</div>
</Card>
<Modal
+3 -9
View File
@@ -1,4 +1,4 @@
import { Copy, ExternalLink, FileText, Link2, RotateCcw } from 'lucide-react';
import { Copy, FileText, Link2, RotateCcw } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { Badge, Button, Card, CardHeader, Modal } from '../../components/ui';
import { useToast } from '../../components/toast';
@@ -226,7 +226,7 @@ function DiffViewer({ diff }: { diff: string }) {
const files = parseDiffFiles(diff);
return (
<div className="h-[72vh] overflow-auto rounded-md border border-border bg-[#080b10] p-3 font-mono text-xs leading-5">
<div className="max-h-[56vh] overflow-auto rounded-md border border-border bg-[#080b10] p-3 font-mono text-xs leading-5">
<div className="space-y-4">
{files.map((file) => (
<section key={file.id} className="min-w-max overflow-hidden rounded-md border border-slate-700/80 bg-[#0b0f14]">
@@ -510,12 +510,6 @@ export function LauncherSourcePage() {
</Button>
</div>
</div>
{activeForm.repository ? (
<a className="inline-flex items-center gap-1.5 text-xs text-primary" href={`${giteaBaseUrl.replace(/\/$/, '')}/${normalizeRepository(activeForm.repository, activeTargetMeta.defaultOrg)}`} target="_blank" rel="noreferrer">
저장소 열기
<ExternalLink className="size-3.5" />
</a>
) : null}
</div>
</Card>
</div>
@@ -618,7 +612,7 @@ export function LauncherSourcePage() {
title="커밋 내역 상세"
onClose={() => setDiffTarget(null)}
className="max-w-[min(98vw,1800px)]"
bodyClassName="max-h-[84vh] px-4 py-4"
bodyClassName="max-h-[70vh] px-4 py-4"
footer={<Button variant="outline" onClick={() => setDiffTarget(null)}>닫기</Button>}
>
{diffTarget ? (
@@ -267,12 +267,6 @@ export function LauncherVersionsPage() {
<span>커밋 <b className="font-mono text-foreground">{latestBackendBuild?.commitSha || '-'}</b></span>
</div>
</div>
<div className="flex justify-end">
<Button onClick={() => setDeployOpen(true)} disabled={!latestBackendBuild?.downloadUrl || deploying}>
<Play className="size-4" />
백엔드 배포
</Button>
</div>
</div>
</Card>
<Card>