@@ -3,10 +3,12 @@ document.addEventListener('DOMContentLoaded', () => {
3
3
// DOM Elements
4
4
const repoUrlInput = document . getElementById ( 'repoUrl' ) ;
5
5
const loadButton = document . getElementById ( 'loadButton' ) ;
6
+ const appTitle = document . querySelector ( 'h1' ) ;
6
7
const releaseInfoDiv = document . getElementById ( 'releaseInfo' ) ;
7
8
const infoGridDiv = document . getElementById ( 'infoGrid' ) ;
8
9
const archSelect = document . getElementById ( 'archSelect' ) ;
9
10
const componentSelect = document . getElementById ( 'componentSelect' ) ;
11
+ const distSelect = document . getElementById ( 'distSelect' ) ;
10
12
const packagesUrlDiv = document . getElementById ( 'packagesUrl' ) ;
11
13
const errorMessageDiv = document . getElementById ( 'errorMessage' ) ;
12
14
const packageCountDiv = document . getElementById ( 'packageCount' ) ;
@@ -30,6 +32,8 @@ document.addEventListener('DOMContentLoaded', () => {
30
32
let sortField = 'name' ;
31
33
let sortDirection = 'asc' ;
32
34
let repoBaseUrl = '' ;
35
+ let availableDists = [ ] ;
36
+ let defaultDist = 'stable' ;
33
37
34
38
// Proxy URL
35
39
const PROXY_URL = '/proxy?url=' ;
@@ -42,9 +46,20 @@ document.addEventListener('DOMContentLoaded', () => {
42
46
} ) ;
43
47
44
48
loadButton . addEventListener ( 'click' , fetchRelease ) ;
49
+
50
+ // Add click handler to the app title to reset the application
51
+ appTitle . addEventListener ( 'click' , ( ) => {
52
+ resetState ( ) ;
53
+ repoUrlInput . value = '' ;
54
+ } ) ;
55
+
56
+ // Add CSS class for clickable title instead of setting the style directly
57
+ appTitle . classList . add ( 'clickable-title' ) ;
45
58
46
59
archSelect . addEventListener ( 'change' , updatePackagesUrl ) ;
47
60
componentSelect . addEventListener ( 'change' , updatePackagesUrl ) ;
61
+ distSelect . addEventListener ( 'change' , handleDistChange ) ;
62
+ distSelect . addEventListener ( 'change' , updatePackagesUrl ) ;
48
63
49
64
searchQueryInput . addEventListener ( 'input' , ( e ) => {
50
65
searchQuery = e . target . value . toLowerCase ( ) ;
@@ -93,14 +108,18 @@ document.addEventListener('DOMContentLoaded', () => {
93
108
94
109
// Functions
95
110
async function fetchRelease ( ) {
96
- const repoUrl = repoUrlInput . value . trim ( ) ;
111
+ let repoUrl = repoUrlInput . value . trim ( ) ;
97
112
113
+ // Use default URL if input is empty
98
114
if ( ! repoUrl ) {
99
- showError ( 'Please enter a repository URL' ) ;
100
- return ;
115
+ repoUrl = 'https://apt.armbian.com' ;
116
+ repoUrlInput . value = repoUrl ;
101
117
}
102
118
103
119
try {
120
+ // Reset all state and selections before loading a new repository
121
+ resetState ( ) ;
122
+
104
123
showLoading ( true ) ;
105
124
clearError ( ) ;
106
125
hidePackagesInfo ( ) ;
@@ -113,6 +132,15 @@ document.addEventListener('DOMContentLoaded', () => {
113
132
repoBaseUrl = cleanBaseUrl ;
114
133
}
115
134
135
+ // Try to extract the distribution from the URL
136
+ let initialDist = defaultDist ;
137
+ if ( cleanBaseUrl . includes ( '/dists/' ) ) {
138
+ const [ _ , distsPath ] = cleanBaseUrl . split ( '/dists/' ) ;
139
+ if ( distsPath && distsPath . trim ( ) !== '' ) {
140
+ initialDist = distsPath . split ( '/' ) [ 0 ] ;
141
+ }
142
+ }
143
+
116
144
// Build the Release file URL - handle different URL patterns
117
145
let releaseUrl ;
118
146
if ( cleanBaseUrl . includes ( '/dists/' ) ) {
@@ -127,10 +155,10 @@ document.addEventListener('DOMContentLoaded', () => {
127
155
releaseUrl = `${ cleanBaseUrl } /Release` ;
128
156
} else {
129
157
// URL just ends with dists/
130
- releaseUrl = `${ basePart } /dists/stable /Release` ;
158
+ releaseUrl = `${ basePart } /dists/${ initialDist } /Release` ;
131
159
}
132
160
} else {
133
- releaseUrl = `${ cleanBaseUrl } /dists/stable /Release` ;
161
+ releaseUrl = `${ cleanBaseUrl } /dists/${ initialDist } /Release` ;
134
162
}
135
163
136
164
// Fetch the Release file through the proxy
@@ -144,6 +172,23 @@ document.addEventListener('DOMContentLoaded', () => {
144
172
// Parse the Release file content
145
173
releaseInfo = parseReleaseFile ( releaseContent ) ;
146
174
175
+ // Check for Suite and Codename to determine available distributions
176
+ availableDists = [ ] ;
177
+ if ( releaseInfo . Suite ) availableDists . push ( releaseInfo . Suite ) ;
178
+ if ( releaseInfo . Codename && releaseInfo . Codename !== releaseInfo . Suite ) availableDists . push ( releaseInfo . Codename ) ;
179
+
180
+ // Fetch dists directory to discover available distributions
181
+ try {
182
+ await fetchAvailableDists ( ) ;
183
+ } catch ( e ) {
184
+ console . warn ( "Could not fetch available distributions:" , e ) ;
185
+ }
186
+
187
+ // Add the initial dist if not already in the list
188
+ if ( ! availableDists . includes ( initialDist ) ) {
189
+ availableDists . push ( initialDist ) ;
190
+ }
191
+
147
192
// Show release info
148
193
showReleaseInfo ( ) ;
149
194
} catch ( error ) {
@@ -186,8 +231,9 @@ document.addEventListener('DOMContentLoaded', () => {
186
231
function updatePackagesUrl ( ) {
187
232
const arch = archSelect . value ;
188
233
const component = componentSelect . value ;
234
+ const dist = distSelect . value ;
189
235
190
- if ( ! arch || ! component || ! releaseInfo || ! releaseInfo . Codename ) {
236
+ if ( ! arch || ! component || ! dist || ! releaseInfo ) {
191
237
packagesUrl = '' ;
192
238
packagesUrlDiv . style . display = 'none' ;
193
239
return ;
@@ -196,7 +242,7 @@ document.addEventListener('DOMContentLoaded', () => {
196
242
// Build the Packages URL
197
243
packagesUrl = buildPackagesUrl (
198
244
repoUrlInput . value . trim ( ) ,
199
- releaseInfo . Codename ,
245
+ dist ,
200
246
component ,
201
247
arch
202
248
) ;
@@ -209,7 +255,98 @@ document.addEventListener('DOMContentLoaded', () => {
209
255
fetchPackages ( ) ;
210
256
}
211
257
212
- function showReleaseInfo ( ) {
258
+ async function fetchAvailableDists ( ) {
259
+ if ( ! repoBaseUrl ) return ;
260
+
261
+ // Try to fetch the /dists/ directory to discover available distributions
262
+ // This may not work on all repositories as they might not allow directory listings
263
+ try {
264
+ const distsUrl = `${ repoBaseUrl } /dists/` ;
265
+ const response = await fetch ( PROXY_URL + encodeURIComponent ( distsUrl ) ) ;
266
+ const content = await response . text ( ) ;
267
+
268
+ // Simple regex to find directory names
269
+ // This is a basic approach and might not work for all server configurations
270
+ const dirRegex = / < a [ ^ > ] * h r e f = " ( [ ^ " \/ ] + ) \/ " [ ^ > ] * > / g;
271
+ let match ;
272
+
273
+ while ( ( match = dirRegex . exec ( content ) ) !== null ) {
274
+ const dist = match [ 1 ] ;
275
+ if ( ! availableDists . includes ( dist ) && dist !== '.' && dist !== '..' ) {
276
+ availableDists . push ( dist ) ;
277
+ }
278
+ }
279
+ } catch ( error ) {
280
+ console . warn ( "Failed to fetch distributions from directory listing" , error ) ;
281
+ }
282
+ }
283
+
284
+ async function handleDistChange ( ) {
285
+ const dist = distSelect . value ;
286
+
287
+ if ( ! dist || ! repoBaseUrl ) {
288
+ return ;
289
+ }
290
+
291
+ try {
292
+ showLoading ( true ) ;
293
+ clearError ( ) ;
294
+
295
+ // Reset package-related UI elements
296
+ packagesUrl = '' ;
297
+ packagesUrlDiv . style . display = 'none' ;
298
+ packageCountDiv . style . display = 'none' ;
299
+ tableContainerDiv . style . display = 'none' ;
300
+
301
+ // Reset architecture and component selections before fetching new data
302
+ archSelect . innerHTML = '<option value="">Select Architecture</option>' ;
303
+ componentSelect . innerHTML = '<option value="">Select Component</option>' ;
304
+
305
+ // Build the Release file URL for the selected distribution
306
+ const releaseUrl = `${ repoBaseUrl } /dists/${ dist } /Release` ;
307
+
308
+ // Fetch the Release file through the proxy
309
+ const response = await fetch ( PROXY_URL + encodeURIComponent ( releaseUrl ) ) ;
310
+ const releaseContent = await response . text ( ) ;
311
+
312
+ if ( response . status !== 200 ) {
313
+ throw new Error ( `Failed to fetch Release file for ${ dist } : ${ response . statusText } ` ) ;
314
+ }
315
+
316
+ // Parse the Release file content
317
+ releaseInfo = parseReleaseFile ( releaseContent ) ;
318
+
319
+ // Populate architecture dropdown
320
+ if ( releaseInfo . Architectures ) {
321
+ releaseInfo . Architectures . forEach ( arch => {
322
+ const option = document . createElement ( 'option' ) ;
323
+ option . value = arch ;
324
+ option . textContent = arch ;
325
+ archSelect . appendChild ( option ) ;
326
+ } ) ;
327
+ }
328
+
329
+ // Populate component dropdown
330
+ if ( releaseInfo . Components ) {
331
+ releaseInfo . Components . forEach ( component => {
332
+ const option = document . createElement ( 'option' ) ;
333
+ option . value = component ;
334
+ option . textContent = component ;
335
+ componentSelect . appendChild ( option ) ;
336
+ } ) ;
337
+ }
338
+
339
+ // Update info grid with the new release info
340
+ updateReleaseInfoGrid ( ) ;
341
+
342
+ } catch ( error ) {
343
+ showError ( `Error loading distribution: ${ error . message } ` ) ;
344
+ } finally {
345
+ showLoading ( false ) ;
346
+ }
347
+ }
348
+
349
+ function updateReleaseInfoGrid ( ) {
213
350
if ( ! releaseInfo ) return ;
214
351
215
352
// Clear the info grid
@@ -230,6 +367,13 @@ document.addEventListener('DOMContentLoaded', () => {
230
367
infoGridDiv . appendChild ( div ) ;
231
368
}
232
369
} ) ;
370
+ }
371
+
372
+ function showReleaseInfo ( ) {
373
+ if ( ! releaseInfo ) return ;
374
+
375
+ // Update info grid
376
+ updateReleaseInfoGrid ( ) ;
233
377
234
378
// Populate architecture dropdown
235
379
archSelect . innerHTML = '<option value="">Select Architecture</option>' ;
@@ -252,6 +396,20 @@ document.addEventListener('DOMContentLoaded', () => {
252
396
componentSelect . appendChild ( option ) ;
253
397
} ) ;
254
398
}
399
+
400
+ // Populate distribution dropdown
401
+ distSelect . innerHTML = '<option value="">Select Distribution</option>' ;
402
+ availableDists . sort ( ) . forEach ( dist => {
403
+ const option = document . createElement ( 'option' ) ;
404
+ option . value = dist ;
405
+ option . textContent = dist ;
406
+ // Set the active distribution based on Codename or selected value
407
+ if ( ( releaseInfo . Codename && dist === releaseInfo . Codename ) ||
408
+ distSelect . value === dist ) {
409
+ option . selected = true ;
410
+ }
411
+ distSelect . appendChild ( option ) ;
412
+ } ) ;
255
413
256
414
// Show the release info section
257
415
releaseInfoDiv . style . display = 'block' ;
@@ -466,9 +624,15 @@ document.addEventListener('DOMContentLoaded', () => {
466
624
}
467
625
468
626
function hidePackagesInfo ( ) {
627
+ // Hide UI elements
469
628
packagesUrlDiv . style . display = 'none' ;
470
629
packageCountDiv . style . display = 'none' ;
471
630
tableContainerDiv . style . display = 'none' ;
631
+
632
+ // Clear packages data
633
+ packages = [ ] ;
634
+ packagesTable . innerHTML = '' ;
635
+ packagesUrl = '' ;
472
636
}
473
637
474
638
// Helper functions for parsing
@@ -550,19 +714,49 @@ document.addEventListener('DOMContentLoaded', () => {
550
714
return packages ;
551
715
}
552
716
553
- function buildPackagesUrl ( baseUrl , codename , component , arch ) {
717
+ function buildPackagesUrl ( baseUrl , dist , component , arch ) {
554
718
const cleanBaseUrl = baseUrl . replace ( / \/ $ / , '' ) ;
555
719
// Check if the base URL already includes the dists directory
556
720
if ( cleanBaseUrl . includes ( '/dists/' ) ) {
557
721
// Extract the base part before /dists/
558
722
const basePart = cleanBaseUrl . split ( '/dists/' ) [ 0 ] ;
559
- // Check if codename is already in the URL
560
- if ( cleanBaseUrl . includes ( `/dists/${ codename } ` ) ) {
723
+ // Check if dist is already in the URL
724
+ if ( cleanBaseUrl . includes ( `/dists/${ dist } ` ) ) {
561
725
return `${ cleanBaseUrl } /${ component } /binary-${ arch } /Packages` ;
562
726
} else {
563
- return `${ basePart } /dists/${ codename } /${ component } /binary-${ arch } /Packages` ;
727
+ return `${ basePart } /dists/${ dist } /${ component } /binary-${ arch } /Packages` ;
564
728
}
565
729
}
566
- return `${ cleanBaseUrl } /dists/${ codename } /${ component } /binary-${ arch } /Packages` ;
730
+ return `${ cleanBaseUrl } /dists/${ dist } /${ component } /binary-${ arch } /Packages` ;
731
+ }
732
+
733
+ // Function to reset all state when loading a new repository
734
+ function resetState ( ) {
735
+ // Reset state variables
736
+ releaseInfo = null ;
737
+ packagesUrl = '' ;
738
+ packages = [ ] ;
739
+ currentPage = 1 ;
740
+ pageInput . value = 1 ;
741
+ searchQuery = '' ;
742
+ searchQueryInput . value = '' ;
743
+ availableDists = [ ] ;
744
+
745
+ // Reset UI selectors
746
+ distSelect . innerHTML = '<option value="">Select Distribution</option>' ;
747
+ archSelect . innerHTML = '<option value="">Select Architecture</option>' ;
748
+ componentSelect . innerHTML = '<option value="">Select Component</option>' ;
749
+
750
+ // Hide all repository-specific UI sections
751
+ releaseInfoDiv . style . display = 'none' ;
752
+ packagesUrlDiv . style . display = 'none' ;
753
+ packageCountDiv . style . display = 'none' ;
754
+ tableContainerDiv . style . display = 'none' ;
755
+
756
+ // Clear packages table
757
+ packagesTable . innerHTML = '' ;
758
+
759
+ // Clear info grid
760
+ infoGridDiv . innerHTML = '' ;
567
761
}
568
762
} ) ;
0 commit comments