This commit is contained in:
@@ -8,10 +8,12 @@ import type { GiteaRepo } from '../../types/application';
|
|||||||
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
import { LoadingModal, useMinimumLoading } from '../operations/operation-utils';
|
||||||
import {
|
import {
|
||||||
getLauncherSource,
|
getLauncherSource,
|
||||||
|
listGiteaCommits,
|
||||||
listLauncherVersions,
|
listLauncherVersions,
|
||||||
listPlatformRepos,
|
listPlatformRepos,
|
||||||
listStudioRepos,
|
listStudioRepos,
|
||||||
updateLauncherSource,
|
updateLauncherSource,
|
||||||
|
type GiteaCommit,
|
||||||
type LauncherVersion
|
type LauncherVersion
|
||||||
} from './launcher-api';
|
} from './launcher-api';
|
||||||
|
|
||||||
@@ -52,6 +54,13 @@ function normalizeRepository(repository?: string | null, fallbackOrg = giteaAppO
|
|||||||
return value.includes('/') ? value : `${fallbackOrg}/${value}`;
|
return value.includes('/') ? value : `${fallbackOrg}/${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitRepository(repository?: string | null, fallbackOrg = giteaAppOrg) {
|
||||||
|
const normalizedRepository = normalizeRepository(repository, fallbackOrg);
|
||||||
|
const [owner, ...repoParts] = normalizedRepository.split('/');
|
||||||
|
const repo = repoParts.join('/');
|
||||||
|
return owner && repo ? { owner, repo } : null;
|
||||||
|
}
|
||||||
|
|
||||||
function httpCloneUrl(repository?: string | null, fallbackOrg?: string) {
|
function httpCloneUrl(repository?: string | null, fallbackOrg?: string) {
|
||||||
const normalizedRepository = normalizeRepository(repository, fallbackOrg);
|
const normalizedRepository = normalizeRepository(repository, fallbackOrg);
|
||||||
return normalizedRepository ? `${giteaBaseUrl.replace(/\/$/, '')}/${normalizedRepository}.git` : '-';
|
return normalizedRepository ? `${giteaBaseUrl.replace(/\/$/, '')}/${normalizedRepository}.git` : '-';
|
||||||
@@ -72,6 +81,45 @@ function emptyForm(): SourceForm {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function commitAuthor(commit: GiteaCommit) {
|
||||||
|
return commit.author?.login
|
||||||
|
|| commit.author?.username
|
||||||
|
|| commit.author?.full_name
|
||||||
|
|| commit.author?.name
|
||||||
|
|| commit.commit?.author?.name
|
||||||
|
|| commit.committer?.login
|
||||||
|
|| '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitMessage(commit: GiteaCommit) {
|
||||||
|
return (commit.commit?.message || '').split(/\r?\n/)[0]?.trim() || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitDate(commit: GiteaCommit) {
|
||||||
|
return commit.commit?.author?.date
|
||||||
|
|| commit.commit?.committer?.date
|
||||||
|
|| commit.created
|
||||||
|
|| '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function relativeTime(value?: string | null) {
|
||||||
|
if (!value) return '-';
|
||||||
|
const time = new Date(value).getTime();
|
||||||
|
if (Number.isNaN(time)) return '-';
|
||||||
|
|
||||||
|
const seconds = Math.max(1, Math.floor((Date.now() - time) / 1000));
|
||||||
|
if (seconds < 60) return '방금 전';
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
if (minutes < 60) return `${minutes}분 전`;
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return `${hours}시간 전`;
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
if (days === 1) return '어제';
|
||||||
|
if (days === 2) return '그저께';
|
||||||
|
if (days < 7) return `${days}일 전`;
|
||||||
|
return new Intl.DateTimeFormat('ko-KR', { month: '2-digit', day: '2-digit' }).format(new Date(value));
|
||||||
|
}
|
||||||
|
|
||||||
export function LauncherSourcePage() {
|
export function LauncherSourcePage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [activeTarget, setActiveTarget] = useState<SourceTarget>('launcher-client');
|
const [activeTarget, setActiveTarget] = useState<SourceTarget>('launcher-client');
|
||||||
@@ -81,6 +129,9 @@ export function LauncherSourcePage() {
|
|||||||
'launcher-client': emptyForm(),
|
'launcher-client': emptyForm(),
|
||||||
'launcher-backend': emptyForm()
|
'launcher-backend': emptyForm()
|
||||||
});
|
});
|
||||||
|
const [commits, setCommits] = useState<GiteaCommit[]>([]);
|
||||||
|
const [commitsLoading, setCommitsLoading] = useState(false);
|
||||||
|
const [commitsError, setCommitsError] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const loadingVisible = useMinimumLoading(loading);
|
const loadingVisible = useMinimumLoading(loading);
|
||||||
@@ -137,6 +188,36 @@ export function LauncherSourcePage() {
|
|||||||
refresh();
|
refresh();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const repository = splitRepository(activeForm.repository, activeTargetMeta.defaultOrg);
|
||||||
|
if (!repository) {
|
||||||
|
setCommits([]);
|
||||||
|
setCommitsError('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canceled = false;
|
||||||
|
setCommitsLoading(true);
|
||||||
|
setCommitsError('');
|
||||||
|
listGiteaCommits(repository.owner, repository.repo, activeForm.branch || 'develop', 5)
|
||||||
|
.then((nextCommits) => {
|
||||||
|
if (!canceled) setCommits(nextCommits);
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
if (!canceled) {
|
||||||
|
setCommits([]);
|
||||||
|
setCommitsError(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (!canceled) setCommitsLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
};
|
||||||
|
}, [activeForm.repository, activeForm.branch, activeTargetMeta.defaultOrg]);
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
if (!activeForm.repository.trim()) return;
|
if (!activeForm.repository.trim()) return;
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
@@ -295,6 +376,50 @@ export function LauncherSourcePage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Card className="mt-4 min-w-0 overflow-hidden">
|
||||||
|
<CardHeader
|
||||||
|
title="최근 커밋"
|
||||||
|
description={`${activeTargetMeta.title} 저장소의 ${activeForm.branch || 'develop'} 브랜치 기준 최근 5개 커밋입니다.`}
|
||||||
|
/>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-[760px] w-full text-left text-sm">
|
||||||
|
<thead className="border-y border-border bg-muted/30 text-xs text-muted-foreground">
|
||||||
|
<tr>
|
||||||
|
<th className="px-5 py-3 font-medium">작성자</th>
|
||||||
|
<th className="px-5 py-3 font-medium">SHA1</th>
|
||||||
|
<th className="px-5 py-3 font-medium">메시지</th>
|
||||||
|
<th className="px-5 py-3 font-medium">날짜</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{commitsLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className="px-5 py-10 text-center text-sm text-muted-foreground">커밋을 불러오는 중입니다.</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
{!commitsLoading && commitsError ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className="px-5 py-10 text-center text-sm text-muted-foreground">커밋 조회 실패: {commitsError}</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
{!commitsLoading && !commitsError && commits.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className="px-5 py-10 text-center text-sm text-muted-foreground">표시할 커밋이 없습니다.</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
{!commitsLoading && !commitsError ? commits.map((commit) => (
|
||||||
|
<tr key={commit.sha} className="border-b border-border last:border-0">
|
||||||
|
<td className="whitespace-nowrap px-5 py-3 font-medium">{commitAuthor(commit)}</td>
|
||||||
|
<td className="whitespace-nowrap px-5 py-3 font-mono text-xs text-muted-foreground">{commit.sha.slice(0, 10)}</td>
|
||||||
|
<td className="max-w-[360px] truncate px-5 py-3">{commitMessage(commit)}</td>
|
||||||
|
<td className="whitespace-nowrap px-5 py-3 text-muted-foreground">{relativeTime(commitDate(commit))}</td>
|
||||||
|
</tr>
|
||||||
|
)) : null}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<LoadingModal open={loadingVisible} />
|
<LoadingModal open={loadingVisible} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,6 +54,34 @@ export type LauncherSource = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GiteaCommit = {
|
||||||
|
sha: string;
|
||||||
|
commit?: {
|
||||||
|
message?: string;
|
||||||
|
author?: {
|
||||||
|
name?: string;
|
||||||
|
date?: string;
|
||||||
|
};
|
||||||
|
committer?: {
|
||||||
|
name?: string;
|
||||||
|
date?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
author?: {
|
||||||
|
login?: string;
|
||||||
|
username?: string;
|
||||||
|
full_name?: string;
|
||||||
|
name?: string;
|
||||||
|
} | null;
|
||||||
|
committer?: {
|
||||||
|
login?: string;
|
||||||
|
username?: string;
|
||||||
|
full_name?: string;
|
||||||
|
name?: string;
|
||||||
|
} | null;
|
||||||
|
created?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type LauncherAppLink = {
|
export type LauncherAppLink = {
|
||||||
id: string;
|
id: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
@@ -151,6 +179,16 @@ export function listStudioRepos(limit = 50) {
|
|||||||
.then((result) => result.repos);
|
.then((result) => result.repos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listGiteaCommits(owner: string, repo: string, branch = 'develop', limit = 5) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
sha: branch,
|
||||||
|
limit: String(limit)
|
||||||
|
});
|
||||||
|
return apiRequest<{ commits: GiteaCommit[] }>(
|
||||||
|
`/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?${params.toString()}`
|
||||||
|
).then((result) => result.commits);
|
||||||
|
}
|
||||||
|
|
||||||
export function listLauncherBuildJobs() {
|
export function listLauncherBuildJobs() {
|
||||||
return apiRequest<LauncherBuildJob[]>('/launcher/builds');
|
return apiRequest<LauncherBuildJob[]>('/launcher/builds');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user