diff --git a/components/DownloadsComponent.tsx b/components/DownloadsComponent.tsx new file mode 100644 index 0000000..657954c --- /dev/null +++ b/components/DownloadsComponent.tsx @@ -0,0 +1,146 @@ +"use client" +import React, { useState, useEffect, useMemo } from 'react'; +import { Box, CircularProgress } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import fetchFilteredReleases, { FilteredRelease } from "../utils/fetchReleases"; // Update this import based on actual file structure +import {OperatingSystems, Architectures, Version} from './Filters'; // Ensure these imports are correctly structured +import ReleasesTable from './ReleasesTable'; +import data from '../public/static/releases-table-data.json'; + +const DownloadsComponent: React.FC = () => { + const [originalData, setOriginalData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [arch, setArch] = React.useState('x86_64'); + const [os, setOs] = React.useState('linux'); + const [version, setVersion] = React.useState('v7.5'); + + const theme = useTheme(); + + const handleArch = ( + event: React.MouseEvent, + newArch: string | null, + ) => { + if (newArch !== arch) { + setArch(newArch || ''); + } else { + setArch(''); + } + }; + + const handleOs = ( + event: React.MouseEvent, + newOs: string | null, + ) => { + if (newOs !== os) { + setOs(newOs || ''); + } else { + setOs(''); + } + }; + const handleVersion = ( + event: React.MouseEvent, + newVersion: string | null, + ) => { + if (newVersion) { + setVersion(newVersion); + } + } + + useEffect(() => { + const fetchAssets = async () => { + setLoading(true); + try { + const releases = await fetchFilteredReleases(); // This function should return an array of FilteredRelease + setOriginalData(releases); + console.log(releases); + } catch (e) { + setError('Failed to fetch release assets'); + console.error(e); + } finally { + setLoading(false); + } + }; + fetchAssets(); + }, []); + + const uniqueVersions = useMemo(() => { + const unique = new Set(); + originalData.forEach(release => { + unique.add(release.version.split('.').slice(0, 2).join('.')); // Only major.minor + }); + return Array.from(unique); + }, [originalData]); + + const filteredData = useMemo(() => { + // Filter releases based on the selected version + const filteredReleases = originalData.filter(release => + version ? release.version.startsWith(version) : true + ); + + // Now, filter assets within those releases based on the selected OS and Arch + const releasesWithFilteredAssets = filteredReleases.map(release => { + const filteredAssets = release.assets.filter(asset => { + const osMatch = !os || asset.operatingSystem.toLowerCase().includes(os.toLowerCase()); + const archMatch = !arch || asset.architecture.includes(arch) || + (arch === 'PPC64' && (asset.architecture.includes('ppc64el') || asset.architecture.includes('ppc64le'))); + + return osMatch && archMatch; + }); + + // Return the release with the filtered assets + return { ...release, assets: filteredAssets }; + }).filter(release => release.assets.length > 0); // Keep only releases with matching assets + + return releasesWithFilteredAssets; + }, [arch, os, version, originalData]); + + + const renderContent = () => { + if (loading) { + return ; + } else if (error) { + return

{error}

; + } else { + return ( + <> + + + + + + {filteredData.map(release => ( + // Assuming data.table_headers is correct + ))} + + ); + } + }; + + return ( + + {renderContent()} + + ); +}; + +export default DownloadsComponent; diff --git a/components/Filters.tsx b/components/Filters.tsx new file mode 100644 index 0000000..ab70367 --- /dev/null +++ b/components/Filters.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Box, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; + +interface VersionProps { + handle: (event: React.MouseEvent, newAlignment: string) => void; + defaultVersion: string; + data: Array; +} + +interface ArchitecturesProps { + handle: (event: React.MouseEvent, newAlignment: string) => void; + defaultArch: string; + data: Array; +} + +interface OperatingSystemsProps { + handle: (event: React.MouseEvent, newAlignment: string) => void; + defaultOs: string; + data: Array; +} + +export const Architectures:React.FC = ({handle, defaultArch, data}) => { + return ( + + + Architectures + + + {data.map((arch) => ( + {arch} + ))} + + + ) +} + + +export const OperatingSystems:React.FC = ({handle, defaultOs, data}) => { + return ( + + + Operating Systems + + + {data.map((operating_systems) => ( + {operating_systems} + ))} + + + ) +} + +export const Version:React.FC = ({handle, defaultVersion, data}) => { + return ( + + + Versions + + + {data.map((version) => ( + {version} + ))} + + + + ) +} diff --git a/components/ReleasesTable.tsx b/components/ReleasesTable.tsx new file mode 100644 index 0000000..98fe7bc --- /dev/null +++ b/components/ReleasesTable.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { + Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Link} + from '@mui/material'; +import { FilteredRelease } from '../utils/fetchReleases'; + + + +interface ReleasesTableProps { + data: Array; + release: FilteredRelease; +} + + const ReleasesTable: React.FC = ({ release , data }) => { + return( + + + + + {data.map((tableRows) => ( + {tableRows} + ))} + + + + {release.assets.map((asset, index) => ( + + {release.version} + {asset.operatingSystem} + {asset.architecture} + + {asset.name} + + + ))} + +
+
+ ); +} + +export default ReleasesTable; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 939e44f..d3b5c21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1090,6 +1090,11 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/unist": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.9.tgz", @@ -1282,6 +1287,14 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001550", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001550.tgz", @@ -1518,6 +1531,24 @@ "which": "^1.2.9" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -4977,6 +5008,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -5118,6 +5154,22 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-loader-spinner": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz", + "integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==", + "dependencies": { + "react-is": "^18.2.0", + "styled-components": "^6.1.2" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -5570,6 +5622,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/sharp": { "version": "0.32.6", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", @@ -5840,6 +5897,48 @@ "inline-style-parser": "0.1.1" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", diff --git a/public/static/releases-table-data.json b/public/static/releases-table-data.json new file mode 100644 index 0000000..4b52f0c --- /dev/null +++ b/public/static/releases-table-data.json @@ -0,0 +1,20 @@ +{ + "architectures": [ + "arm64", + "amd64", + "ppc64", + "x86_64", + "aarch64" + ], + "operating_systems": [ + "linux", + "darwin", + "windows" + ], + "table_rows": [ + "Version", + "Operating System", + "Architecture", + "File" + ] +} diff --git a/utils/fetchReleases.tsx b/utils/fetchReleases.tsx new file mode 100644 index 0000000..d15d44a --- /dev/null +++ b/utils/fetchReleases.tsx @@ -0,0 +1,66 @@ +export interface Asset { + name: string; + browser_download_url: string; + id: number; +} + +export interface Release { + assets: Asset[]; + tag_name: string; // To determine the version of the release. + name: string; +} + +export type FilteredRelease = { + version: string; + assets: FilteredAsset[]; +} + +export type FilteredAsset = { + name: string; + downloadUrl: string; + id: number; + assetVersion: string; // Version of the release this asset belongs to + operatingSystem: string; + architecture: string; +} + + +const operatingSystems = ['Darwin', 'Linux', 'Windows']; +const architectures = ['arm64', 'amd64', 'ppc64le', 'ppc64el', 'x86_64', 'aarch64']; + +async function fetchFilteredReleases(): Promise { + const response = await fetch('https://api.github.com/repos/PelicanPlatform/pelican/releases'); + const releases: Release[] = await response.json(); + + // Sort releases by version using semver sort (consider using a library for robust sorting) + const sortedReleases = releases.sort((a, b) => b.tag_name.localeCompare(a.tag_name, undefined, {numeric: true, sensitivity: 'base'})); + + // Extract major versions and find the latest minor for each + let majorVersions = new Set(); + let filteredReleases: FilteredRelease[] = []; + + for (const release of sortedReleases) { + const [major, minor] = release.tag_name.replace('v', '').split('.').map(Number); + const majorVersion = `${major}.${minor}`; + + if (majorVersions.size < 4 && !majorVersions.has(majorVersion)) { + majorVersions.add(majorVersion); + + filteredReleases.push({ + version: release.tag_name, + assets: release.assets.map(asset => ({ + name: asset.name, + downloadUrl: asset.browser_download_url, + id: asset.id, + assetVersion: release.tag_name, + operatingSystem: asset.name.includes('checksums.txt') ? '' : operatingSystems.find(os => asset.name.includes(os)) || 'Linux', + architecture: asset.name.includes('checksums.txt') ? '' : architectures.find(arch => asset.name.includes(arch)) || 'unknown', + })) + }); + } + } + + return filteredReleases; +} + +export default fetchFilteredReleases;