ticker
This commit is contained in:
parent
63fa79b95e
commit
e54a29230d
|
|
@ -40,6 +40,7 @@ import { getApiBaseUrl } from "@/lib/api-base";
|
|||
import {
|
||||
getForgejoRepositories,
|
||||
deleteForgejoRepository,
|
||||
refreshRepositoryRecentIssues,
|
||||
syncRepository,
|
||||
validateRepository,
|
||||
type ForgejoRepository,
|
||||
|
|
@ -531,10 +532,10 @@ export default function ForgejoRepositoriesPage() {
|
|||
|
||||
const handleSync = async (repository: ForgejoRepository) => {
|
||||
try {
|
||||
const result = await syncRepository(repository.id);
|
||||
const result = await refreshRepositoryRecentIssues(repository.id);
|
||||
setNotice({
|
||||
tone: "success",
|
||||
message: `${repositoryName(repository)} synced: ${result.created} created, ${result.updated} updated, ${result.open} open, ${result.closed} closed.`,
|
||||
message: `${repositoryName(repository)} refreshed: ${result.created} created, ${result.updated} updated, ${result.open} open, ${result.closed} closed.`,
|
||||
});
|
||||
// Refetch to update last_sync_at
|
||||
const data = await getForgejoRepositories();
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ textarea::placeholder {
|
|||
animation: progress-shimmer 1.8s linear infinite;
|
||||
}
|
||||
.animate-ticker {
|
||||
animation: ticker-scroll 45s linear infinite;
|
||||
animation: ticker-scroll 70s linear infinite;
|
||||
}
|
||||
.ticker-fade-mask {
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0px, black 48px, black calc(100% - 48px), transparent 100%);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
Archive,
|
||||
CheckCircle2,
|
||||
CircleDot,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
GitBranch,
|
||||
GitCommitHorizontal,
|
||||
|
|
@ -51,6 +52,25 @@ const formatConnectionUrl = (value?: string | null) => {
|
|||
return value.replace(/^https?:\/\//i, "").replace(/\/$/, "");
|
||||
};
|
||||
|
||||
const buildRepositoryUrl = (repo: ForgejoRepository) => {
|
||||
const baseUrl = repo.connection?.base_url;
|
||||
if (!baseUrl) return null;
|
||||
const normalizedBaseUrl = /^https?:\/\//i.test(baseUrl)
|
||||
? baseUrl
|
||||
: `https://${baseUrl}`;
|
||||
try {
|
||||
const base = normalizedBaseUrl.endsWith("/")
|
||||
? normalizedBaseUrl
|
||||
: `${normalizedBaseUrl}/`;
|
||||
return new URL(
|
||||
`${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}`,
|
||||
base,
|
||||
).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const repositoryTone = (repo: ForgejoRepository) => {
|
||||
if (repo.last_sync_error) return "danger";
|
||||
if (!repo.active || repo.is_archived) return "muted";
|
||||
|
|
@ -300,14 +320,33 @@ const columns = (
|
|||
accessorKey: "connection",
|
||||
header: "Connection",
|
||||
cell: ({ row }) => {
|
||||
const connection = row.original.connection;
|
||||
const repo = row.original;
|
||||
const repositoryUrl = buildRepositoryUrl(repo);
|
||||
const label = formatConnectionUrl(repo.connection?.base_url);
|
||||
if (!repositoryUrl) {
|
||||
return (
|
||||
<div className="min-w-[180px]">
|
||||
<span className="mt-1 inline-flex max-w-[240px] items-center rounded-full border border-[color:var(--border)] bg-[color:var(--surface-muted)] px-2 py-0.5 font-mono text-[11px] text-muted">
|
||||
{formatConnectionUrl(connection?.base_url)}
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-w-[180px]">
|
||||
<a
|
||||
href={repositoryUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-1 inline-flex max-w-[240px] items-center gap-1 rounded-full border border-[color:var(--border)] bg-[color:var(--surface-muted)] px-2 py-0.5 font-mono text-[11px] text-muted transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
|
||||
title={`Open ${repo.owner}/${repo.repo}`}
|
||||
>
|
||||
<span className="truncate">{label}</span>
|
||||
<ExternalLink className="h-3 w-3 shrink-0" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -503,7 +542,7 @@ function ActionsCell({
|
|||
onClick={handleSync}
|
||||
disabled={isSyncLoading}
|
||||
className="h-7 w-7 rounded-lg p-0 text-muted hover:bg-[color:var(--surface-muted)] hover:text-strong"
|
||||
aria-label={`Sync issues for ${repository.display_name || `${repository.owner}/${repository.repo}`}`}
|
||||
aria-label={`Refresh recent issues for ${repository.display_name || `${repository.owner}/${repository.repo}`}`}
|
||||
>
|
||||
{isSyncLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
|
|
@ -514,7 +553,7 @@ function ActionsCell({
|
|||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Sync issues</TooltipContent>
|
||||
<TooltipContent>Refresh recent issues</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{onValidate && (
|
||||
|
|
|
|||
|
|
@ -258,6 +258,28 @@ export async function syncRepository(repositoryId: string): Promise<{
|
|||
});
|
||||
}
|
||||
|
||||
export async function refreshRepositoryRecentIssues(
|
||||
repositoryId: string,
|
||||
days = 7,
|
||||
): Promise<{
|
||||
created: number;
|
||||
updated: number;
|
||||
open: number;
|
||||
closed: number;
|
||||
total: number;
|
||||
}> {
|
||||
const params = new URLSearchParams({ days: String(days) });
|
||||
return fetchJson<{
|
||||
created: number;
|
||||
updated: number;
|
||||
open: number;
|
||||
closed: number;
|
||||
total: number;
|
||||
}>(`/api/v1/forgejo/repositories/${repositoryId}/sync/recent?${params}`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export interface ForgejoValidationStatus {
|
||||
ok: boolean;
|
||||
status: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue