Dashboard UI overhaul: modern design with system stats, SVG icons, Inter font, theme toggle, loading states, and log modal
This commit is contained in:
@@ -1,38 +1,124 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "../lib/api.js";
|
||||
|
||||
let repos = $state([]);
|
||||
let commits = $state([]);
|
||||
let selectedRepo = $state("");
|
||||
async function load() { const data = await get("/gitea/repos"); repos = data?.data || []; }
|
||||
let loading = $state(true);
|
||||
let loadingCommits = $state(false);
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
const data = await get("/gitea/repos");
|
||||
repos = Array.isArray(data) ? data : data?.data || [];
|
||||
loading = false;
|
||||
}
|
||||
|
||||
async function showCommits(owner, repo) {
|
||||
selectedRepo = `${owner}/${repo}`;
|
||||
loadingCommits = true;
|
||||
const data = await get(`/gitea/repos/${owner}/${repo}/commits`);
|
||||
commits = Array.isArray(data) ? data : [];
|
||||
loadingCommits = false;
|
||||
}
|
||||
load();
|
||||
|
||||
function timeAgo(d) {
|
||||
const s = Math.floor((Date.now() - new Date(d)) / 1000);
|
||||
if (s < 60) return "just now";
|
||||
if (s < 3600) return Math.floor(s / 60) + "m ago";
|
||||
if (s < 86400) return Math.floor(s / 3600) + "h ago";
|
||||
return Math.floor(s / 86400) + "d ago";
|
||||
}
|
||||
|
||||
const langColors = {
|
||||
Python: "#3572A5", JavaScript: "#f1e05a", Shell: "#89e051", Go: "#00ADD8",
|
||||
TypeScript: "#3178c6", Markdown: "#083fa1", CSS: "#563d7c", HTML: "#e34c26",
|
||||
};
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-6">Gitea Repos</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{#each repos as r}
|
||||
<button class="border rounded-lg p-4 text-left hover:bg-gray-50" onclick={() => showCommits(r.owner?.login, r.name)}>
|
||||
<span class="font-medium">{r.full_name}</span>
|
||||
<p class="text-xs text-gray-400 mt-1">{r.description || "No description"}</p>
|
||||
<p class="text-xs text-gray-400">Updated {new Date(r.updated_at).toLocaleDateString()}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-surface-900 tracking-tight">Repositories</h1>
|
||||
<p class="text-sm text-surface-400 mt-1">{repos.length} repositories on Gitea</p>
|
||||
</div>
|
||||
|
||||
{#if selectedRepo}
|
||||
<div class="mt-6">
|
||||
<h3 class="font-semibold mb-3">Commits: {selectedRepo}</h3>
|
||||
<div class="space-y-2">
|
||||
{#each commits.slice(0, 20) as c}
|
||||
<div class="border-b pb-2">
|
||||
<p class="text-sm">{c.commit?.message?.split("\n")[0]}</p>
|
||||
<p class="text-xs text-gray-400">{c.commit?.author?.name} - {new Date(c.commit?.author?.date).toLocaleString()}</p>
|
||||
</div>
|
||||
{#if loading}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{#each Array(4) as _}
|
||||
<div class="h-[100px] rounded-xl bg-surface-100 animate-pulse"></div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{#each repos as r}
|
||||
<button
|
||||
class="bg-white rounded-xl border border-surface-200 p-4 text-left shadow-sm hover:shadow-md hover:border-primary-200 transition-all duration-200 group {selectedRepo === `${r.owner?.login}/${r.name}` ? 'ring-2 ring-primary-500 border-primary-300' : ''}"
|
||||
onclick={() => showCommits(r.owner?.login, r.name)}
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-semibold text-surface-800 group-hover:text-primary-600 transition-colors truncate">{r.name}</p>
|
||||
<p class="text-xs text-surface-400 mt-1 line-clamp-1">{r.description || "No description"}</p>
|
||||
</div>
|
||||
{#if r.language}
|
||||
<span class="flex items-center gap-1 text-[11px] text-surface-500 shrink-0 ml-2">
|
||||
<span class="w-2 h-2 rounded-full" style="background: {langColors[r.language] || '#888'}"></span>
|
||||
{r.language}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-[11px] text-surface-400 mt-2.5">Updated {timeAgo(r.updated_at)}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Commits Panel -->
|
||||
{#if selectedRepo}
|
||||
<div class="bg-white rounded-xl border border-surface-200 p-5 shadow-sm">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-bold text-surface-800">Recent Commits</h3>
|
||||
<p class="text-xs text-surface-400 mt-0.5">{selectedRepo}</p>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close commits"
|
||||
class="text-xs text-surface-400 hover:text-surface-600 transition-colors"
|
||||
onclick={() => { selectedRepo = ""; commits = []; }}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
{#if loadingCommits}
|
||||
<div class="space-y-3">
|
||||
{#each Array(3) as _}
|
||||
<div class="h-12 rounded-lg bg-surface-100 animate-pulse"></div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-0">
|
||||
{#each commits.slice(0, 15) as c, i}
|
||||
<div class="flex gap-3 py-2.5 {i < commits.length - 1 ? 'border-b border-surface-100' : ''}">
|
||||
<div class="flex flex-col items-center pt-1">
|
||||
<div class="w-2 h-2 rounded-full bg-primary-400 shrink-0"></div>
|
||||
{#if i < commits.length - 1}
|
||||
<div class="w-px flex-1 bg-surface-200 mt-1"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm text-surface-700 truncate">{c.commit?.message?.split("\n")[0]}</p>
|
||||
<p class="text-[11px] text-surface-400 mt-0.5">
|
||||
<span class="font-medium">{c.commit?.author?.name}</span> · {timeAgo(c.commit?.author?.date)}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-[10px] font-mono text-surface-400 bg-surface-50 px-1.5 py-0.5 rounded h-fit shrink-0">{c.sha?.slice(0, 7)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user