Skip to content

Commit af78739

Browse files
committed
Add Dist selection + state reset on title click
1 parent 9e91529 commit af78739

File tree

3 files changed

+215
-14
lines changed

3 files changed

+215
-14
lines changed

static/script.js

Lines changed: 207 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ document.addEventListener('DOMContentLoaded', () => {
33
// DOM Elements
44
const repoUrlInput = document.getElementById('repoUrl');
55
const loadButton = document.getElementById('loadButton');
6+
const appTitle = document.querySelector('h1');
67
const releaseInfoDiv = document.getElementById('releaseInfo');
78
const infoGridDiv = document.getElementById('infoGrid');
89
const archSelect = document.getElementById('archSelect');
910
const componentSelect = document.getElementById('componentSelect');
11+
const distSelect = document.getElementById('distSelect');
1012
const packagesUrlDiv = document.getElementById('packagesUrl');
1113
const errorMessageDiv = document.getElementById('errorMessage');
1214
const packageCountDiv = document.getElementById('packageCount');
@@ -30,6 +32,8 @@ document.addEventListener('DOMContentLoaded', () => {
3032
let sortField = 'name';
3133
let sortDirection = 'asc';
3234
let repoBaseUrl = '';
35+
let availableDists = [];
36+
let defaultDist = 'stable';
3337

3438
// Proxy URL
3539
const PROXY_URL = '/proxy?url=';
@@ -42,9 +46,20 @@ document.addEventListener('DOMContentLoaded', () => {
4246
});
4347

4448
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');
4558

4659
archSelect.addEventListener('change', updatePackagesUrl);
4760
componentSelect.addEventListener('change', updatePackagesUrl);
61+
distSelect.addEventListener('change', handleDistChange);
62+
distSelect.addEventListener('change', updatePackagesUrl);
4863

4964
searchQueryInput.addEventListener('input', (e) => {
5065
searchQuery = e.target.value.toLowerCase();
@@ -93,14 +108,18 @@ document.addEventListener('DOMContentLoaded', () => {
93108

94109
// Functions
95110
async function fetchRelease() {
96-
const repoUrl = repoUrlInput.value.trim();
111+
let repoUrl = repoUrlInput.value.trim();
97112

113+
// Use default URL if input is empty
98114
if (!repoUrl) {
99-
showError('Please enter a repository URL');
100-
return;
115+
repoUrl = 'https://apt.armbian.com';
116+
repoUrlInput.value = repoUrl;
101117
}
102118

103119
try {
120+
// Reset all state and selections before loading a new repository
121+
resetState();
122+
104123
showLoading(true);
105124
clearError();
106125
hidePackagesInfo();
@@ -113,6 +132,15 @@ document.addEventListener('DOMContentLoaded', () => {
113132
repoBaseUrl = cleanBaseUrl;
114133
}
115134

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+
116144
// Build the Release file URL - handle different URL patterns
117145
let releaseUrl;
118146
if (cleanBaseUrl.includes('/dists/')) {
@@ -127,10 +155,10 @@ document.addEventListener('DOMContentLoaded', () => {
127155
releaseUrl = `${cleanBaseUrl}/Release`;
128156
} else {
129157
// URL just ends with dists/
130-
releaseUrl = `${basePart}/dists/stable/Release`;
158+
releaseUrl = `${basePart}/dists/${initialDist}/Release`;
131159
}
132160
} else {
133-
releaseUrl = `${cleanBaseUrl}/dists/stable/Release`;
161+
releaseUrl = `${cleanBaseUrl}/dists/${initialDist}/Release`;
134162
}
135163

136164
// Fetch the Release file through the proxy
@@ -144,6 +172,23 @@ document.addEventListener('DOMContentLoaded', () => {
144172
// Parse the Release file content
145173
releaseInfo = parseReleaseFile(releaseContent);
146174

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+
147192
// Show release info
148193
showReleaseInfo();
149194
} catch (error) {
@@ -186,8 +231,9 @@ document.addEventListener('DOMContentLoaded', () => {
186231
function updatePackagesUrl() {
187232
const arch = archSelect.value;
188233
const component = componentSelect.value;
234+
const dist = distSelect.value;
189235

190-
if (!arch || !component || !releaseInfo || !releaseInfo.Codename) {
236+
if (!arch || !component || !dist || !releaseInfo) {
191237
packagesUrl = '';
192238
packagesUrlDiv.style.display = 'none';
193239
return;
@@ -196,7 +242,7 @@ document.addEventListener('DOMContentLoaded', () => {
196242
// Build the Packages URL
197243
packagesUrl = buildPackagesUrl(
198244
repoUrlInput.value.trim(),
199-
releaseInfo.Codename,
245+
dist,
200246
component,
201247
arch
202248
);
@@ -209,7 +255,98 @@ document.addEventListener('DOMContentLoaded', () => {
209255
fetchPackages();
210256
}
211257

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[^>]*href="([^"\/]+)\/"[^>]*>/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() {
213350
if (!releaseInfo) return;
214351

215352
// Clear the info grid
@@ -230,6 +367,13 @@ document.addEventListener('DOMContentLoaded', () => {
230367
infoGridDiv.appendChild(div);
231368
}
232369
});
370+
}
371+
372+
function showReleaseInfo() {
373+
if (!releaseInfo) return;
374+
375+
// Update info grid
376+
updateReleaseInfoGrid();
233377

234378
// Populate architecture dropdown
235379
archSelect.innerHTML = '<option value="">Select Architecture</option>';
@@ -252,6 +396,20 @@ document.addEventListener('DOMContentLoaded', () => {
252396
componentSelect.appendChild(option);
253397
});
254398
}
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+
});
255413

256414
// Show the release info section
257415
releaseInfoDiv.style.display = 'block';
@@ -466,9 +624,15 @@ document.addEventListener('DOMContentLoaded', () => {
466624
}
467625

468626
function hidePackagesInfo() {
627+
// Hide UI elements
469628
packagesUrlDiv.style.display = 'none';
470629
packageCountDiv.style.display = 'none';
471630
tableContainerDiv.style.display = 'none';
631+
632+
// Clear packages data
633+
packages = [];
634+
packagesTable.innerHTML = '';
635+
packagesUrl = '';
472636
}
473637

474638
// Helper functions for parsing
@@ -550,19 +714,49 @@ document.addEventListener('DOMContentLoaded', () => {
550714
return packages;
551715
}
552716

553-
function buildPackagesUrl(baseUrl, codename, component, arch) {
717+
function buildPackagesUrl(baseUrl, dist, component, arch) {
554718
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
555719
// Check if the base URL already includes the dists directory
556720
if (cleanBaseUrl.includes('/dists/')) {
557721
// Extract the base part before /dists/
558722
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}`)) {
561725
return `${cleanBaseUrl}/${component}/binary-${arch}/Packages`;
562726
} else {
563-
return `${basePart}/dists/${codename}/${component}/binary-${arch}/Packages`;
727+
return `${basePart}/dists/${dist}/${component}/binary-${arch}/Packages`;
564728
}
565729
}
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 = '';
567761
}
568762
});

static/style.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ h1 {
1717
margin-bottom: 20px;
1818
}
1919

20+
.clickable-title {
21+
cursor: pointer;
22+
}
23+
2024
.search-box {
2125
background: white;
2226
padding: 20px;

templates/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ <h1>WebAPT</h1>
1515
<input
1616
id="repoUrl"
1717
type="text"
18-
placeholder="Enter APT repository base URL (e.g., https://apt.armbian.com/dists/noble)"
18+
placeholder="Enter APT repository base URL (e.g., https://apt.armbian.com/)"
1919
>
2020
<button id="loadButton">Load Repository</button>
2121
</div>
@@ -25,6 +25,9 @@ <h2>Repository Information</h2>
2525
<div class="info-grid" id="infoGrid"></div>
2626

2727
<div class="selectors">
28+
<select id="distSelect">
29+
<option value="">Select Distribution</option>
30+
</select>
2831
<select id="archSelect">
2932
<option value="">Select Architecture</option>
3033
</select>

0 commit comments

Comments
 (0)