Use ProductDownloader component, remove duplicate code (#768)
* Use ProductDownloader component, remove duplicate code * hard-code red-d1 for now * bump ProductDownloader
This commit is contained in:
1 parent
8813952439
commit
b483dd9df0
13 files changed
+93
-906
No files matched your search
@@ -1,136 +0,0 @@
|
||||
import Tabs from '@hashicorp/react-tabs'
|
||||
import {
|
||||
prettyOs,
|
||||
prettyArch,
|
||||
trackDownload,
|
||||
} from 'components/downloader/utils/downloader'
|
||||
import styles from './style.module.css'
|
||||
|
||||
export default function DownloadTabs({
|
||||
defaultTabIdx,
|
||||
tabData,
|
||||
downloads,
|
||||
version,
|
||||
merchandisingSlot,
|
||||
brand,
|
||||
logo,
|
||||
tutorialLink,
|
||||
}) {
|
||||
return (
|
||||
<Tabs
|
||||
key={defaultTabIdx}
|
||||
centered
|
||||
fullWidthBorder
|
||||
theme={brand}
|
||||
className={styles.tabs}
|
||||
defaultTabIdx={defaultTabIdx}
|
||||
items={tabData.map(({ os, packageManagers }) => ({
|
||||
heading: prettyOs(os),
|
||||
tabChildren: function TabChildren() {
|
||||
return (
|
||||
<div className={styles.cards}>
|
||||
<Cards
|
||||
key={os}
|
||||
os={os}
|
||||
downloads={downloads}
|
||||
packageManagers={packageManagers}
|
||||
version={version}
|
||||
theme={brand}
|
||||
logo={logo}
|
||||
tutorialLink={tutorialLink}
|
||||
/>
|
||||
{merchandisingSlot}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Cards({
|
||||
os,
|
||||
downloads,
|
||||
packageManagers,
|
||||
version,
|
||||
theme,
|
||||
logo,
|
||||
tutorialLink,
|
||||
}) {
|
||||
const arches = downloads[os]
|
||||
const hasPackageManager = Boolean(packageManagers)
|
||||
const hasMultiplePackageManagers = Array.isArray(packageManagers)
|
||||
|
||||
function handleArchClick(arch) {
|
||||
return () => trackDownload('boundary', version, os, arch)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
hasMultiplePackageManagers
|
||||
? styles.downloadCardsSingle
|
||||
: styles.downloadCards
|
||||
}
|
||||
>
|
||||
{hasPackageManager && (
|
||||
<div className={styles.packageManagers}>
|
||||
<span className={styles.cardTitle}>Package Manager</span>
|
||||
{Array.isArray(packageManagers) ? (
|
||||
<Tabs
|
||||
theme={theme}
|
||||
items={packageManagers.map(({ label, commands }) => ({
|
||||
heading: label,
|
||||
tabChildren: function TabChildren() {
|
||||
return (
|
||||
<div className={styles.install}>
|
||||
{commands.map((command) => (
|
||||
<pre key={command}>{command}</pre>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.install}>
|
||||
{packageManagers.commands.map((command) => (
|
||||
<pre key={command}>{command}</pre>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{tutorialLink && (
|
||||
<div>
|
||||
<a href={tutorialLink.href}>{tutorialLink.label}</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={hasPackageManager ? styles.card : styles.soloCard}>
|
||||
<span className={styles.cardTitle}>Binary Download</span>
|
||||
<div className={styles.logoDownloadWrapper}>
|
||||
<div className={styles.logoWrapper}>
|
||||
{logo}
|
||||
<span className={styles.version}>{version}</span>
|
||||
</div>
|
||||
{Object.entries(arches).map(([arch, url]) => (
|
||||
<a
|
||||
href={url}
|
||||
key={arch}
|
||||
className={styles.downloadLink}
|
||||
onClick={handleArchClick(arch)}
|
||||
>
|
||||
{prettyArch(arch)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.fastly}>
|
||||
Bandwidth courtesy of
|
||||
<img src={require('../logos/fastly.svg')} alt="Fastly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
.cards {
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.logoWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logoDownloadWrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.version {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.downloadCards {
|
||||
justify-content: center;
|
||||
margin: 54px 0 24px;
|
||||
|
||||
& a {
|
||||
color: var(--gray-2);
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
margin: 64px 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-column-gap: 48px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
& .downloadLink {
|
||||
color: var(--brand);
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.downloadCardsSingle {
|
||||
composes: downloadCards;
|
||||
|
||||
& .card {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
& .card:first-child {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 100%;
|
||||
min-height: 278px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: space-between;
|
||||
padding: 40px 32px;
|
||||
color: var(--gray-2);
|
||||
border: 1px solid var(--gray-6);
|
||||
|
||||
&:first-child {
|
||||
border-bottom: none;
|
||||
|
||||
@media (--medium-up) {
|
||||
border-bottom: 1px solid var(--gray-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.packageManagers {
|
||||
composes: card;
|
||||
|
||||
& a {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
& :global {
|
||||
& .g-tabs {
|
||||
width: 100%;
|
||||
|
||||
@media (--medium-up) {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.soloCard {
|
||||
composes: card;
|
||||
grid-column: span 2;
|
||||
|
||||
&:first-child {
|
||||
border-bottom: 1px solid var(--gray-6);
|
||||
}
|
||||
}
|
||||
|
||||
.install {
|
||||
color: var(--white);
|
||||
background-color: var(--black);
|
||||
padding: 16px 24px;
|
||||
border-radius: 3px;
|
||||
text-align: left;
|
||||
margin: 32px 0;
|
||||
|
||||
& pre {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
white-space: normal;
|
||||
|
||||
&::before {
|
||||
color: #e93471;
|
||||
content: '$';
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font-family: var(--font-display);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gray-4);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fastly {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
& svg path {
|
||||
fill: var(--black);
|
||||
}
|
||||
|
||||
& img {
|
||||
margin-top: 12px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import Button from '@hashicorp/react-button'
|
||||
import styles from './style.module.css'
|
||||
|
||||
export default function Dropdown({ options, onChange, title, brand }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const buttonRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (buttonRef.current.contains(event.target)) return
|
||||
|
||||
setOpen(false)
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.root} ref={buttonRef}>
|
||||
<Button
|
||||
className="trigger"
|
||||
theme={{ brand }}
|
||||
onClick={() => setOpen((open) => !open)}
|
||||
aria-expanded={open}
|
||||
aria-controls="menu-list"
|
||||
aria-haspopup="true"
|
||||
title={title}
|
||||
icon={{
|
||||
position: 'right',
|
||||
svg:
|
||||
'<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 7.5L10 12.5L15 7.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
}}
|
||||
/>
|
||||
{open && (
|
||||
<ul className={styles.menu} id="menu-list" role="menu">
|
||||
{options.map((option) => (
|
||||
<li
|
||||
className={styles.option}
|
||||
tabIndex={0}
|
||||
role="menuitem"
|
||||
key={option.value}
|
||||
onClick={() => {
|
||||
onChange(option.value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
.root {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
top: 35px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.option {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
background-color: var(--brand);
|
||||
color: var(--white);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--brand-d1);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<svg width="156" height="61" viewBox="0 0 156 61" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M106.167 6.237v40.95h12.226V40.93h-4.04V0h-8.187v6.237zM0 40.93h4.156V21.174H0V15.74l4.156-.687v-5.5C4.156 2.891 5.597 0 14.042 0c1.824 0 3.986.271 5.879.614l-1.122 6.697c-1.283-.204-1.918-.24-2.728-.24-2.976 0-3.728.298-3.728 3.222v4.76h6.177v6.12h-6.177V40.93h4.113v6.255L0 47.187V40.93zm102.002-1.977c-1.28.272-2.4.239-3.211.259-3.368.083-3.077-1.03-3.077-4.226V21.174h6.41v-6.121h-6.41V0h-8.187v36.694c0 7.204 1.767 10.492 9.469 10.492 1.824 0 4.33-.472 6.223-.88l-1.217-7.353zm-24.695-17.79v-1.096c-2.477-.455-4.937-.461-6.27-.461-3.811 0-4.276 2.032-4.276 3.134 0 1.56.53 2.402 4.657 3.31 6.033 1.363 12.093 2.784 12.093 10.31 0 7.139-3.652 10.826-11.338 10.826-5.143 0-10.134-1.11-13.941-2.082v-6.151h6.2l-.004 1.092c2.668.519 5.465.467 6.926.467 4.064 0 4.721-2.198 4.721-3.367 0-1.622-1.165-2.4-4.975-3.178-7.177-1.234-12.873-3.699-12.873-11.033 0-6.943 4.616-9.666 12.302-9.666 5.207 0 9.166.812 12.976 1.785v6.11h-6.198zm-41.475 17.77v-.967h-.98v.965c-4.395-.257-7.91-3.81-8.139-8.237h.975v-.987h-.97c.26-4.397 3.761-7.915 8.133-8.171v.97h.981v-.973c4.31.232 7.782 3.633 8.157 7.928v.283h-.983v.987h.983v.27c-.373 4.298-3.846 7.701-8.157 7.933zm16.347.02l-.006-25.632h-8.186v2.404a16.673 16.673 0 00-5.567-2.119h.046v-2.844h1V8.671H31.22v2.091h1v2.845h.056c-7.83 1.45-13.764 8.348-13.764 16.646 0 9.35 7.534 16.93 16.828 16.93a16.674 16.674 0 008.672-2.419l1.474 2.423h8.648v-8.234h-1.957.001zm86.935-23.9H156v6.115h-4.037L141.607 46.8c-2.967 7.2-7.843 13.977-15.266 13.977-1.826 0-4.257-.203-5.942-.61l.74-7.476c1.082.203 2.499.337 3.242.337 3.443 0 7.326-2.146 8.541-5.883L122.43 21.168h-4.036v-6.115h16.895v6.115h-4.036l5.945 14.713 5.944-14.713h-4.027v-6.115zM39.639 26.569l-.623-.627-3.164 2.774a1.494 1.494 0 00-.51-.09c-.853 0-1.544.716-1.544 1.598 0 .883.69 1.598 1.545 1.598.853 0 1.546-.715 1.546-1.598 0-.168-.025-.33-.071-.481l2.82-3.174z"
|
||||
fill="#000"
|
||||
fill-rule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,165 +0,0 @@
|
||||
import { useState, Fragment, useEffect } from 'react'
|
||||
|
||||
import Dropdown from 'components/downloader/dropdown'
|
||||
import {
|
||||
prettyArch,
|
||||
prettyOs,
|
||||
trackDownload,
|
||||
sortPlatforms,
|
||||
getVersionLabel,
|
||||
sortAndFilterReleases,
|
||||
} from 'components/downloader/utils/downloader'
|
||||
import styles from './style.module.css'
|
||||
|
||||
export default function ReleaseInformation({
|
||||
productId,
|
||||
productName,
|
||||
latestVersion,
|
||||
packageManagers,
|
||||
containers,
|
||||
tutorials,
|
||||
changelog,
|
||||
brand,
|
||||
}) {
|
||||
const [selectedVersionId, setSelectedVersionId] = useState(latestVersion)
|
||||
const [releases, setReleases] = useState([])
|
||||
const { version, ...selectedVersion } =
|
||||
releases.find((release) => release.version === selectedVersionId) || {}
|
||||
|
||||
useEffect(() => {
|
||||
// lazy load all release versions to populate dropdown & releases section
|
||||
fetch(`https://releases.hashicorp.com/${productId}/index.json`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const latestReleases = sortAndFilterReleases(Object.keys(data.versions))
|
||||
|
||||
const releases = latestReleases.map((releaseVersion) => ({
|
||||
...sortPlatforms(data.versions[releaseVersion]),
|
||||
version: releaseVersion,
|
||||
}))
|
||||
setReleases(releases)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className="g-container">
|
||||
<h2>Release Information</h2>
|
||||
<div className={styles.grid}>
|
||||
{releases.length > 0 && (
|
||||
<>
|
||||
<div className={styles.releases}>Releases:</div>
|
||||
<div>
|
||||
<Dropdown
|
||||
title={`${productName} ${getVersionLabel(
|
||||
version,
|
||||
latestVersion
|
||||
)}`}
|
||||
brand={brand}
|
||||
options={releases.map((releaseData) => ({
|
||||
label: `${productName} ${getVersionLabel(
|
||||
releaseData.version,
|
||||
latestVersion
|
||||
)}`,
|
||||
value: releaseData.version,
|
||||
}))}
|
||||
onChange={(release) => setSelectedVersionId(release)}
|
||||
/>
|
||||
<a
|
||||
href={
|
||||
changelog ||
|
||||
`https://github.com/hashicorp/${productId}/blob/v${version}/CHANGELOG.md`
|
||||
}
|
||||
className={styles.changelog}
|
||||
>
|
||||
Changelog
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={styles.latestDownloads}>Latest Downloads:</div>
|
||||
<div>
|
||||
Package downloads for {productName} {version}
|
||||
<div className={styles.downloads}>
|
||||
{Object.entries(selectedVersion).map(([os, release]) => (
|
||||
<Fragment key={os}>
|
||||
<div className={styles.os}>{prettyOs(os)}</div>
|
||||
<div>
|
||||
{Object.entries(release).map(([arch, file]) => (
|
||||
<a
|
||||
href={file}
|
||||
key={arch}
|
||||
onClick={() =>
|
||||
trackDownload('boundary', version, os, arch)
|
||||
}
|
||||
>
|
||||
{prettyArch(arch)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<p>
|
||||
You can find the{' '}
|
||||
<a
|
||||
href={`https://releases.hashicorp.com/${productId}/${version}/${productId}_${version}_SHA256SUMS`}
|
||||
>
|
||||
SHA256 checksums for {productName} {version}
|
||||
</a>{' '}
|
||||
online and you can{' '}
|
||||
<a
|
||||
href={`https://releases.hashicorp.com/${productId}/${version}/${productId}_${version}_SHA256SUMS.sig`}
|
||||
>
|
||||
verify the checksums signature file
|
||||
</a>{' '}
|
||||
which has been signed using{' '}
|
||||
<a href="https://hashicorp.com/security">
|
||||
HashiCorp's GPG key.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{packageManagers?.length > 0 && (
|
||||
<>
|
||||
<div className={styles.heading}>Package Managers</div>
|
||||
<div className={styles.links}>
|
||||
{packageManagers.map((packageManager) => (
|
||||
<div key={packageManager.label}>
|
||||
Install with{' '}
|
||||
<a href={packageManager.url}>{packageManager.label}</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{containers?.length > 0 && (
|
||||
<>
|
||||
<div className={styles.heading}>Containers</div>
|
||||
<div className={styles.links}>
|
||||
{containers.map((container) => (
|
||||
<div key={container.label}>
|
||||
Run with <a href={container.url}>{container.label}</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{containers?.length > 0 && (
|
||||
<>
|
||||
<div className={styles.heading}>Tutorials</div>
|
||||
<div className={styles.links}>
|
||||
{tutorials.map((tutorial) => (
|
||||
<div key={tutorial.label}>
|
||||
<a href={tutorial.url}>{tutorial.label}</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
.root {
|
||||
background-color: var(--gray-7);
|
||||
padding: 42px 0;
|
||||
margin-top: 42px;
|
||||
color: var(--gray-2);
|
||||
|
||||
& p {
|
||||
color: var(--gray-2);
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--gray-2);
|
||||
}
|
||||
|
||||
& :global(.g-container) {
|
||||
max-width: 825px;
|
||||
}
|
||||
|
||||
& h2 {
|
||||
text-align: center;
|
||||
color: var(--black);
|
||||
margin-bottom: 54px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 24px 0;
|
||||
font-weight: bold;
|
||||
color: var(--black);
|
||||
|
||||
@media (--medium-up) {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.changelog {
|
||||
display: block;
|
||||
margin: 24px 0 0;
|
||||
color: var(--black);
|
||||
|
||||
@media (--medium-up) {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
margin: 48px auto;
|
||||
|
||||
@media (--medium-up) {
|
||||
display: grid;
|
||||
grid-column-gap: 32px;
|
||||
grid-row-gap: 48px;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.latestDownloads {
|
||||
composes: heading;
|
||||
margin-top: 40px;
|
||||
|
||||
@media (--medium-up) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.downloads {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: auto 1fr;
|
||||
margin: 17px 0 40px;
|
||||
grid-column-gap: 28px;
|
||||
grid-row-gap: 16px;
|
||||
|
||||
& a {
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
& .os {
|
||||
font-weight: bold;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
margin: 20px 0 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.releases {
|
||||
align-self: center;
|
||||
composes: heading;
|
||||
}
|
||||
|
||||
.links > div:not(:first-child) {
|
||||
margin: 15px 0;
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import semverRSort from 'semver/functions/rsort'
|
||||
import semverPrerelease from 'semver/functions/prerelease'
|
||||
import semverValid from 'semver/functions/valid'
|
||||
|
||||
export function getVersionLabel(version, latestVersion) {
|
||||
if (version === latestVersion) {
|
||||
return `${version} (latest)`
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
export function sortAndFilterReleases(releases) {
|
||||
const validReleases = releases.filter(semverValid)
|
||||
// descending sort on releases, while filtering out pre-releases
|
||||
return semverRSort(validReleases).filter(
|
||||
(version) => !semverPrerelease(version)
|
||||
)
|
||||
}
|
||||
|
||||
/** TODO: Below utils directly from Product-Downloader component.
|
||||
* Should either be exported, or migrated back to web-components */
|
||||
|
||||
export function prettyArch(arch) {
|
||||
switch (arch) {
|
||||
case 'all':
|
||||
return 'Universal (32 and 64-bit)'
|
||||
case 'i686':
|
||||
case 'i386':
|
||||
case '686':
|
||||
case '386':
|
||||
return '32-bit'
|
||||
case 'x86_64':
|
||||
case '86_64':
|
||||
case 'amd64':
|
||||
return '64-bit'
|
||||
default:
|
||||
if (/-/.test(arch)) {
|
||||
const parts = arch.split(/-(.+)/)
|
||||
return `${prettyArch(parts[0])} (${parts[1]})`
|
||||
} else {
|
||||
const parts = arch.split('_')
|
||||
if (parts.length > 0) {
|
||||
return (
|
||||
parts[parts.length - 1].charAt(0).toUpperCase() +
|
||||
parts[parts.length - 1].slice(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function detectOs(platform) {
|
||||
for (let key in platformMap) {
|
||||
if (platform.indexOf(key) !== -1) {
|
||||
return platformMap[key]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function prettyOs(os) {
|
||||
switch (os) {
|
||||
case 'darwin':
|
||||
return 'Mac OS X'
|
||||
case 'freebsd':
|
||||
return 'FreeBSD'
|
||||
case 'openbsd':
|
||||
return 'OpenBSD'
|
||||
case 'netbsd':
|
||||
return 'NetBSD'
|
||||
case 'archlinux':
|
||||
return 'Arch Linux'
|
||||
case 'linux':
|
||||
return 'Linux'
|
||||
case 'windows':
|
||||
return 'Windows'
|
||||
default:
|
||||
return os.charAt(0).toUpperCase() + os.slice(1)
|
||||
}
|
||||
}
|
||||
|
||||
const platformMap = {
|
||||
Mac: 'darwin',
|
||||
Win: 'windows',
|
||||
Linux: 'linux',
|
||||
}
|
||||
|
||||
export function sortPlatforms(releaseData) {
|
||||
// first we pull the platforms out of the release data object and format it the way we want
|
||||
const platforms = releaseData.builds.reduce((acc, build) => {
|
||||
if (!acc[build.os]) acc[build.os] = {}
|
||||
acc[build.os][build.arch] = build.url
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const platformKeys = Object.keys(platforms)
|
||||
|
||||
// create array of sorted values to base the order on
|
||||
const sortedValues = Object.keys(platformMap)
|
||||
.map((e) => platformMap[e])
|
||||
// join the lists together to make sure
|
||||
// all items are accounted for when sorting
|
||||
.concat(platformKeys)
|
||||
// filter our any duplicates and unneeded items
|
||||
.filter((elem, pos, arr) => {
|
||||
return arr.indexOf(elem) == pos && platformKeys.indexOf(elem) > -1
|
||||
})
|
||||
|
||||
return (
|
||||
platformKeys
|
||||
// sort items based on platformMap order
|
||||
.sort((a, b) => {
|
||||
return sortedValues.indexOf(a) - sortedValues.indexOf(b)
|
||||
})
|
||||
// create new sorted object to return
|
||||
.reduce((result, key) => {
|
||||
result[key] = platforms[key]
|
||||
return result
|
||||
}, {})
|
||||
)
|
||||
}
|
||||
|
||||
export function trackDownload(product, version, _os, _arch) {
|
||||
const os = prettyOs(_os)
|
||||
const arch = prettyArch(_arch)
|
||||
|
||||
if (window.analytics) {
|
||||
window.analytics.track('Download', {
|
||||
category: 'Button',
|
||||
label: `${product} | v${version} | ${os} | ${arch}`,
|
||||
version,
|
||||
os,
|
||||
architecture: arch,
|
||||
product,
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
+6
-4
@@ -1728,11 +1728,13 @@
|
||||
}
|
||||
},
|
||||
"@hashicorp/react-product-downloader": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-product-downloader/-/react-product-downloader-4.1.5.tgz",
|
||||
"integrity": "sha512-ytmQi0ZfGmhSboRujDtXJGIhJE13ZSd0MsEpyAEoiWwZqv190qSXnYgl3HcT7iM4nENJEmPcHA4MnvCcKQaODA==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-product-downloader/-/react-product-downloader-6.1.1.tgz",
|
||||
"integrity": "sha512-jvWQiMmrJkkAAhNa13fL9ogaMDD4WhLR319ozlKySVT7g8a03vwum0GG8FyIFf0LyKXBQJzSsuXpSX4vC6eKzQ==",
|
||||
"requires": {
|
||||
"@hashicorp/react-button": "^2.2.8"
|
||||
"@hashicorp/react-button": "^2.2.8",
|
||||
"@hashicorp/react-tabs": "^1.0.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hashicorp/react-button": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@hashicorp/react-head": "1.1.4",
|
||||
"@hashicorp/react-hero": "^3.1.9",
|
||||
"@hashicorp/react-image": "3.0.1",
|
||||
"@hashicorp/react-product-downloader": "4.1.5",
|
||||
"@hashicorp/react-product-downloader": "^6.1.1",
|
||||
"@hashicorp/react-product-features-list": "^2.1.2",
|
||||
"@hashicorp/react-section-header": "3.0.0",
|
||||
"@hashicorp/react-subnav": "5.0.0",
|
||||
|
||||
@@ -1,84 +1,114 @@
|
||||
import { useMemo, useState, useEffect } from 'react'
|
||||
import VERSION from 'data/version.js'
|
||||
import Head from 'next/head'
|
||||
import HashiHead from '@hashicorp/react-head'
|
||||
|
||||
import { productName, productSlug } from 'data/metadata'
|
||||
import { packageManagersByOs, getStartedLinks } from 'data/downloads'
|
||||
import ReleaseInformation from 'components/downloader/release-information'
|
||||
import { sortPlatforms, detectOs } from 'components/downloader/utils/downloader'
|
||||
import DownloadCards from 'components/downloader/cards'
|
||||
import ProductDownloader from '@hashicorp/react-product-downloader'
|
||||
import styles from './style.module.css'
|
||||
|
||||
export default function DownloadsPage({ releaseData, previousVersions }) {
|
||||
const sortedDownloads = useMemo(() => sortPlatforms(releaseData), [
|
||||
releaseData,
|
||||
])
|
||||
const osKeys = Object.keys(sortedDownloads)
|
||||
const [osIndex, setSelectedOsIndex] = useState()
|
||||
|
||||
const tabData = Object.keys(sortedDownloads).map((osKey) => ({
|
||||
os: osKey,
|
||||
packageManagers: packageManagersByOs[osKey] || null,
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
// if we're on the client side, detect the default platform only on initial render
|
||||
const index = osKeys.indexOf(detectOs(window.navigator.platform))
|
||||
setSelectedOsIndex(index)
|
||||
}, [])
|
||||
|
||||
export default function DownloadsPage({ releases }) {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<h1>Download {productName}</h1>
|
||||
<HashiHead is={Head} title={`Downloads | ${productName} by HashiCorp`} />
|
||||
<DownloadCards
|
||||
brand="red"
|
||||
defaultTabIdx={osIndex}
|
||||
tabData={tabData}
|
||||
downloads={sortedDownloads}
|
||||
version={VERSION}
|
||||
<ProductDownloader
|
||||
releases={releases}
|
||||
packageManagers={[
|
||||
{
|
||||
label: 'Homebrew',
|
||||
commands: [
|
||||
'brew tap hashicorp/tap',
|
||||
'brew install hashicorp/tap/boundary',
|
||||
],
|
||||
os: 'darwin',
|
||||
},
|
||||
{
|
||||
label: 'Ubuntu/Debian',
|
||||
commands: [
|
||||
'curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -',
|
||||
'sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"',
|
||||
'sudo apt-get update && sudo apt-get install boundary',
|
||||
],
|
||||
os: 'linux',
|
||||
},
|
||||
{
|
||||
label: 'CentOS/RHEL',
|
||||
commands: [
|
||||
'sudo yum install -y yum-utils',
|
||||
'sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo',
|
||||
'sudo yum -y install boundary',
|
||||
],
|
||||
os: 'linux',
|
||||
},
|
||||
{
|
||||
label: 'Fedora',
|
||||
commands: [
|
||||
'sudo dnf install -y dnf-plugins-core',
|
||||
'sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/fedora/hashicorp.repo',
|
||||
'sudo dnf -y install boundary',
|
||||
],
|
||||
os: 'linux',
|
||||
},
|
||||
{
|
||||
label: 'Amazon Linux',
|
||||
commands: [
|
||||
'sudo yum install -y yum-utils',
|
||||
'sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo',
|
||||
'sudo yum -y install boundary',
|
||||
],
|
||||
os: 'linux',
|
||||
},
|
||||
]}
|
||||
productName={productName}
|
||||
productId={productSlug}
|
||||
latestVersion={VERSION}
|
||||
getStartedLinks={[
|
||||
{
|
||||
label: 'Install Boundary',
|
||||
href:
|
||||
'https://learn.hashicorp.com/tutorials/boundary/getting-started-install',
|
||||
},
|
||||
{
|
||||
label: 'Introduction to Boundary',
|
||||
href:
|
||||
'https://learn.hashicorp.com/tutorials/boundary/getting-started-intro',
|
||||
},
|
||||
{
|
||||
label: 'Start a Development Environment',
|
||||
href:
|
||||
'https://learn.hashicorp.com/tutorials/boundary/getting-started-dev',
|
||||
},
|
||||
]}
|
||||
logo={
|
||||
<img
|
||||
className={styles.logo}
|
||||
alt="Boundary"
|
||||
src={require('./img/boundary-logo.svg')}
|
||||
/>
|
||||
}
|
||||
brand="red"
|
||||
tutorialLink={{
|
||||
label: 'View Tutorial on HashiCorp Learn',
|
||||
href:
|
||||
'https://learn.hashicorp.com/tutorials/boundary/getting-started-install',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="g-container">
|
||||
<div className={styles.gettingStarted}>
|
||||
<h2>Getting Started</h2>
|
||||
<div className={styles.links}>
|
||||
{getStartedLinks.map((link) => (
|
||||
<a href={link.href} key={link.href}>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ReleaseInformation
|
||||
brand="red"
|
||||
productId="boundary"
|
||||
productName={productName}
|
||||
releases={previousVersions}
|
||||
latestVersion={releaseData.version}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return fetch(`https://releases.hashicorp.com/boundary/${VERSION}/index.json`)
|
||||
.then((r) => r.json())
|
||||
.then((releaseData) => ({ props: { releaseData } }))
|
||||
return fetch(`https://releases.hashicorp.com/boundary/index.json`, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((result) => {
|
||||
return {
|
||||
props: {
|
||||
releases: result,
|
||||
},
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(
|
||||
`--------------------------------------------------------
|
||||
|
||||
@@ -1,61 +1,7 @@
|
||||
.root {
|
||||
margin-top: 72px;
|
||||
|
||||
& a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
& h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
--brand: var(--red);
|
||||
--brand-d1: #c03e43;
|
||||
--red-d1: #c03e43;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.hosting {
|
||||
border: 1px solid var(--gray-6);
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--gray-2);
|
||||
}
|
||||
|
||||
.gettingStarted {
|
||||
max-width: 680px;
|
||||
margin: 40px auto 64px;
|
||||
text-align: center;
|
||||
|
||||
& p {
|
||||
color: var(--gray-3);
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
& h2 {
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
margin: 88px auto 128px;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-top: 32px;
|
||||
& a {
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.signUpButton {
|
||||
margin-left: 26px;
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
@import '~@hashicorp/react-consent-manager/dist/style.css';
|
||||
@import '~@hashicorp/react-toggle/dist/style.css';
|
||||
@import '~@hashicorp/react-section-header/style.css';
|
||||
@import '~@hashicorp/react-product-downloader/dist/style.css';
|
||||
@import '~@hashicorp/react-vertical-text-block-list/style.css';
|
||||
@import '~@hashicorp/react-docs-sidenav/style.css';
|
||||
@import '~@hashicorp/react-content/style.css';
|
||||
|
||||
Reference in new issue
Block a user