ticker
This commit is contained in:
parent
63fa79b95e
commit
e54a29230d
|
|
@ -40,6 +40,7 @@ import { getApiBaseUrl } from "@/lib/api-base";
|
||||||
import {
|
import {
|
||||||
getForgejoRepositories,
|
getForgejoRepositories,
|
||||||
deleteForgejoRepository,
|
deleteForgejoRepository,
|
||||||
|
refreshRepositoryRecentIssues,
|
||||||
syncRepository,
|
syncRepository,
|
||||||
validateRepository,
|
validateRepository,
|
||||||
type ForgejoRepository,
|
type ForgejoRepository,
|
||||||
|
|
@ -531,10 +532,10 @@ export default function ForgejoRepositoriesPage() {
|
||||||
|
|
||||||
const handleSync = async (repository: ForgejoRepository) => {
|
const handleSync = async (repository: ForgejoRepository) => {
|
||||||
try {
|
try {
|
||||||
const result = await syncRepository(repository.id);
|
const result = await refreshRepositoryRecentIssues(repository.id);
|
||||||
setNotice({
|
setNotice({
|
||||||
tone: "success",
|
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
|
// Refetch to update last_sync_at
|
||||||
const data = await getForgejoRepositories();
|
const data = await getForgejoRepositories();
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@ textarea::placeholder {
|
||||||
animation: progress-shimmer 1.8s linear infinite;
|
animation: progress-shimmer 1.8s linear infinite;
|
||||||
}
|
}
|
||||||
.animate-ticker {
|
.animate-ticker {
|
||||||
animation: ticker-scroll 45s linear infinite;
|
animation: ticker-scroll 70s linear infinite;
|
||||||
}
|
}
|
||||||
.ticker-fade-mask {
|
.ticker-fade-mask {
|
||||||
-webkit-mask-image: linear-gradient(to right, transparent 0px, black 48px, black calc(100% - 48px), transparent 100%);
|
-webkit-mask-image: linear-gradient(to right, transparent 0px, black 48px, black calc(100% - 48px), transparent 100%);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
Archive,
|
Archive,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
CircleDot,
|
CircleDot,
|
||||||
|
ExternalLink,
|
||||||
Eye,
|
Eye,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
GitCommitHorizontal,
|
GitCommitHorizontal,
|
||||||
|
|
@ -51,6 +52,25 @@ const formatConnectionUrl = (value?: string | null) => {
|
||||||
return value.replace(/^https?:\/\//i, "").replace(/\/$/, "");
|
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) => {
|
const repositoryTone = (repo: ForgejoRepository) => {
|
||||||
if (repo.last_sync_error) return "danger";
|
if (repo.last_sync_error) return "danger";
|
||||||
if (!repo.active || repo.is_archived) return "muted";
|
if (!repo.active || repo.is_archived) return "muted";
|
||||||
|
|
@ -300,14 +320,33 @@ const columns = (
|
||||||
accessorKey: "connection",
|
accessorKey: "connection",
|
||||||
header: "Connection",
|
header: "Connection",
|
||||||
cell: ({ row }) => {
|
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 (
|
return (
|
||||||
<div className="min-w-[180px]">
|
<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">
|
<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>
|
</span>
|
||||||
</div>
|
</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}
|
onClick={handleSync}
|
||||||
disabled={isSyncLoading}
|
disabled={isSyncLoading}
|
||||||
className="h-7 w-7 rounded-lg p-0 text-muted hover:bg-[color:var(--surface-muted)] hover:text-strong"
|
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 ? (
|
{isSyncLoading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
|
@ -514,7 +553,7 @@ function ActionsCell({
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Sync issues</TooltipContent>
|
<TooltipContent>Refresh recent issues</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{onValidate && (
|
{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 {
|
export interface ForgejoValidationStatus {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue