nx/nx-dev/ui-powerpack/src/lib/powerpack-features.tsx
Juri Strumpflohner a22f4fce49
docs(misc): new self-hosted cache updates (#30550)
Adds the new updates around self-hosted caching possibilities.
2025-03-31 13:28:42 -04:00

580 lines
20 KiB
TypeScript

'use client';
import {
forwardRef,
ReactElement,
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import {
ButtonLink,
SectionHeading,
Strong,
TextLink,
} from '@nx/nx-dev/ui-common';
import { cx } from '@nx/nx-dev/ui-primitives';
import { AnimatedCurvedBeam } from '@nx/nx-dev/ui-animations';
import {
CircleStackIcon,
ServerIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import {
AmazonS3Icon,
AzureDevOpsIcon,
GoogleCloudIcon,
MinIOIcon,
NxIcon,
} from '@nx/nx-dev/ui-icons';
import Link from 'next/link';
import { AnimatePresence, motion } from 'framer-motion';
export function PowerpackFeatures(): ReactElement {
return (
<section className="relative isolate">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="relative flex justify-center">
<div className="pointer-events-auto w-fit justify-between gap-x-6 bg-slate-950 px-6 py-2.5 sm:rounded-xl sm:py-3 sm:pl-4 sm:pr-3.5 dark:bg-white">
<p className="text-sm/6 text-white dark:text-slate-950">
<strong className="font-semibold">
Looking for self-hosted caching?
</strong>
<svg
viewBox="0 0 2 2"
aria-hidden="true"
className="mx-2 inline size-0.5 fill-current"
>
<circle r={1} cx={1} cy={1} />
</svg>
It is now free for everyone&nbsp;
<Link
href="/remote-cache"
title="Self-hosted cache storage"
className="text-white dark:text-slate-950"
>
<span className="absolute inset-0" />
<span aria-hidden="true">&rarr;</span>
</Link>
</p>
</div>
</div>
<div className="mt-32 grid grid-cols-1 gap-8 lg:grid-cols-2">
{/*<div className="flex max-w-full flex-col gap-16 bg-white/50 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 md:col-span-full lg:mx-0 lg:max-w-full lg:flex-row lg:items-center lg:py-16 xl:px-16 dark:bg-white/5 dark:ring-white/10">
<div className="xl:max-w-xl">
<SectionHeading
as="h2"
variant="title"
id="self-hosted-cache-storage"
className="scroll-mt-48"
>
Self-hosted cache storage
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
Use <Strong>Amazon S3</Strong>, <Strong>MinIO</Strong>,{' '}
<Strong>GCP</Strong>, <Strong>Azure</Strong> or a{' '}
<Strong>shared network drive</Strong> as your remote cache
storage, offering a flexible, self-managed solution for faster
builds. Nx Powerpack self-hosted cache storage is{' '}
<TextLink
href="/nx-enterprise/powerpack/free-licenses-and-trials"
title="Get a Powerpack license"
>
free for small teams
</TextLink>
.
</p>
<div className="mt-16">
<ButtonLink
href="/nx-enterprise/powerpack/custom-caching"
title="Learn more about self-hosted cache storage"
variant="secondary"
size="default"
>
Learn about self-hosted cache storage
</ButtonLink>
</div>
</div>
<div className="hidden w-full xl:block">
<CustomRemoteCacheAnimation />
</div>
</div>*/}
<div className="flex flex-col gap-16 bg-white/50 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:justify-between lg:py-16 xl:px-16 dark:bg-white/5 dark:ring-white/10">
<div className="max-w-2xl">
<SectionHeading
as="h2"
variant="title"
id="codeowners-for-monorepos"
className="scroll-mt-48"
>
Codeowners for monorepos
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
Common VCS providers require folder-based ownership definitions.
Now, define and manage ownership where it matters
<Strong>at the project level</Strong>.
</p>
<p className="mt-6 text-pretty text-lg">
Bridge the gap by{' '}
<Strong>
automatically tracking changes and syncing ownership data
</Strong>{' '}
with GitHub, GitLab, or Bitbucket-specific CODEOWNERS files.
This ensures clear responsibilities and enables efficient
collaboration across large-scale projects.
</p>
</div>
<div className="flex">
<ButtonLink
href="/nx-enterprise/powerpack/owners"
title="Learn more about codeowners"
variant="secondary"
size="default"
>
Learn more about codeowners
</ButtonLink>
</div>
</div>
<div className="flex flex-col gap-16 bg-white/50 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:justify-between lg:py-16 xl:px-16 dark:bg-white/5 dark:ring-white/10">
<div className="max-w-2xl">
<SectionHeading
as="h2"
variant="title"
id="workspace-conformance"
className="scroll-mt-48"
>
Workspace conformance
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
<Strong>
Define and run conformance rules throughout your workspace
</Strong>
, leverage built-in rules or{' '}
<Strong>
create your own to ensure compliance with organizational
standards.
</Strong>
</p>
<p className="mt-6 text-pretty text-lg">
With Nx Enterprise, you can{' '}
<Strong>
upload your custom rules to your Nx Cloud organization
</Strong>{' '}
and automatically enforce them across multiple repositories and
workspaces, regardless of your tech stack.
</p>
</div>
<div className="flex">
<ButtonLink
href="/nx-enterprise/powerpack/conformance"
title="Learn how to set up conformance rules"
variant="secondary"
size="default"
>
Learn how to use conformance rules
</ButtonLink>
</div>
</div>
<div className="flex max-w-full flex-col gap-16 bg-slate-50/80 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:col-span-2 lg:mx-0 lg:max-w-full lg:flex-row lg:items-center lg:py-16 xl:px-16 dark:bg-white/15 dark:ring-white/10">
<div className="relative hidden h-full w-64 shrink-0 overflow-hidden lg:block">
<img
src="/images/powerpack/trust-secure-light.avif"
alt="trust & secure illustration"
className="absolute inset-0 block -translate-y-[85px] scale-150 transform dark:hidden"
/>
<img
src="/images/powerpack/trust-secure-dark.avif"
alt="trust & secure illustration"
className="absolute inset-0 hidden -translate-y-[85px] scale-150 transform dark:block"
/>
</div>
<div>
<SectionHeading
as="h2"
variant="title"
id="trustworthy-and-secure"
className="scroll-mt-48"
>
Trustworthy and secure
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
Nx Powerpack is reliably maintained by the Nx team. Nx (the
company) adheres to strict security and data-handling standards,
including compliance with <Strong>SOC 2</Strong> (Type 1 and
Type 2).
</p>
<div className="mt-6">
<ButtonLink
href="https://security.nx.app"
target="_blank"
title="Learn how to set up conformance rules"
variant="secondary"
size="default"
>
See our Trust Report
</ButtonLink>
</div>
</div>
</div>
<div className="flex flex-col gap-16 bg-slate-50/80 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:justify-between lg:py-16 xl:px-16 dark:bg-white/15 dark:ring-white/10">
<div className="max-w-2xl">
<SectionHeading
as="h2"
variant="title"
id="faster-procurement"
className="scroll-mt-48"
>
Faster procurement, simpler licensing
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
A simple{' '}
<Strong>
licensing model that reduces red tape and speeds up
procurement processes
</Strong>
. Your teams can get started quickly without lengthy
negotiations, ensuring a faster go-to-market.
</p>
</div>
</div>
<div className="flex flex-col gap-16 bg-slate-50/80 px-6 py-16 ring-1 ring-slate-200 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:justify-between lg:py-16 xl:px-16 dark:bg-white/15 dark:ring-white/10">
<div className="max-w-2xl">
<SectionHeading
as="h2"
variant="title"
id="included-by-default"
className="scroll-mt-48"
>
Ready to go for Nx Enterprise
</SectionHeading>
<p className="mt-6 text-pretty text-lg">
<Strong>
Nx Powerpack is included at no extra cost for Nx Enterprise
customers
</Strong>
, unlocking additional capabilities without needing to manage
more tools or onboard a new vendor.{' '}
<TextLink href="/enterprise/trial" title="Nx Enterprise trial">
Request a free trial of Nx Enterprise
</TextLink>
.
</p>
</div>
</div>
</div>
</div>
<div
className="absolute inset-x-0 top-16 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
aria-hidden="true"
>
<div
className="aspect-[1318/752] w-[82.375rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-25"
style={{
clipPath:
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
}}
/>
</div>
</section>
);
}
const Card = forwardRef<
HTMLDivElement,
{
className?: string;
children?: ReactNode;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
}
>(({ className, children, onMouseEnter, onMouseLeave }, ref) => {
return (
<div
ref={ref}
className={cx(
'z-10 flex items-center gap-2 rounded-md border border-slate-200 bg-white p-2 py-2 shadow-sm dark:border-white/10 dark:bg-slate-950',
className
)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</div>
);
});
Card.displayName = 'Card';
export function CustomRemoteCacheAnimation(): ReactElement {
const awsRef = useRef<HTMLDivElement>(null);
const azureRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const gcpRef = useRef<HTMLDivElement>(null);
const minioRef = useRef<HTMLDivElement>(null);
const networkDriveRef = useRef<HTMLDivElement>(null);
const nxRef = useRef<HTMLDivElement>(null);
const animatedBeamMap: Record<string, ReactElement> = {
aws: (
<AnimatedCurvedBeam
containerRef={containerRef}
fromRef={awsRef}
toRef={nxRef}
curvature={-75}
startXOffset={-75}
endYOffset={0}
bidirectional={true}
duration={5}
/>
),
azure: (
<AnimatedCurvedBeam
containerRef={containerRef}
fromRef={azureRef}
toRef={nxRef}
curvature={75}
startXOffset={-75}
endYOffset={0}
bidirectional={true}
reverse={true}
duration={5}
/>
),
gcp: (
<AnimatedCurvedBeam
containerRef={containerRef}
fromRef={gcpRef}
toRef={nxRef}
bidirectional={true}
curvature={75}
startXOffset={-75}
endYOffset={0}
reverse={true}
duration={5}
/>
),
minio: (
<AnimatedCurvedBeam
containerRef={containerRef}
fromRef={minioRef}
toRef={nxRef}
curvature={-75}
startXOffset={-75}
endYOffset={0}
bidirectional={true}
duration={5}
/>
),
networkDrive: (
<AnimatedCurvedBeam
containerRef={containerRef}
fromRef={networkDriveRef}
toRef={nxRef}
curvature={10}
startXOffset={-75}
endXOffset={0}
bidirectional={true}
duration={5}
/>
),
};
const links = Object.keys(animatedBeamMap);
const duration = 6000;
const timeout = useRef<NodeJS.Timeout>();
const [selected, setSelected] = useState<string | null>('aws');
const [autoplay, setAutoplay] = useState<boolean>(true);
const play = useCallback(() => {
timeout.current = setTimeout(next, duration);
}, [selected]);
const next = () => {
if (links.length <= 1) return; // No change if there's only one or no items
if (selected === null)
return setSelected(links[Math.floor(Math.random() * links.length)]);
const availableLinks = links.filter((link) => link !== selected);
const randomIndex = Math.floor(Math.random() * availableLinks.length);
setSelected(availableLinks[randomIndex]);
};
useEffect(() => {
clearTimeout(timeout.current);
if (autoplay) play();
}, [selected, autoplay, play]);
return (
<div className="relative flex h-full w-full" ref={containerRef}>
<div className="grid w-full grid-cols-2 items-center justify-center gap-24">
<div className="flex w-full justify-center">
<Card
ref={nxRef}
className="size-18 relative p-3 transition hover:bg-slate-50 dark:hover:bg-slate-800"
>
<NxIcon
aria-hidden="true"
className="size-10 text-slate-900 dark:text-white"
/>
<CircleStackIcon
aria-hidden="true"
className="absolute bottom-1 right-1 size-4 text-slate-900 dark:text-white"
/>
</Card>
</div>
<div className="flex w-full flex-col items-stretch gap-4">
<Card
ref={awsRef}
onMouseEnter={() => {
setAutoplay(false);
setSelected('aws');
}}
onMouseLeave={() => {
setAutoplay(true);
setSelected(null);
}}
className={cx(
'relative transition hover:bg-slate-50 dark:hover:bg-slate-800',
{ 'bg-slate-50 dark:bg-slate-800': selected === 'aws' }
)}
>
<AmazonS3Icon aria-hidden="true" className="size-4" />
<Link
href="/nx-api/powerpack-s3-cache"
title="Learn how to configure Amazon S3 caching"
className="text-center text-xs text-slate-900 dark:text-white"
>
<span className="absolute inset-0" />
Amazon S3
</Link>
</Card>
<Card
ref={minioRef}
onMouseEnter={() => {
setAutoplay(false);
setSelected('minio');
}}
onMouseLeave={() => {
setAutoplay(true);
setSelected(null);
}}
className={cx(
'relative transition hover:bg-slate-50 dark:hover:bg-slate-800',
{ 'bg-slate-50 dark:bg-slate-800': selected === 'minio' }
)}
>
<MinIOIcon aria-hidden="true" className="size-4" />
<Link
href="/nx-api/powerpack-s3-cache"
title="Learn how to configure Amazon S3 caching"
className="text-center text-xs text-slate-900 dark:text-white"
>
<span className="absolute inset-0" />
MinIO
</Link>
</Card>
<Card
ref={networkDriveRef}
onMouseEnter={() => {
setAutoplay(false);
setSelected('networkDrive');
}}
onMouseLeave={() => {
setAutoplay(true);
setSelected(null);
}}
className={cx(
'relative transition hover:bg-slate-50 dark:hover:bg-slate-800',
{ 'bg-slate-50 dark:bg-slate-800': selected === 'networkDrive' }
)}
>
<ServerIcon aria-hidden="true" className="size-4" />
<Link
href="/nx-api/powerpack-shared-fs-cache"
title="Learn how to configure network drive caching"
className="text-center text-xs text-slate-900 dark:text-white"
>
<span className="absolute inset-0" />
Network Drive
</Link>
</Card>
<Card
ref={gcpRef}
onMouseEnter={() => {
setAutoplay(false);
setSelected('gcp');
}}
onMouseLeave={() => {
setAutoplay(true);
setSelected(null);
}}
className={cx(
'relative transition hover:bg-slate-50 dark:hover:bg-slate-800',
{ 'bg-slate-50 dark:bg-slate-800': selected === 'gcp' }
)}
>
<GoogleCloudIcon aria-hidden="true" className="size-4" />
<Link
href="/nx-api/powerpack-gcs-cache"
title="Learn how to configure Google Storage caching"
className="text-center text-xs text-slate-900 dark:text-white"
>
<span className="absolute inset-0" />
GCP
</Link>
</Card>
<Card
ref={azureRef}
onMouseEnter={() => {
setAutoplay(false);
setSelected('azure');
}}
onMouseLeave={() => {
setAutoplay(true);
setSelected(null);
}}
className={cx(
'relative transition hover:bg-slate-50 dark:hover:bg-slate-800',
{ 'bg-slate-50 dark:bg-slate-800': selected === 'azure' }
)}
>
<AzureDevOpsIcon aria-hidden="true" className="size-4" />
<Link
href="/nx-api/powerpack-azure-cache"
title="Learn how to configure Azure Blob Storage caching"
className="text-center text-xs text-slate-900 dark:text-white"
>
<span className="absolute inset-0" />
Azure
</Link>
</Card>
</div>
</div>
<AnimatePresence>
{selected ? (
<motion.div
key={selected}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
opacity: { duration: 0.2 },
}}
>
{animatedBeamMap[selected]}
</motion.div>
) : null}
</AnimatePresence>
</div>
);
}