website: website source code / content split (#1838)
@ -1,10 +0,0 @@
|
||||
docker:
|
||||
- image: docker.mirror.hashicorp.services/node:12
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Push content to Algolia Index
|
||||
command: |
|
||||
cd website/
|
||||
npm install
|
||||
node scripts/index_search_content.js
|
||||
@ -0,0 +1,34 @@
|
||||
.DEFAULT_GOAL := website
|
||||
|
||||
PWD=$$(pwd)
|
||||
DOCKER_IMAGE="hashicorp/dev-portal"
|
||||
DOCKER_IMAGE_LOCAL="dev-portal-local"
|
||||
DOCKER_RUN_FLAGS=-it \
|
||||
--publish "3000:3000" \
|
||||
--rm \
|
||||
--tty \
|
||||
--volume "$(PWD)/content:/app/content" \
|
||||
--volume "$(PWD)/public:/app/public" \
|
||||
--volume "$(PWD)/data:/app/data" \
|
||||
--volume "$(PWD)/redirects.js:/app/redirects.js" \
|
||||
--volume "next-dir:/app/website-preview/.next" \
|
||||
--volume "$(PWD)/.env:/app/.env" \
|
||||
-e "REPO=boundary"
|
||||
|
||||
.PHONY: website
|
||||
website:
|
||||
@echo "==> Downloading latest Docker image..."
|
||||
@docker pull $(DOCKER_IMAGE)
|
||||
@echo "==> Starting website..."
|
||||
@docker run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE)
|
||||
|
||||
.PHONY: website/local
|
||||
website/local:
|
||||
@echo "==> Starting website from local image..."
|
||||
@docker run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE_LOCAL)
|
||||
|
||||
.PHONY: website/build-local
|
||||
website/build-local:
|
||||
@echo "==> Building local Docker image"
|
||||
@docker build https://github.com/hashicorp/dev-portal.git\#main \
|
||||
-t $(DOCKER_IMAGE_LOCAL)
|
||||
@ -1,62 +0,0 @@
|
||||
.brandedCta {
|
||||
padding: 88px 0;
|
||||
background-image: url('/img/cta-bg.svg');
|
||||
background-color: var(--boundary-secondary);
|
||||
background-position: bottom right -350px;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@media (--medium-up) {
|
||||
background-position: bottom right -250px;
|
||||
}
|
||||
|
||||
@media (--large) {
|
||||
background-position: bottom right -150px;
|
||||
}
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
@media (width >= 992px) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (width < 992px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--black);
|
||||
margin-top: 0;
|
||||
|
||||
@media (width >= 992px) {
|
||||
flex-basis: 33.3%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 647px;
|
||||
margin: 0;
|
||||
color: var(--gray-2);
|
||||
}
|
||||
|
||||
.content-and-links {
|
||||
@media (width >= 992px) {
|
||||
flex-basis: 66.6%;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
& .g-type-body-large {
|
||||
max-width: 35em;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-top: 40px;
|
||||
margin-bottom: -16px;
|
||||
|
||||
& a {
|
||||
margin-right: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import styles from './branded-cta.module.css'
|
||||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function BrandedCta({ heading, content, links }) {
|
||||
return (
|
||||
<div className={styles.brandedCta}>
|
||||
<div className={`g-grid-container ${styles.contentContainer}`}>
|
||||
<h2
|
||||
data-testid="heading"
|
||||
className={`g-type-display-2 ${styles.heading}`}
|
||||
>
|
||||
{heading}
|
||||
</h2>
|
||||
<div className="content-and-links">
|
||||
<p
|
||||
data-testid="content"
|
||||
className={`g-type-body-large ${styles.content}`}
|
||||
>
|
||||
{content}
|
||||
</p>
|
||||
<div data-testid="links" className={styles.links}>
|
||||
{links.map((link, stableIdx) => {
|
||||
const buttonVariant = stableIdx === 0 ? 'primary' : 'secondary'
|
||||
const linkType = link.type || ''
|
||||
return (
|
||||
<Button
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={stableIdx}
|
||||
linkType={linkType}
|
||||
theme={{
|
||||
variant: buttonVariant,
|
||||
brand: 'boundary',
|
||||
background: 'light',
|
||||
}}
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
icon={link.icon}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Footer({ openConsentManager }) {
|
||||
return (
|
||||
<footer className="g-footer">
|
||||
<div className="g-grid-container">
|
||||
<div className="left">
|
||||
<Link href="/docs">
|
||||
<a>Docs</a>
|
||||
</Link>
|
||||
<a href="https://learn.hashicorp.com/boundary">Learn</a>
|
||||
<a href="https://hashicorp.com/privacy">Privacy</a>
|
||||
<Link href="/security">
|
||||
<a>Security</a>
|
||||
</Link>
|
||||
<a href="/files/press-kit.zip">Press Kit</a>
|
||||
<a onClick={openConsentManager}>Consent Manager</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
.g-footer {
|
||||
padding: 25px 0 17px 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
|
||||
& .g-grid-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: black;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.25s ease;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& .left > a {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
.homepageHero {
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--boundary-secondary);
|
||||
background-image: url(/img/hero-pattern.svg);
|
||||
background-position: bottom -50px right -100px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
padding: 88px 0;
|
||||
background-position: bottom -130px right -130px;
|
||||
}
|
||||
|
||||
& .hero {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
import Hero from '@hashicorp/react-hero'
|
||||
import styles from './HomepageHero.module.css'
|
||||
|
||||
/**
|
||||
* A simple Facade around our react-hero to make the interface a little more straightforward
|
||||
* for the end-user updating this on our Homepage, while also allowing us to shim in some
|
||||
* additional styles and encapsulate that logic.
|
||||
*/
|
||||
export default function HomepageHero({
|
||||
title,
|
||||
description,
|
||||
links,
|
||||
uiVideo,
|
||||
cliVideo,
|
||||
desktopVideo,
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.homepageHero}>
|
||||
<Hero
|
||||
videoControlsTop
|
||||
className={styles.hero}
|
||||
data={{
|
||||
product: 'boundary',
|
||||
title: title,
|
||||
description: description,
|
||||
buttons: links,
|
||||
backgroundTheme: 'light',
|
||||
centered: false,
|
||||
videos: [
|
||||
{
|
||||
name: 'UI',
|
||||
playbackRate: uiVideo.playbackRate,
|
||||
aspectRatio: uiVideo.aspectRatio,
|
||||
src: [
|
||||
{
|
||||
srcType: uiVideo.srcType,
|
||||
url: uiVideo.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'CLI',
|
||||
playbackRate: cliVideo.playbackRate,
|
||||
aspectRatio: cliVideo.aspectRatio,
|
||||
src: [
|
||||
{
|
||||
srcType: cliVideo.srcType,
|
||||
url: cliVideo.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Desktop',
|
||||
playbackRate: desktopVideo.playbackRate,
|
||||
aspectRatio: desktopVideo.aspectRatio,
|
||||
src: [
|
||||
{
|
||||
srcType: desktopVideo.srcType,
|
||||
url: desktopVideo.url,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import s from './style.module.css'
|
||||
|
||||
export default function HowBoundaryWorks({ heading, description, items, img }) {
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<div className={s.inner}>
|
||||
<div className="content">
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<p className={s.description}>{description}</p>
|
||||
<ul className={s.items}>
|
||||
{items.map((item, index) => {
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
return (
|
||||
<li key={index} className={s.item}>
|
||||
{item}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div className={s.media}>
|
||||
<img src={img.src} alt={img.alt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
.root {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.inner {
|
||||
--columns: 1;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
grid-gap: 48px 32px;
|
||||
padding: 48px 32px;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
|
||||
border-radius: 1px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 2;
|
||||
|
||||
padding: 100px 75px;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-3 from global;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 32px 0 0;
|
||||
composes: g-type-body-small from global;
|
||||
}
|
||||
|
||||
.items {
|
||||
list-style: none;
|
||||
margin: 15px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
color: var(--gray-2);
|
||||
composes: g-type-display-5 from global;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
margin-right: 20px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 9999px;
|
||||
background-color: #c4c4c4;
|
||||
}
|
||||
|
||||
& + .item {
|
||||
border-top: 2px solid var(--gray-6);
|
||||
}
|
||||
}
|
||||
|
||||
.media {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
& img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
.root {
|
||||
& > h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import LogoList from './logo-list'
|
||||
import s from './feature.module.css'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Feature({
|
||||
title,
|
||||
description,
|
||||
logos,
|
||||
onInViewStatusChanged,
|
||||
}) {
|
||||
const [ref, inView] = useInView({ threshold: 0.8 })
|
||||
const [inViewStatus, setInViewStatus] = useState(false)
|
||||
if (inView != inViewStatus) {
|
||||
setInViewStatus(inView)
|
||||
onInViewStatusChanged(inView)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.root} ref={ref}>
|
||||
<h3 className="g-type-display-4">{title}</h3>
|
||||
<p className="g-type-body">{description}</p>
|
||||
{logos ? <LogoList logos={logos} /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
import s from './logo-list.module.css'
|
||||
|
||||
export default function LogoList({ logos }) {
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<div className={s.logos}>
|
||||
{logos.map((logo, stableIdx) => (
|
||||
<div
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={stableIdx}
|
||||
>
|
||||
<img src={logo.url} width={logo.width} alt={logo.alt} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
.logos {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
max-width: 430px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > div {
|
||||
margin-right: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
& img {
|
||||
max-height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.footerText {
|
||||
color: var(--gray-4);
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
.root {
|
||||
display: grid;
|
||||
|
||||
@media (--medium-up) {
|
||||
position: sticky;
|
||||
top: calc(50vh - (249px));
|
||||
}
|
||||
|
||||
& > * {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
transition: opacity ease-in 0.5s;
|
||||
|
||||
&:last-child {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--medium-up) {
|
||||
& > *:last-child {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
& > *.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
& > svg {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
.root {
|
||||
composes: g-grid-container from global;
|
||||
padding-top: 88px;
|
||||
padding-bottom: 88px;
|
||||
|
||||
@media (--medium-up) {
|
||||
padding-top: 160px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
.intro {
|
||||
--columns: 1;
|
||||
|
||||
padding-top: 88px;
|
||||
padding-bottom: 88px;
|
||||
border-top: 1px solid var(--gray-6);
|
||||
display: grid;
|
||||
column-gap: 32px;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
row-gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
|
||||
padding-top: 140px;
|
||||
padding-bottom: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
.introTitle {
|
||||
margin: 0;
|
||||
grid-column: 1 / -1;
|
||||
composes: g-type-display-2 from global;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
}
|
||||
|
||||
.introDescription {
|
||||
grid-column: 1 / -1;
|
||||
composes: g-type-body-large from global;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
|
||||
& :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
--columns: 1;
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
grid-gap: 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 12;
|
||||
}
|
||||
}
|
||||
|
||||
.diagram {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 1 / 6;
|
||||
}
|
||||
}
|
||||
|
||||
.features {
|
||||
padding: 0;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
@media (--medium-up) {
|
||||
grid-column: 7 / -1;
|
||||
}
|
||||
|
||||
& > li {
|
||||
list-style: none;
|
||||
|
||||
& > div {
|
||||
list-style: none;
|
||||
padding-bottom: 285px;
|
||||
|
||||
@media (--small) {
|
||||
padding-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
& > div {
|
||||
padding-bottom: 355px;
|
||||
margin-bottom: -200px;
|
||||
@media (--small) {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
& > div {
|
||||
padding-top: 108px;
|
||||
@media (width < 1120px) {
|
||||
padding-top: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
import s from './how-it-works.module.css'
|
||||
import HowBoundaryWorksDiagram from './how-boundary-works-diagram'
|
||||
import Feature from './feature'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function HowItWorks({ title, description, features }) {
|
||||
const [activeExampleIndex, setActiveExampleIndex] = useState(0)
|
||||
const [viewportStatus, setViewportStatus] = useState(
|
||||
new Array(features.length).fill(false)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<div className={s.intro}>
|
||||
<h2 className={s.introTitle}>{title}</h2>
|
||||
<div className={s.introDescription}>{description}</div>
|
||||
</div>
|
||||
<div className={s.contentContainer}>
|
||||
<div className={s.diagram}>
|
||||
<HowBoundaryWorksDiagram activeExampleIndex={activeExampleIndex} />
|
||||
</div>
|
||||
<ul className={s.features}>
|
||||
{features.map((feature, index) => (
|
||||
<li key={feature.title}>
|
||||
<Feature
|
||||
{...feature}
|
||||
onInViewStatusChanged={(state) => {
|
||||
const newStatusArray = [...viewportStatus]
|
||||
newStatusArray[index] = state
|
||||
setViewportStatus(newStatusArray)
|
||||
// Calculate the first element in focus, set that as
|
||||
// our new activeExampleIndex. If it's been updated
|
||||
// notify the subscriber.
|
||||
const newExampleIndex = newStatusArray.lastIndexOf(true)
|
||||
if (
|
||||
activeExampleIndex != newExampleIndex &&
|
||||
newExampleIndex != -1
|
||||
) {
|
||||
setActiveExampleIndex(newExampleIndex)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 274 B |
@ -1,71 +0,0 @@
|
||||
import s from './merch-desktop-client.module.css'
|
||||
import InlineSvg from '@hashicorp/react-inline-svg'
|
||||
|
||||
export default function MerchDesktopDownload({ version, releases }) {
|
||||
const { builds } = releases.versions[version]
|
||||
|
||||
// Calculate all Operating Systems that we have versions for
|
||||
// and place their respective builds under them
|
||||
const operatingSystems = builds
|
||||
.map((build) => {
|
||||
return build.os
|
||||
})
|
||||
.filter((value, index, self) => {
|
||||
// Parse out duplicates
|
||||
return self.indexOf(value) === index
|
||||
})
|
||||
.map((os) => {
|
||||
// Add the respective builds under their OS
|
||||
return {
|
||||
os: os,
|
||||
builds: builds.filter((build) => {
|
||||
return build.os === os
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={s.container} id="desktop">
|
||||
<div className={s.wrapper}>
|
||||
<span className={s.title}>Desktop Client - {version}</span>
|
||||
<ul className={s.downloadsList}>
|
||||
{operatingSystems.map((operatingSystem) => {
|
||||
return (
|
||||
<li key={operatingSystem.os}>
|
||||
<InlineSvg
|
||||
src={require(`./img/${operatingSystem.os}.svg?include`)}
|
||||
/>
|
||||
<ul className={s.versionsList}>
|
||||
{operatingSystem.builds.map((build) => {
|
||||
return (
|
||||
<li key={build.filename} className={s.versionDownload}>
|
||||
<a href={build.url}>
|
||||
.{getFileExtension(build.filename)} (
|
||||
{humanArch(build.arch)})
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function humanArch(arch) {
|
||||
if (arch === '386') {
|
||||
return '32-bit'
|
||||
}
|
||||
if (arch === 'amd64') {
|
||||
return '64-bit'
|
||||
}
|
||||
return arch
|
||||
}
|
||||
|
||||
function getFileExtension(filename) {
|
||||
return filename.substring(filename.lastIndexOf('.') + 1, filename.length)
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
border: 1px solid var(--gray-5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 193px;
|
||||
padding: 40px 50px;
|
||||
|
||||
& .wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
|
||||
& .title {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
& .downloadsList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
margin-top: 22px;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
|
||||
& > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
& svg {
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
& .versionsList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
& > .versionDownload {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import styles from './section-break-cta.module.css'
|
||||
import Button from '@hashicorp/react-button'
|
||||
|
||||
export default function SectionBreakCta({ heading, content, link }) {
|
||||
return (
|
||||
<div className={styles.sectionBreakCta}>
|
||||
<hr />
|
||||
<h4 className="g-type-display-4">{heading}</h4>
|
||||
<p className="g-type-body">{content}</p>
|
||||
<Button
|
||||
title={link.text}
|
||||
url={link.url}
|
||||
theme={{
|
||||
brand: 'neutral',
|
||||
variant: 'tertiary-neutral',
|
||||
background: 'light',
|
||||
}}
|
||||
linkType="outbound"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
.sectionBreakCta {
|
||||
padding: 60px 30px;
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 12px rgba(37, 38, 45, 0.08);
|
||||
margin: 0 auto;
|
||||
background-color: var(--white);
|
||||
|
||||
@media (width <= 880px) {
|
||||
margin: 0 40px;
|
||||
}
|
||||
|
||||
& > h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
& hr {
|
||||
width: 64px;
|
||||
background-color: var(--boundary);
|
||||
margin-top: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
import Subnav from '@hashicorp/react-subnav'
|
||||
import { useRouter } from 'next/router'
|
||||
import subnavItems from 'data/navigation'
|
||||
import { productSlug } from 'data/metadata'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function ProductSubnav() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<Subnav
|
||||
titleLink={{
|
||||
text: 'Boundary',
|
||||
url: '/',
|
||||
}}
|
||||
ctaLinks={[
|
||||
{
|
||||
text: 'GitHub',
|
||||
url: `https://www.github.com/hashicorp/${productSlug}`,
|
||||
},
|
||||
{
|
||||
text: 'Download',
|
||||
url: '/downloads',
|
||||
},
|
||||
]}
|
||||
currentPath={router.asPath}
|
||||
menuItemsAlign="right"
|
||||
menuItems={subnavItems}
|
||||
constrainWidth
|
||||
Link={Link}
|
||||
matchOnBasePath
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import s from './style.module.css'
|
||||
|
||||
export default function WhyBoundary({ heading, items }) {
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<h2 className={s.heading}>{heading}</h2>
|
||||
<ul className={s.items}>
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
// Index is stable
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index}>
|
||||
<img className={s.itemIcon} src={item.icon} alt={item.heading} />
|
||||
<h3 className={s.itemHeading}>{item.heading}</h3>
|
||||
<p className={s.itemDescription}>{item.description}</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
.root {
|
||||
composes: g-grid-container from global;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
composes: g-type-display-2 from global;
|
||||
}
|
||||
|
||||
.items {
|
||||
--columns: 1;
|
||||
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 90px 0 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
||||
grid-gap: 48px 32px;
|
||||
|
||||
@media (--medium-up) {
|
||||
--columns: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.itemHeading {
|
||||
margin: 38px 0 0;
|
||||
composes: g-type-display-4 from global;
|
||||
}
|
||||
|
||||
.itemDescription {
|
||||
margin: 12px 0 0;
|
||||
composes: g-type-body from global;
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
export const ALERT_BANNER_ACTIVE = false
|
||||
|
||||
// https://github.com/hashicorp/web-components/tree/master/packages/alert-banner
|
||||
export default {
|
||||
tag: 'Blog post',
|
||||
url: 'https://www.hashicorp.com/blog/a-new-chapter-for-hashicorp',
|
||||
text:
|
||||
'HashiCorp shares have begun trading on the Nasdaq. Read the blog from our founders, Mitchell Hashimoto and Armon Dadgar.',
|
||||
linkText: 'Read the post',
|
||||
// Set the expirationDate prop with a datetime string (e.g. '2020-01-31T12:00:00-07:00')
|
||||
// if you'd like the component to stop showing at or after a certain date
|
||||
expirationDate: '2021-12-17T23:00:00-07:00',
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
export const packageManagers = {
|
||||
homebrew: {
|
||||
label: 'Homebrew',
|
||||
url: '',
|
||||
commands: ['brew tap hashicorp/tap', 'brew install hashicorp/tap/boundary'],
|
||||
},
|
||||
ubuntu: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
centos: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
fedora: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
amazonLinux: {
|
||||
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',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const packageManagersByOs = {
|
||||
darwin: packageManagers.homebrew,
|
||||
linux: [
|
||||
packageManagers.ubuntu,
|
||||
packageManagers.centos,
|
||||
packageManagers.fedora,
|
||||
packageManagers.amazonLinux,
|
||||
],
|
||||
}
|
||||
|
||||
export const 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',
|
||||
},
|
||||
]
|
||||
@ -1,2 +0,0 @@
|
||||
export const productName = 'Boundary'
|
||||
export const productSlug = 'boundary'
|
||||
@ -1,28 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
text: 'Overview',
|
||||
url: '/',
|
||||
type: 'inbound',
|
||||
},
|
||||
'divider',
|
||||
{
|
||||
text: 'Tutorials',
|
||||
url: 'https://learn.hashicorp.com/boundary',
|
||||
type: 'inbound',
|
||||
},
|
||||
{
|
||||
text: 'Docs',
|
||||
url: '/docs',
|
||||
type: 'inbound',
|
||||
},
|
||||
{
|
||||
text: 'API',
|
||||
url: '/api-docs',
|
||||
type: 'inbound',
|
||||
},
|
||||
{
|
||||
text: 'Community',
|
||||
url: '/community',
|
||||
type: 'inbound',
|
||||
},
|
||||
]
|
||||
@ -1,2 +0,0 @@
|
||||
export const VERSION = '0.7.6'
|
||||
export const DESKTOP_VERSION = '1.4.2'
|
||||
@ -1 +0,0 @@
|
||||
/// <reference types="@hashicorp/platform-types" />
|
||||
@ -1,15 +0,0 @@
|
||||
import { ConsentManagerService } from '@hashicorp/react-consent-manager/types'
|
||||
|
||||
const localConsentManagerServices: ConsentManagerService[] = [
|
||||
{
|
||||
name: 'Demandbase Tag',
|
||||
description:
|
||||
'The Demandbase tag is a tracking service to identify website visitors and measure interest on our website.',
|
||||
category: 'Analytics',
|
||||
url: 'https://tag.demandbase.com/960ab0a0f20fb102.min.js',
|
||||
async: true,
|
||||
},
|
||||
]
|
||||
|
||||
export default localConsentManagerServices
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
|
||||
function useHover() {
|
||||
const [value, setValue] = useState(false)
|
||||
|
||||
const ref = useRef(null)
|
||||
const handleMouseOver = () => setValue(true)
|
||||
const handleMouseOut = () => setValue(false)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const node = ref.current
|
||||
if (node) {
|
||||
node.addEventListener('mouseover', handleMouseOver)
|
||||
node.addEventListener('mouseout', handleMouseOut)
|
||||
|
||||
return () => {
|
||||
node.removeEventListener('mouseover', handleMouseOver)
|
||||
node.removeEventListener('mouseout', handleMouseOut)
|
||||
}
|
||||
}
|
||||
},
|
||||
[ref.current] // Recall only if ref changes
|
||||
)
|
||||
|
||||
return [ref, value]
|
||||
}
|
||||
|
||||
export default useHover
|
||||
@ -1,22 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* Hook that runs a callback on clicks outside the ref
|
||||
*/
|
||||
function useOnClickOutside(ref, callbackFn) {
|
||||
useEffect(() => {
|
||||
function handleClick(event) {
|
||||
const isOutside = ref.current && !ref.current.contains(event.target)
|
||||
if (isOutside) callbackFn(event)
|
||||
}
|
||||
|
||||
// Bind the event listener
|
||||
document.addEventListener('mousedown', handleClick)
|
||||
return () => {
|
||||
// Unbind the event listener on clean up
|
||||
document.removeEventListener('mousedown', handleClick)
|
||||
}
|
||||
}, [ref])
|
||||
}
|
||||
|
||||
export default useOnClickOutside
|
||||
@ -1,8 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const RefParser = require('@apidevtools/json-schema-ref-parser')
|
||||
|
||||
export default async function parseSchema(filePath) {
|
||||
const content = JSON.parse(fs.readFileSync(filePath))
|
||||
const schema = await RefParser.dereference(content)
|
||||
return schema
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@ -1,29 +0,0 @@
|
||||
const withHashicorp = require('@hashicorp/platform-nextjs-plugin')
|
||||
const path = require('path')
|
||||
const redirects = require('./redirects.js')
|
||||
|
||||
// log out our primary environment variables for clarity in build logs
|
||||
console.log(`HASHI_ENV: ${process.env.HASHI_ENV}`)
|
||||
console.log(`NODE_ENV: ${process.env.NODE_ENV}`)
|
||||
console.log(`VERCEL_ENV: ${process.env.VERCEL_ENV}`)
|
||||
console.log(`MKTG_CONTENT_API: ${process.env.MKTG_CONTENT_API}`)
|
||||
console.log(`ENABLE_VERSIONED_DOCS: ${process.env.ENABLE_VERSIONED_DOCS}`)
|
||||
|
||||
module.exports = withHashicorp({
|
||||
defaultLayout: true,
|
||||
mdx: { resolveIncludes: path.join(__dirname, 'pages/partials') },
|
||||
nextOptimizedImages: true,
|
||||
})({
|
||||
async redirects() {
|
||||
return await redirects
|
||||
},
|
||||
svgo: { plugins: [{ removeViewBox: false }] },
|
||||
tipBranch: 'main',
|
||||
env: {
|
||||
HASHI_ENV: process.env.HASHI_ENV || 'development',
|
||||
SEGMENT_WRITE_KEY: 'JkNZiSgwVRAAFrkqqdHLxf0xfcZuhYYc',
|
||||
BUGSNAG_CLIENT_KEY: '635db43e199cb02419379291d573205b',
|
||||
BUGSNAG_SERVER_KEY: 'f85278a46e1b5565a9e91974cdc2843b',
|
||||
ENABLE_VERSIONED_DOCS: process.env.ENABLE_VERSIONED_DOCS || false,
|
||||
},
|
||||
})
|
||||
@ -1,32 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function NotFound() {
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window?.analytics?.track === 'function' &&
|
||||
typeof window?.document?.referrer === 'string' &&
|
||||
typeof window?.location?.href === 'string'
|
||||
)
|
||||
window.analytics.track(window.location.href, {
|
||||
category: '404 Response',
|
||||
label: window.document.referrer || 'No Referrer',
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div id="p-404" className="g-grid-container">
|
||||
<h1 className="g-type-display-1">Page Not Found</h1>
|
||||
<p>
|
||||
We‘re sorry but we can‘t find the page you‘re looking
|
||||
for.
|
||||
</p>
|
||||
<p>
|
||||
<Link href="/">
|
||||
<a>Back to Home</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import './style.css'
|
||||
import '@hashicorp/platform-util/nprogress/style.css'
|
||||
|
||||
import useFathomAnalytics from '@hashicorp/platform-analytics'
|
||||
import NProgress from '@hashicorp/platform-util/nprogress'
|
||||
import createConsentManager from '@hashicorp/react-consent-manager/loader'
|
||||
import localConsentManagerServices from 'lib/consent-manager-services'
|
||||
import useAnchorLinkAnalytics from '@hashicorp/platform-util/anchor-link-analytics'
|
||||
import HashiStackMenu from '@hashicorp/react-hashi-stack-menu'
|
||||
import Router from 'next/router'
|
||||
import HashiHead from '@hashicorp/react-head'
|
||||
import { ErrorBoundary } from '@hashicorp/platform-runtime-error-monitoring'
|
||||
import ProductSubnav from '../components/subnav'
|
||||
import Footer from 'components/footer'
|
||||
import Error from './_error'
|
||||
import AlertBanner from '@hashicorp/react-alert-banner'
|
||||
import alertBannerData, { ALERT_BANNER_ACTIVE } from 'data/alert-banner'
|
||||
|
||||
NProgress({ Router })
|
||||
const { ConsentManager, openConsentManager } = createConsentManager({
|
||||
preset: 'oss',
|
||||
otherServices: [...localConsentManagerServices],
|
||||
})
|
||||
|
||||
const title = 'Boundary by HashiCorp'
|
||||
const description =
|
||||
'Boundary is an open source solution that automates a secure identity-based user access to hosts and services across environments.'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
useFathomAnalytics()
|
||||
useAnchorLinkAnalytics()
|
||||
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={Error}>
|
||||
<HashiHead
|
||||
title={title}
|
||||
siteName={title}
|
||||
description={description}
|
||||
image="https://www.boundaryproject.io/img/og-image.png"
|
||||
icon={[{ href: '/_favicon.ico' }]}
|
||||
>
|
||||
<meta name="og:title" property="og:title" content={title} />
|
||||
<meta name="og:description" property="og:title" content={description} />
|
||||
</HashiHead>
|
||||
{ALERT_BANNER_ACTIVE && (
|
||||
<AlertBanner {...alertBannerData} product="boundary" hideOnMobile />
|
||||
)}
|
||||
<HashiStackMenu />
|
||||
<ProductSubnav />
|
||||
<div className="content">
|
||||
<Component {...pageProps} />
|
||||
</div>
|
||||
<Footer openConsentManager={openConsentManager} />
|
||||
<ConsentManager />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import HashiHead from '@hashicorp/react-head'
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx)
|
||||
return { ...initialProps }
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<HashiHead />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<script
|
||||
noModule
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.MSInputMethodContext && document.documentMode && document.write('<script src="/ie-warning.js"><\\x2fscript>');`,
|
||||
}}
|
||||
/>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import NotFound from './404'
|
||||
import Bugsnag from '@hashicorp/platform-runtime-error-monitoring'
|
||||
|
||||
function Error({ statusCode }) {
|
||||
return <NotFound statusCode={statusCode} />
|
||||
}
|
||||
|
||||
Error.getInitialProps = ({ res, err }) => {
|
||||
if (err) Bugsnag.notify(err)
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
|
||||
return { statusCode }
|
||||
}
|
||||
|
||||
export default Error
|
||||
@ -1,37 +0,0 @@
|
||||
import { productName, productSlug } from 'data/metadata'
|
||||
import OpenApiPage from '@hashicorp/react-open-api-page'
|
||||
/* Used server-side only */
|
||||
import path from 'path'
|
||||
import {
|
||||
getPathsFromSchema,
|
||||
getPropsForPage,
|
||||
} from '@hashicorp/react-open-api-page/server'
|
||||
import { processSchemaFile } from '@hashicorp/react-open-api-page/process-schema'
|
||||
|
||||
const targetFile = '../internal/gen/controller.swagger.json'
|
||||
const pathFromRoot = 'api-docs'
|
||||
|
||||
export default function OpenApiDocsPage(props) {
|
||||
return (
|
||||
<OpenApiPage
|
||||
{...props}
|
||||
productName={productName}
|
||||
productSlug={productSlug}
|
||||
baseRoute={pathFromRoot}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const swaggerFile = path.join(process.cwd(), targetFile)
|
||||
const schema = await processSchemaFile(swaggerFile)
|
||||
const paths = getPathsFromSchema(schema)
|
||||
return { paths, fallback: false }
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }) {
|
||||
const swaggerFile = path.join(process.cwd(), targetFile)
|
||||
const schema = await processSchemaFile(swaggerFile)
|
||||
const props = getPropsForPage(schema, params)
|
||||
return { props }
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
.root {
|
||||
composes: g-grid-container from global;
|
||||
margin-bottom: 72px;
|
||||
margin-top: 72px;
|
||||
|
||||
& :global(.g-section-header) {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { productName, productSlug } from 'data/metadata'
|
||||
import DocsPage from '@hashicorp/react-docs-page'
|
||||
import { getStaticGenerationFunctions } from '@hashicorp/react-docs-page/server'
|
||||
|
||||
const NAV_DATA_FILE = 'data/docs-nav-data.json'
|
||||
const CONTENT_DIR = 'content/docs'
|
||||
const basePath = 'docs'
|
||||
|
||||
export default function DocsLayout(props) {
|
||||
return (
|
||||
<DocsPage
|
||||
product={{ name: productName, slug: productSlug }}
|
||||
baseRoute={basePath}
|
||||
staticProps={props}
|
||||
showVersionSelect={process.env.ENABLE_VERSIONED_DOCS === 'true'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const { getStaticPaths, getStaticProps } = getStaticGenerationFunctions(
|
||||
process.env.ENABLE_VERSIONED_DOCS === 'true'
|
||||
? {
|
||||
strategy: 'remote',
|
||||
basePath: basePath,
|
||||
fallback: 'blocking',
|
||||
revalidate: 360, // 1 hour
|
||||
product: productSlug,
|
||||
}
|
||||
: {
|
||||
strategy: 'fs',
|
||||
localContentDir: CONTENT_DIR,
|
||||
navDataFile: NAV_DATA_FILE,
|
||||
product: productSlug,
|
||||
}
|
||||
)
|
||||
|
||||
export { getStaticPaths, getStaticProps }
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@ -1,92 +0,0 @@
|
||||
import { VERSION, DESKTOP_VERSION } from 'data/version.js'
|
||||
import { productSlug } from 'data/metadata'
|
||||
import ProductDownloadsPage from '@hashicorp/react-product-downloads-page'
|
||||
import MerchDesktopClient from 'components/merch-desktop-client'
|
||||
import styles from './style.module.css'
|
||||
|
||||
const DESKTOP_BINARY_SLUG = 'boundary-desktop'
|
||||
|
||||
export default function DownloadsPage({ binaryReleases, desktopReleases }) {
|
||||
return (
|
||||
<ProductDownloadsPage
|
||||
releases={binaryReleases}
|
||||
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('@hashicorp/mktg-logos/product/boundary/primary/color.svg')}
|
||||
/>
|
||||
}
|
||||
product="boundary"
|
||||
tutorialLink={{
|
||||
label: 'View Tutorial on HashiCorp Learn',
|
||||
href:
|
||||
'https://learn.hashicorp.com/tutorials/boundary/getting-started-install',
|
||||
}}
|
||||
merchandisingSlot={
|
||||
<MerchDesktopClient
|
||||
version={DESKTOP_VERSION}
|
||||
releases={desktopReleases}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return Promise.all([
|
||||
fetch(`https://releases.hashicorp.com/boundary/index.json`, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
}).then((res) => res.json()),
|
||||
fetch(`https://releases.hashicorp.com/boundary-desktop/index.json`, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
}).then((res) => res.json()),
|
||||
])
|
||||
.then((result) => {
|
||||
const binaryReleases = result.find(
|
||||
(releases) => releases.name === productSlug
|
||||
)
|
||||
const desktopReleases = result.find(
|
||||
(releases) => releases.name === DESKTOP_BINARY_SLUG
|
||||
)
|
||||
return {
|
||||
props: {
|
||||
binaryReleases,
|
||||
desktopReleases,
|
||||
},
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(
|
||||
`--------------------------------------------------------
|
||||
Unable to resolve version ${VERSION} on releases.hashicorp.com from link
|
||||
<https://releases.hashicorp.com/${productSlug}/${VERSION}/index.json>. Usually this
|
||||
means that the specified version has not yet been released. The downloads page
|
||||
version can only be updated after the new version has been released, to ensure
|
||||
that it works for all users.
|
||||
----------------------------------------------------------`
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
.logo {
|
||||
width: 140px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 649 B |
|
Before Width: | Height: | Size: 725 B |
|
Before Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 434 B |
|
Before Width: | Height: | Size: 505 B |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 885 B |
|
Before Width: | Height: | Size: 928 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 561 B |
|
Before Width: | Height: | Size: 642 B |
|
Before Width: | Height: | Size: 392 B |