Skip to content

Commit baadc8c

Browse files
committed
update
1 parent b5a100e commit baadc8c

2 files changed

Lines changed: 364 additions & 4 deletions

File tree

packages/app/app/pages/index.vue

Lines changed: 270 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,140 @@ definePageMeta({
44
mainClass: "",
55
});
66
7+
import { ref, onMounted } from 'vue';
8+
9+
// Define types for the repository and log data
10+
interface RepositoryOwner {
11+
id?: string;
12+
login: string;
13+
avatar_url: string;
14+
}
15+
16+
interface LatestCommit {
17+
sha: string;
18+
message: string;
19+
date: string;
20+
}
21+
22+
interface Repository {
23+
id: string;
24+
name: string;
25+
owner: RepositoryOwner;
26+
full_name: string;
27+
description: string | null;
28+
default_branch: string;
29+
html_url: string;
30+
homepage?: string | null;
31+
stargazers_count: number;
32+
watchers_count: number;
33+
forks_count: number;
34+
open_issues_count?: number;
35+
indexed_at: string;
36+
latest_commit: LatestCommit | null;
37+
}
38+
39+
interface LogEntry {
40+
timestamp: string;
41+
message: string;
42+
type: 'info' | 'error' | 'success';
43+
data: any;
44+
}
45+
46+
// State for repositories and logs
47+
const repositories = ref<Repository[]>([]);
48+
const loading = ref(true);
49+
const error = ref<string | null>(null);
50+
const clientLogs = ref<LogEntry[]>([]);
51+
const showLogs = ref(false);
52+
53+
// Function to add logs that will be visible in the client
54+
const logToClient = (message: string, type: 'info' | 'error' | 'success' = 'info', data: any = null): void => {
55+
const timestamp = new Date().toISOString();
56+
const logEntry: LogEntry = {
57+
timestamp,
58+
message,
59+
type,
60+
data
61+
};
62+
clientLogs.value.unshift(logEntry);
63+
console.log(`[CLIENT-LOG][${type}] ${message}`, data || '');
64+
};
65+
66+
// Fetch all repositories from R2 storage
67+
const fetchRepositories = async (): Promise<void> => {
68+
try {
69+
loading.value = true;
70+
logToClient('Fetching repositories from R2 storage...', 'info');
71+
72+
const response = await fetch('/api/repos');
73+
const responseData = await response.json() as {
74+
repositories: Repository[];
75+
error?: boolean;
76+
message?: string;
77+
debug_info?: any;
78+
};
79+
80+
if (responseData.error) {
81+
logToClient(`Error fetching repositories: ${responseData.message || 'Unknown error'}`, 'error', responseData.debug_info);
82+
error.value = responseData.message || 'Unknown error';
83+
loading.value = false;
84+
return;
85+
}
86+
87+
repositories.value = responseData.repositories;
88+
logToClient(
89+
`Successfully fetched ${responseData.repositories.length} repositories from R2 storage`,
90+
'success',
91+
responseData.debug_info
92+
);
93+
94+
// Log available repositories
95+
repositories.value.forEach(repo => {
96+
logToClient(`Repository available: ${repo.full_name}`, 'info', {
97+
default_branch: repo.default_branch,
98+
latest_commit: repo.latest_commit ?
99+
`${repo.latest_commit.sha.substring(0, 7)} - ${repo.latest_commit.message.split('\n')[0]}` :
100+
'No commits found'
101+
});
102+
});
103+
} catch (e: any) {
104+
logToClient(`Failed to fetch repositories: ${e.message}`, 'error');
105+
error.value = `Failed to fetch repositories: ${e.message}`;
106+
} finally {
107+
loading.value = false;
108+
}
109+
};
110+
111+
// Truncate long texts
112+
const truncate = (text: string, length = 60): string => {
113+
if (!text) return '';
114+
return text.length > length ? text.substring(0, length) + '...' : text;
115+
};
116+
117+
// Format date
118+
const formatDate = (dateString: string): string => {
119+
if (!dateString) return 'Unknown';
120+
const date = new Date(dateString);
121+
return date.toLocaleString();
122+
};
123+
124+
// Toggle logs visibility
125+
const toggleLogs = (): void => {
126+
showLogs.value = !showLogs.value;
127+
};
128+
129+
// Clear logs
130+
const clearLogs = (): void => {
131+
clientLogs.value = [];
132+
logToClient('Logs cleared', 'info');
133+
};
134+
135+
// Load repositories on mount
136+
onMounted(() => {
137+
fetchRepositories();
138+
});
139+
140+
// Template refs
7141
const gettingStartedEl = useTemplateRef("getting-started");
8142
9143
function scrollToGettingStarted() {
@@ -15,14 +149,131 @@ function scrollToGettingStarted() {
15149

16150
<template>
17151
<div>
18-
<div
19-
class="my-container flex flex-col items-center gap-4 md:gap-12 min-h-[calc(100vh-80px)]"
20-
>
152+
<div class="my-container flex flex-col items-center gap-4 md:gap-8 min-h-[calc(100vh-80px)]">
21153
<img src="/favicon.svg" alt="logo" width="64" height="64" />
22154

155+
<!-- Repository search -->
23156
<RepoSearch />
24157

25-
<div class="flex-1" />
158+
<!-- Repository List -->
159+
<div class="w-full max-w-4xl mt-4">
160+
<h2 class="text-xl font-semibold mb-4">Available Repositories in R2 Storage</h2>
161+
162+
<div v-if="loading" class="p-4 text-center">
163+
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-primary-500 mx-auto"></div>
164+
<p class="mt-2">Loading repositories...</p>
165+
</div>
166+
167+
<div v-else-if="error" class="p-4 border border-red-300 bg-red-50 dark:bg-red-900/20 dark:border-red-800 rounded-lg">
168+
<p class="text-red-600 dark:text-red-400">{{ error }}</p>
169+
<button @click="fetchRepositories" class="mt-2 px-4 py-2 bg-primary-500 text-white rounded hover:bg-primary-600 transition">
170+
Try Again
171+
</button>
172+
</div>
173+
174+
<div v-else-if="repositories.length === 0" class="p-4 border border-gray-300 dark:border-gray-700 rounded-lg">
175+
<p>No repositories found in R2 storage.</p>
176+
</div>
177+
178+
<div v-else class="grid gap-4 md:grid-cols-2">
179+
<div v-for="repo in repositories" :key="repo.id"
180+
class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition">
181+
<div class="flex items-center gap-3 mb-2">
182+
<img v-if="repo.owner.avatar_url" :src="repo.owner.avatar_url" alt="avatar" class="w-8 h-8 rounded-full" />
183+
<div v-else class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
184+
<span>{{ repo.owner.login[0] }}</span>
185+
</div>
186+
187+
<h3 class="font-semibold">
188+
<NuxtLink :to="`/view/${repo.owner.login}/${repo.name}`" class="text-primary-500 hover:underline">
189+
{{ repo.full_name }}
190+
</NuxtLink>
191+
</h3>
192+
</div>
193+
194+
<p v-if="repo.description" class="text-sm text-gray-600 dark:text-gray-300 mb-3">
195+
{{ truncate(repo.description) }}
196+
</p>
197+
198+
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
199+
<div class="flex gap-2 items-center">
200+
<UIcon name="i-heroicons-code-bracket" />
201+
<span>Default branch: {{ repo.default_branch }}</span>
202+
</div>
203+
204+
<div class="flex gap-2 items-center mt-1">
205+
<UIcon name="i-heroicons-star" />
206+
<span>{{ repo.stargazers_count || 0 }} stars</span>
207+
</div>
208+
209+
<div class="mt-2">
210+
<p class="font-medium">Latest commit:</p>
211+
<div v-if="repo.latest_commit" class="bg-gray-50 dark:bg-gray-800 p-2 rounded mt-1">
212+
<div class="flex gap-2 items-center">
213+
<UIcon name="i-heroicons-hashtag" />
214+
<span class="text-xs font-mono">{{ repo.latest_commit.sha.substring(0, 7) }}</span>
215+
</div>
216+
<p class="text-xs mt-1">{{ truncate(repo.latest_commit.message, 80) }}</p>
217+
<p class="text-xs mt-1 text-gray-400">{{ formatDate(repo.latest_commit.date) }}</p>
218+
</div>
219+
<p v-else class="text-yellow-600 dark:text-yellow-400 italic">No commits found</p>
220+
</div>
221+
222+
<div class="text-xs mt-2 text-gray-400">
223+
Indexed: {{ formatDate(repo.indexed_at) }}
224+
</div>
225+
</div>
226+
</div>
227+
</div>
228+
</div>
229+
230+
<!-- Client-side Logs Panel -->
231+
<div class="fixed bottom-4 right-4 z-50">
232+
<button @click="toggleLogs"
233+
class="px-4 py-2 bg-gray-700 text-white rounded-full flex items-center gap-2 hover:bg-gray-800 transition">
234+
<UIcon name="i-heroicons-bug-ant" />
235+
<span>{{ showLogs ? 'Hide' : 'Show' }} Logs</span>
236+
<span class="bg-red-500 rounded-full px-2 text-xs">{{ clientLogs.length }}</span>
237+
</button>
238+
239+
<div v-if="showLogs" class="mt-2 bg-gray-800 text-white p-4 rounded-lg w-96 max-h-96 overflow-y-auto shadow-xl">
240+
<div class="flex justify-between items-center mb-2">
241+
<h3 class="font-bold">Client Logs</h3>
242+
<button @click="clearLogs" class="text-xs px-2 py-1 bg-red-600 rounded hover:bg-red-700">
243+
Clear
244+
</button>
245+
</div>
246+
247+
<div v-if="clientLogs.length === 0" class="text-center py-4 text-gray-400">
248+
No logs yet
249+
</div>
250+
251+
<div v-for="(log, index) in clientLogs" :key="index"
252+
:class="`mb-2 p-2 rounded text-xs ${
253+
log.type === 'error' ? 'bg-red-900/50 border-l-2 border-red-600' :
254+
log.type === 'success' ? 'bg-green-900/50 border-l-2 border-green-600' :
255+
'bg-gray-700/50 border-l-2 border-gray-500'
256+
}`">
257+
<div class="flex justify-between text-xs">
258+
<span class="font-mono">{{ new Date(log.timestamp).toLocaleTimeString() }}</span>
259+
<span :class="`font-bold ${
260+
log.type === 'error' ? 'text-red-400' :
261+
log.type === 'success' ? 'text-green-400' :
262+
'text-blue-400'
263+
}`">
264+
{{ log.type.toUpperCase() }}
265+
</span>
266+
</div>
267+
<div class="mt-1">{{ log.message }}</div>
268+
<div v-if="log.data"
269+
class="mt-1 p-1 bg-black/30 rounded font-mono text-gray-300 overflow-x-auto">
270+
<pre>{{ typeof log.data === 'string' ? log.data : JSON.stringify(log.data, null, 2) }}</pre>
271+
</div>
272+
</div>
273+
</div>
274+
</div>
275+
276+
<div class="flex-1"></div>
26277

27278
<div
28279
class="flex flex-col items-center gap-2"
@@ -60,3 +311,18 @@ function scrollToGettingStarted() {
60311
</div>
61312
</div>
62313
</template>
314+
315+
<style scoped>
316+
.animate-spin {
317+
animation: spin 1s linear infinite;
318+
}
319+
320+
@keyframes spin {
321+
from {
322+
transform: rotate(0deg);
323+
}
324+
to {
325+
transform: rotate(360deg);
326+
}
327+
}
328+
</style>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useR2GitHubService } from "../utils/r2-service";
2+
3+
export default defineEventHandler(async (event) => {
4+
try {
5+
console.log("[R2API] List all repositories endpoint called");
6+
7+
// Use R2 service
8+
const r2Service = useR2GitHubService(event);
9+
10+
// Get all repositories from R2
11+
const repositories = await r2Service.listAllRepositories();
12+
13+
console.log(`[R2API] Found ${repositories.length} repositories in R2 storage`);
14+
15+
// Get latest commit for each repository
16+
const reposWithLatestCommit = await Promise.all(
17+
repositories.map(async (repo) => {
18+
try {
19+
const commits = await r2Service.listCommits(repo.owner.login, repo.name, 1, 1);
20+
const latestCommit = commits.length > 0 ? commits[0] : null;
21+
22+
return {
23+
id: repo.id,
24+
name: repo.name,
25+
owner: {
26+
login: repo.owner.login,
27+
avatar_url: repo.owner.avatar_url,
28+
},
29+
full_name: `${repo.owner.login}/${repo.name}`,
30+
description: repo.description,
31+
default_branch: repo.default_branch,
32+
html_url: repo.html_url,
33+
stargazers_count: repo.stargazers_count,
34+
watchers_count: repo.watchers_count,
35+
forks_count: repo.forks_count,
36+
indexed_at: repo.indexed_at,
37+
latest_commit: latestCommit ? {
38+
sha: latestCommit.sha,
39+
message: latestCommit.commit.message,
40+
date: latestCommit.commit.author.date,
41+
} : null,
42+
};
43+
} catch (error) {
44+
console.error(`[R2API] Error fetching commits for ${repo.owner.login}/${repo.name}:`, error);
45+
return {
46+
id: repo.id,
47+
name: repo.name,
48+
owner: {
49+
login: repo.owner.login,
50+
avatar_url: repo.owner.avatar_url,
51+
},
52+
full_name: `${repo.owner.login}/${repo.name}`,
53+
description: repo.description,
54+
default_branch: repo.default_branch,
55+
html_url: repo.html_url,
56+
stargazers_count: repo.stargazers_count,
57+
watchers_count: repo.watchers_count,
58+
forks_count: repo.forks_count,
59+
indexed_at: repo.indexed_at,
60+
latest_commit: null,
61+
};
62+
}
63+
})
64+
);
65+
66+
// Add debug info for client
67+
const clientDebugInfo = {
68+
timestamp: new Date().toISOString(),
69+
storage_info: r2Service.getStorageInfo(),
70+
repository_count: repositories.length,
71+
repository_names: repositories.map(repo => `${repo.owner.login}/${repo.name}`),
72+
};
73+
74+
return {
75+
repositories: reposWithLatestCommit,
76+
debug_info: clientDebugInfo
77+
};
78+
} catch (error) {
79+
console.error("[R2API] Error listing repositories:", error);
80+
81+
// Return error with debug info for client
82+
return {
83+
repositories: [],
84+
error: true,
85+
message: `Failed to load repositories: ${error instanceof Error ? error.message : 'Unknown error'}`,
86+
debug_info: {
87+
timestamp: new Date().toISOString(),
88+
error_message: error instanceof Error ? error.message : 'Unknown error',
89+
error_name: error instanceof Error ? error.name : 'Unknown',
90+
r2_connection: 'Failed'
91+
}
92+
};
93+
}
94+
});

0 commit comments

Comments
 (0)