mirror of
https://github.com/netbirdio/docs.git
synced 2026-04-18 00:16:36 +00:00
add first version of tailwind docs
This commit is contained in:
65
src/components/Button.jsx
Normal file
65
src/components/Button.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
function ArrowIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
primary:
|
||||
'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-1 dark:ring-inset dark:ring-orange-400/20 dark:hover:bg-orange-400/10 dark:hover:text-orange-300 dark:hover:ring-orange-300',
|
||||
// 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-400/10 dark:text-emerald-400 dark:ring-1 dark:ring-inset dark:ring-emerald-400/20 dark:hover:bg-emerald-400/10 dark:hover:text-emerald-300 dark:hover:ring-emerald-300',
|
||||
secondary:
|
||||
'rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300',
|
||||
filled:
|
||||
'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-orange-500 dark:text-white dark:hover:bg-orange-400',
|
||||
// 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-500 dark:text-white dark:hover:bg-emerald-400',
|
||||
outline:
|
||||
'rounded-full py-1 px-3 text-zinc-700 ring-1 ring-inset ring-zinc-900/10 hover:bg-zinc-900/2.5 hover:text-zinc-900 dark:text-zinc-400 dark:ring-white/10 dark:hover:bg-white/5 dark:hover:text-white',
|
||||
text: 'text-orange-500 hover:text-orange-600 dark:text-orange-400 dark:hover:text-orange-500',
|
||||
// text: 'text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-500',
|
||||
}
|
||||
|
||||
export function Button({
|
||||
variant = 'primary',
|
||||
className,
|
||||
children,
|
||||
arrow,
|
||||
...props
|
||||
}) {
|
||||
let Component = props.href ? Link : 'button'
|
||||
|
||||
className = clsx(
|
||||
'inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition',
|
||||
variantStyles[variant],
|
||||
className
|
||||
)
|
||||
|
||||
let arrowIcon = (
|
||||
<ArrowIcon
|
||||
className={clsx(
|
||||
'mt-0.5 h-5 w-5',
|
||||
variant === 'text' && 'relative top-px',
|
||||
arrow === 'left' && '-ml-1 rotate-180',
|
||||
arrow === 'right' && '-mr-1'
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Component className={className} {...props}>
|
||||
{arrow === 'left' && arrowIcon}
|
||||
{children}
|
||||
{arrow === 'right' && arrowIcon}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
299
src/components/Code.jsx
Normal file
299
src/components/Code.jsx
Normal file
@@ -0,0 +1,299 @@
|
||||
import {
|
||||
Children,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Tab } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import { create } from 'zustand'
|
||||
|
||||
import { Tag } from '@/components/Tag'
|
||||
|
||||
const languageNames = {
|
||||
js: 'JavaScript',
|
||||
ts: 'TypeScript',
|
||||
javascript: 'JavaScript',
|
||||
typescript: 'TypeScript',
|
||||
php: 'PHP',
|
||||
python: 'Python',
|
||||
ruby: 'Ruby',
|
||||
go: 'Go',
|
||||
}
|
||||
|
||||
function getPanelTitle({ title, language }) {
|
||||
return title ?? languageNames[language] ?? 'Code'
|
||||
}
|
||||
|
||||
function ClipboardIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinejoin="round"
|
||||
d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function CopyButton({ code }) {
|
||||
let [copyCount, setCopyCount] = useState(0)
|
||||
let copied = copyCount > 0
|
||||
|
||||
useEffect(() => {
|
||||
if (copyCount > 0) {
|
||||
let timeout = setTimeout(() => setCopyCount(0), 1000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [copyCount])
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
'group/button absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 text-2xs font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100',
|
||||
copied
|
||||
? 'bg-orange-400/10 ring-1 ring-inset ring-orange-400/20'
|
||||
// ? 'bg-emerald-400/10 ring-1 ring-inset ring-emerald-400/20'
|
||||
: 'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5'
|
||||
)}
|
||||
onClick={() => {
|
||||
window.navigator.clipboard.writeText(code).then(() => {
|
||||
setCopyCount((count) => count + 1)
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span
|
||||
aria-hidden={copied}
|
||||
className={clsx(
|
||||
'pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300',
|
||||
copied && '-translate-y-1.5 opacity-0'
|
||||
)}
|
||||
>
|
||||
<ClipboardIcon className="h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400" />
|
||||
Copy
|
||||
</span>
|
||||
<span
|
||||
aria-hidden={!copied}
|
||||
className={clsx(
|
||||
'pointer-events-none absolute inset-0 flex items-center justify-center text-orange-400 transition duration-300',
|
||||
!copied && 'translate-y-1.5 opacity-0'
|
||||
)}
|
||||
>
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function CodePanelHeader({ tag, label }) {
|
||||
if (!tag && !label) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-9 items-center gap-2 border-y border-b-white/7.5 border-t-transparent bg-white/2.5 bg-zinc-900 px-4 dark:border-b-white/5 dark:bg-white/1">
|
||||
{tag && (
|
||||
<div className="dark flex">
|
||||
<Tag variant="small">{tag}</Tag>
|
||||
</div>
|
||||
)}
|
||||
{tag && label && (
|
||||
<span className="h-0.5 w-0.5 rounded-full bg-zinc-500" />
|
||||
)}
|
||||
{label && (
|
||||
<span className="font-mono text-xs text-zinc-400">{label}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CodePanel({ tag, label, code, children }) {
|
||||
let child = Children.only(children)
|
||||
|
||||
return (
|
||||
<div className="group dark:bg-white/2.5">
|
||||
<CodePanelHeader
|
||||
tag={child.props.tag ?? tag}
|
||||
label={child.props.label ?? label}
|
||||
/>
|
||||
<div className="relative">
|
||||
<pre className="overflow-x-auto p-4 text-xs text-white">{children}</pre>
|
||||
<CopyButton code={child.props.code ?? code} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeGroupHeader({ title, children, selectedIndex }) {
|
||||
let hasTabs = Children.count(children) > 1
|
||||
|
||||
if (!title && !hasTabs) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b border-zinc-700 bg-zinc-800 px-4 dark:border-zinc-800 dark:bg-transparent">
|
||||
{title && (
|
||||
<h3 className="mr-auto pt-3 text-xs font-semibold text-white">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
{hasTabs && (
|
||||
<Tab.List className="-mb-px flex gap-4 text-xs font-medium">
|
||||
{Children.map(children, (child, childIndex) => (
|
||||
<Tab
|
||||
className={clsx(
|
||||
'border-b py-3 transition focus:[&:not(:focus-visible)]:outline-none',
|
||||
childIndex === selectedIndex
|
||||
// ? 'border-emerald-500 text-emerald-400'
|
||||
? 'border-orange-500 text-orange-400'
|
||||
: 'border-transparent text-zinc-400 hover:text-zinc-300'
|
||||
)}
|
||||
>
|
||||
{getPanelTitle(child.props)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeGroupPanels({ children, ...props }) {
|
||||
let hasTabs = Children.count(children) > 1
|
||||
|
||||
if (hasTabs) {
|
||||
return (
|
||||
<Tab.Panels>
|
||||
{Children.map(children, (child) => (
|
||||
<Tab.Panel>
|
||||
<CodePanel {...props}>{child}</CodePanel>
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
)
|
||||
}
|
||||
|
||||
return <CodePanel {...props}>{children}</CodePanel>
|
||||
}
|
||||
|
||||
function usePreventLayoutShift() {
|
||||
let positionRef = useRef()
|
||||
let rafRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
window.cancelAnimationFrame(rafRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
positionRef,
|
||||
preventLayoutShift(callback) {
|
||||
let initialTop = positionRef.current.getBoundingClientRect().top
|
||||
|
||||
callback()
|
||||
|
||||
rafRef.current = window.requestAnimationFrame(() => {
|
||||
let newTop = positionRef.current.getBoundingClientRect().top
|
||||
window.scrollBy(0, newTop - initialTop)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const usePreferredLanguageStore = create((set) => ({
|
||||
preferredLanguages: [],
|
||||
addPreferredLanguage: (language) =>
|
||||
set((state) => ({
|
||||
preferredLanguages: [
|
||||
...state.preferredLanguages.filter(
|
||||
(preferredLanguage) => preferredLanguage !== language
|
||||
),
|
||||
language,
|
||||
],
|
||||
})),
|
||||
}))
|
||||
|
||||
function useTabGroupProps(availableLanguages) {
|
||||
let { preferredLanguages, addPreferredLanguage } = usePreferredLanguageStore()
|
||||
let [selectedIndex, setSelectedIndex] = useState(0)
|
||||
let activeLanguage = [...availableLanguages].sort(
|
||||
(a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a)
|
||||
)[0]
|
||||
let languageIndex = availableLanguages.indexOf(activeLanguage)
|
||||
let newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex
|
||||
if (newSelectedIndex !== selectedIndex) {
|
||||
setSelectedIndex(newSelectedIndex)
|
||||
}
|
||||
|
||||
let { positionRef, preventLayoutShift } = usePreventLayoutShift()
|
||||
|
||||
return {
|
||||
as: 'div',
|
||||
ref: positionRef,
|
||||
selectedIndex,
|
||||
onChange: (newSelectedIndex) => {
|
||||
preventLayoutShift(() =>
|
||||
addPreferredLanguage(availableLanguages[newSelectedIndex])
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const CodeGroupContext = createContext(false)
|
||||
|
||||
export function CodeGroup({ children, title, ...props }) {
|
||||
let languages = Children.map(children, (child) => getPanelTitle(child.props))
|
||||
let tabGroupProps = useTabGroupProps(languages)
|
||||
let hasTabs = Children.count(children) > 1
|
||||
let Container = hasTabs ? Tab.Group : 'div'
|
||||
let containerProps = hasTabs ? tabGroupProps : {}
|
||||
let headerProps = hasTabs
|
||||
? { selectedIndex: tabGroupProps.selectedIndex }
|
||||
: {}
|
||||
|
||||
return (
|
||||
<CodeGroupContext.Provider value={true}>
|
||||
<Container
|
||||
{...containerProps}
|
||||
className="not-prose my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md dark:ring-1 dark:ring-white/10"
|
||||
>
|
||||
<CodeGroupHeader title={title} {...headerProps}>
|
||||
{children}
|
||||
</CodeGroupHeader>
|
||||
<CodeGroupPanels {...props}>{children}</CodeGroupPanels>
|
||||
</Container>
|
||||
</CodeGroupContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function Code({ children, ...props }) {
|
||||
let isGrouped = useContext(CodeGroupContext)
|
||||
|
||||
if (isGrouped) {
|
||||
return <code {...props} dangerouslySetInnerHTML={{ __html: children }} />
|
||||
}
|
||||
|
||||
return <code {...props}>{children}</code>
|
||||
}
|
||||
|
||||
export function Pre({ children, ...props }) {
|
||||
let isGrouped = useContext(CodeGroupContext)
|
||||
|
||||
if (isGrouped) {
|
||||
return children
|
||||
}
|
||||
|
||||
return <CodeGroup {...props}>{children}</CodeGroup>
|
||||
}
|
||||
230
src/components/Footer.jsx
Normal file
230
src/components/Footer.jsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import {forwardRef, Fragment, useEffect, useState} from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Transition } from '@headlessui/react'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import {apiNavigation, docsNavigation, navigation} from '@/components/Navigation'
|
||||
|
||||
function CheckIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<circle cx="10" cy="10" r="10" strokeWidth="0" />
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="m6.75 10.813 2.438 2.437c1.218-4.469 4.062-6.5 4.062-6.5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedbackButton(props) {
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="px-3 text-sm font-medium text-zinc-600 transition hover:bg-zinc-900/2.5 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackForm = forwardRef(function FeedbackForm({ onSubmit }, ref) {
|
||||
return (
|
||||
<form
|
||||
ref={ref}
|
||||
onSubmit={onSubmit}
|
||||
className="absolute inset-0 flex items-center justify-center gap-6 md:justify-start"
|
||||
>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Was this page helpful?
|
||||
</p>
|
||||
<div className="group grid h-8 grid-cols-[1fr,1px,1fr] overflow-hidden rounded-full border border-zinc-900/10 dark:border-white/10">
|
||||
<FeedbackButton data-response="yes">Yes</FeedbackButton>
|
||||
<div className="bg-zinc-900/10 dark:bg-white/10" />
|
||||
<FeedbackButton data-response="no">No</FeedbackButton>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
})
|
||||
|
||||
const FeedbackThanks = forwardRef(function FeedbackThanks(_props, ref) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="absolute inset-0 flex justify-center md:justify-start"
|
||||
>
|
||||
<div className="flex items-center gap-3 rounded-full bg-orange-50/50 py-1 pl-1.5 pr-3 text-sm text-orange-900 ring-1 ring-inset ring-orange-500/20 dark:bg-orange-500/5 dark:text-orange-200 dark:ring-orange-500/30">
|
||||
{/*<div className="flex items-center gap-3 rounded-full bg-emerald-50/50 py-1 pl-1.5 pr-3 text-sm text-emerald-900 ring-1 ring-inset ring-emerald-500/20 dark:bg-emerald-500/5 dark:text-emerald-200 dark:ring-emerald-500/30">*/}
|
||||
<CheckIcon className="h-5 w-5 flex-none fill-orange-500 stroke-white dark:fill-orange-200/20 dark:stroke-orange-200" />
|
||||
{/*<CheckIcon className="h-5 w-5 flex-none fill-emerald-500 stroke-white dark:fill-emerald-200/20 dark:stroke-emerald-200" />*/}
|
||||
Thanks for your feedback!
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
function Feedback() {
|
||||
let [submitted, setSubmitted] = useState(false)
|
||||
|
||||
function onSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
// event.nativeEvent.submitter.dataset.response
|
||||
// => "yes" or "no"
|
||||
|
||||
setSubmitted(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative h-8">
|
||||
<Transition
|
||||
show={!submitted}
|
||||
as={Fragment}
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
leave="pointer-events-none duration-300"
|
||||
>
|
||||
<FeedbackForm onSubmit={onSubmit} />
|
||||
</Transition>
|
||||
<Transition
|
||||
show={submitted}
|
||||
as={Fragment}
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
enter="delay-150 duration-300"
|
||||
>
|
||||
<FeedbackThanks />
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PageLink({ label, page, previous = false }) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
href={page.href}
|
||||
aria-label={`${label}: ${page.title}`}
|
||||
variant="secondary"
|
||||
arrow={previous ? 'left' : 'right'}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
<Link
|
||||
href={page.href}
|
||||
tabIndex={-1}
|
||||
aria-hidden="true"
|
||||
className="text-base font-semibold text-zinc-900 transition hover:text-zinc-600 dark:text-white dark:hover:text-zinc-300"
|
||||
>
|
||||
{page.title}
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PageNavigation() {
|
||||
let router = useRouter()
|
||||
let allPages = router.route.startsWith('/docs') ? docsNavigation.flatMap((group) => group.links) : apiNavigation.flatMap((group) => group.links)
|
||||
let currentPageIndex = allPages.findIndex(
|
||||
(page) => page.href === router.pathname
|
||||
)
|
||||
|
||||
if (currentPageIndex === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
let previousPage = allPages[currentPageIndex - 1]
|
||||
let nextPage = allPages[currentPageIndex + 1]
|
||||
|
||||
if (!previousPage && !nextPage) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
{previousPage && (
|
||||
<div className="flex flex-col items-start gap-3">
|
||||
<PageLink label="Previous" page={previousPage} previous />
|
||||
</div>
|
||||
)}
|
||||
{nextPage && (
|
||||
<div className="ml-auto flex flex-col items-end gap-3">
|
||||
<PageLink label="Next" page={nextPage} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TwitterIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path d="M16.712 6.652c.01.146.01.29.01.436 0 4.449-3.267 9.579-9.242 9.579v-.003a8.963 8.963 0 0 1-4.98-1.509 6.379 6.379 0 0 0 4.807-1.396c-1.39-.027-2.608-.966-3.035-2.337.487.097.99.077 1.467-.059-1.514-.316-2.606-1.696-2.606-3.3v-.041c.45.26.956.404 1.475.42C3.18 7.454 2.74 5.486 3.602 3.947c1.65 2.104 4.083 3.382 6.695 3.517a3.446 3.446 0 0 1 .94-3.217 3.172 3.172 0 0 1 4.596.148 6.38 6.38 0 0 0 2.063-.817 3.357 3.357 0 0 1-1.428 1.861 6.283 6.283 0 0 0 1.865-.53 6.735 6.735 0 0 1-1.62 1.744Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function GitHubIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DiscordIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path d="M16.238 4.515a14.842 14.842 0 0 0-3.664-1.136.055.055 0 0 0-.059.027 10.35 10.35 0 0 0-.456.938 13.702 13.702 0 0 0-4.115 0 9.479 9.479 0 0 0-.464-.938.058.058 0 0 0-.058-.027c-1.266.218-2.497.6-3.664 1.136a.052.052 0 0 0-.024.02C1.4 8.023.76 11.424 1.074 14.782a.062.062 0 0 0 .024.042 14.923 14.923 0 0 0 4.494 2.272.058.058 0 0 0 .064-.02c.346-.473.654-.972.92-1.496a.057.057 0 0 0-.032-.08 9.83 9.83 0 0 1-1.404-.669.058.058 0 0 1-.029-.046.058.058 0 0 1 .023-.05c.094-.07.189-.144.279-.218a.056.056 0 0 1 .058-.008c2.946 1.345 6.135 1.345 9.046 0a.056.056 0 0 1 .059.007c.09.074.184.149.28.22a.058.058 0 0 1 .023.049.059.059 0 0 1-.028.046 9.224 9.224 0 0 1-1.405.669.058.058 0 0 0-.033.033.056.056 0 0 0 .002.047c.27.523.58 1.022.92 1.495a.056.056 0 0 0 .062.021 14.878 14.878 0 0 0 4.502-2.272.055.055 0 0 0 .016-.018.056.056 0 0 0 .008-.023c.375-3.883-.63-7.256-2.662-10.246a.046.046 0 0 0-.023-.021Zm-9.223 8.221c-.887 0-1.618-.814-1.618-1.814s.717-1.814 1.618-1.814c.908 0 1.632.821 1.618 1.814 0 1-.717 1.814-1.618 1.814Zm5.981 0c-.887 0-1.618-.814-1.618-1.814s.717-1.814 1.618-1.814c.908 0 1.632.821 1.618 1.814 0 1-.71 1.814-1.618 1.814Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SocialLink({ href, icon: Icon, children }) {
|
||||
return (
|
||||
<Link href={href} className="group">
|
||||
<span className="sr-only">{children}</span>
|
||||
<Icon className="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 dark:group-hover:fill-zinc-500" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function SmallPrint() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 dark:border-white/5 sm:flex-row">
|
||||
<p className="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
© Copyright {new Date().getFullYear()}. All rights reserved.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<SocialLink href="#" icon={TwitterIcon}>
|
||||
Follow us on Twitter
|
||||
</SocialLink>
|
||||
<SocialLink href="#" icon={GitHubIcon}>
|
||||
Follow us on GitHub
|
||||
</SocialLink>
|
||||
<SocialLink href="#" icon={DiscordIcon}>
|
||||
Join our Discord server
|
||||
</SocialLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
let router = useRouter()
|
||||
|
||||
return (
|
||||
<footer className="mx-auto max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
|
||||
<Feedback key={router.pathname} />
|
||||
<PageNavigation />
|
||||
<SmallPrint />
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
42
src/components/GridPattern.jsx
Normal file
42
src/components/GridPattern.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useId } from 'react'
|
||||
|
||||
export function GridPattern({ width, height, x, y, squares, ...props }) {
|
||||
let patternId = useId()
|
||||
|
||||
return (
|
||||
<svg aria-hidden="true" {...props}>
|
||||
<defs>
|
||||
<pattern
|
||||
id={patternId}
|
||||
width={width}
|
||||
height={height}
|
||||
patternUnits="userSpaceOnUse"
|
||||
x={x}
|
||||
y={y}
|
||||
>
|
||||
<path d={`M.5 ${height}V.5H${width}`} fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect
|
||||
width="100%"
|
||||
height="100%"
|
||||
strokeWidth={0}
|
||||
fill={`url(#${patternId})`}
|
||||
/>
|
||||
{squares && (
|
||||
<svg x={x} y={y} className="overflow-visible">
|
||||
{squares.map(([x, y]) => (
|
||||
<rect
|
||||
strokeWidth="0"
|
||||
key={`${x}-${y}`}
|
||||
width={width + 1}
|
||||
height={height + 1}
|
||||
x={x * width}
|
||||
y={y * height}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
54
src/components/Guides.jsx
Normal file
54
src/components/Guides.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Button } from '@/components/Button'
|
||||
import { Heading } from '@/components/Heading'
|
||||
|
||||
const guides = [
|
||||
{
|
||||
href: '/authentication',
|
||||
name: 'Authentication',
|
||||
description: 'Learn how to authenticate your API requests.',
|
||||
},
|
||||
{
|
||||
href: '/pagination',
|
||||
name: 'Pagination',
|
||||
description: 'Understand how to work with paginated responses.',
|
||||
},
|
||||
{
|
||||
href: '/errors',
|
||||
name: 'Errors',
|
||||
description:
|
||||
'Read about the different types of errors returned by the API.',
|
||||
},
|
||||
{
|
||||
href: '/webhooks',
|
||||
name: 'Webhooks',
|
||||
description:
|
||||
'Learn how to programmatically configure webhooks for your app.',
|
||||
},
|
||||
]
|
||||
|
||||
export function Guides() {
|
||||
return (
|
||||
<div className="my-16 xl:max-w-none">
|
||||
<Heading level={2} id="guides">
|
||||
Guides
|
||||
</Heading>
|
||||
<div className="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 dark:border-white/5 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{guides.map((guide) => (
|
||||
<div key={guide.href}>
|
||||
<h3 className="text-sm font-semibold text-zinc-900 dark:text-white">
|
||||
{guide.name}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{guide.description}
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
<Button href={guide.href} variant="text" arrow="right">
|
||||
Read more
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
90
src/components/Header.jsx
Normal file
90
src/components/Header.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { forwardRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
import { motion, useScroll, useTransform } from 'framer-motion'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Logo } from '@/components/Logo'
|
||||
import {
|
||||
MobileNavigation,
|
||||
useIsInsideMobileNavigation,
|
||||
} from '@/components/MobileNavigation'
|
||||
import { useMobileNavigationStore } from '@/components/MobileNavigation'
|
||||
import { ModeToggle } from '@/components/ModeToggle'
|
||||
import { MobileSearch, Search } from '@/components/Search'
|
||||
|
||||
function TopLevelNavItem({ href, children }) {
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={href}
|
||||
className="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export const Header = forwardRef(function Header({ className }, ref) {
|
||||
let { isOpen: mobileNavIsOpen } = useMobileNavigationStore()
|
||||
let isInsideMobileNavigation = useIsInsideMobileNavigation()
|
||||
|
||||
let { scrollY } = useScroll()
|
||||
let bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9])
|
||||
let bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8])
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
className,
|
||||
'fixed inset-x-0 top-0 z-50 flex h-14 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80',
|
||||
!isInsideMobileNavigation &&
|
||||
'backdrop-blur-sm dark:backdrop-blur lg:left-72 xl:left-80',
|
||||
isInsideMobileNavigation
|
||||
? 'bg-white dark:bg-zinc-900'
|
||||
: 'bg-white/[var(--bg-opacity-light)] dark:bg-zinc-900/[var(--bg-opacity-dark)]'
|
||||
)}
|
||||
style={{
|
||||
'--bg-opacity-light': bgOpacityLight,
|
||||
'--bg-opacity-dark': bgOpacityDark,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute inset-x-0 top-full h-px transition',
|
||||
(isInsideMobileNavigation || !mobileNavIsOpen) &&
|
||||
'bg-zinc-900/7.5 dark:bg-white/7.5'
|
||||
)}
|
||||
/>
|
||||
<Search />
|
||||
<div className="flex items-center gap-5 lg:hidden">
|
||||
<MobileNavigation />
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-6" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-5">
|
||||
<nav className="hidden md:block">
|
||||
<ul role="list" className="flex items-center gap-8">
|
||||
<TopLevelNavItem href="https://netbird.io/">Home</TopLevelNavItem>
|
||||
<TopLevelNavItem href="/docs/introductions">Docs</TopLevelNavItem>
|
||||
<TopLevelNavItem href="/users">API</TopLevelNavItem>
|
||||
<TopLevelNavItem href="https://netbird.io/blog/">Blog</TopLevelNavItem>
|
||||
<TopLevelNavItem href="https://github.com/netbirdio/netbird">Github</TopLevelNavItem>
|
||||
<TopLevelNavItem href="#">Support</TopLevelNavItem>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15" />
|
||||
<div className="flex gap-4">
|
||||
<MobileSearch />
|
||||
<ModeToggle />
|
||||
</div>
|
||||
<div className="hidden min-[416px]:contents">
|
||||
<Button href="https://app.netbird.io/">Sign in</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
})
|
||||
102
src/components/Heading.jsx
Normal file
102
src/components/Heading.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useInView } from 'framer-motion'
|
||||
|
||||
import { useSectionStore } from '@/components/SectionProvider'
|
||||
import { Tag } from '@/components/Tag'
|
||||
import { remToPx } from '@/lib/remToPx'
|
||||
|
||||
function AnchorIcon(props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function Eyebrow({ tag, label }) {
|
||||
if (!tag && !label) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-3">
|
||||
{tag && <Tag>{tag}</Tag>}
|
||||
{tag && label && (
|
||||
<span className="h-0.5 w-0.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
|
||||
)}
|
||||
{label && (
|
||||
<span className="font-mono text-xs text-zinc-400">{label}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Anchor({ id, inView, children }) {
|
||||
return (
|
||||
<Link
|
||||
href={`#${id}`}
|
||||
className="group text-inherit no-underline hover:text-inherit"
|
||||
>
|
||||
{inView && (
|
||||
<div className="absolute ml-[calc(-1*var(--width))] mt-1 hidden w-[var(--width)] opacity-0 transition [--width:calc(2.625rem+0.5px+50%-min(50%,calc(theme(maxWidth.lg)+theme(spacing.8))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
|
||||
<div className="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 dark:bg-zinc-800 dark:ring-zinc-700 dark:hover:bg-zinc-700 dark:hover:ring-zinc-600">
|
||||
<AnchorIcon className="h-5 w-5 stroke-zinc-500 transition dark:stroke-zinc-400 dark:group-hover/anchor:stroke-white" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export function Heading({
|
||||
level = 2,
|
||||
children,
|
||||
id,
|
||||
tag,
|
||||
label,
|
||||
anchor = true,
|
||||
...props
|
||||
}) {
|
||||
let Component = `h${level}`
|
||||
let ref = useRef()
|
||||
let registerHeading = useSectionStore((s) => s.registerHeading)
|
||||
|
||||
let inView = useInView(ref, {
|
||||
margin: `${remToPx(-3.5)}px 0px 0px 0px`,
|
||||
amount: 'all',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (level === 2) {
|
||||
registerHeading({ id, ref, offsetRem: tag || label ? 8 : 6 })
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Eyebrow tag={tag} label={label} />
|
||||
<Component
|
||||
ref={ref}
|
||||
id={anchor ? id : undefined}
|
||||
className={tag || label ? 'mt-2 scroll-mt-32' : 'scroll-mt-24'}
|
||||
{...props}
|
||||
>
|
||||
{anchor ? (
|
||||
<Anchor id={id} inView={inView}>
|
||||
{children}
|
||||
</Anchor>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Component>
|
||||
</>
|
||||
)
|
||||
}
|
||||
32
src/components/HeroPattern.jsx
Normal file
32
src/components/HeroPattern.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { GridPattern } from '@/components/GridPattern'
|
||||
|
||||
export function HeroPattern() {
|
||||
return (
|
||||
<div className="absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden">
|
||||
<div className="absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem] dark:[mask-image:linear-gradient(white,transparent)]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#F39C12] to-[#FAD7A0] opacity-40 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-[#F39C12]/30 dark:to-[#FAD7A0]/30 dark:opacity-100">
|
||||
<GridPattern
|
||||
width={72}
|
||||
height={56}
|
||||
x="-12"
|
||||
y="4"
|
||||
squares={[
|
||||
[4, 3],
|
||||
[2, 1],
|
||||
[7, 3],
|
||||
[10, 6],
|
||||
]}
|
||||
className="absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay dark:fill-white/2.5 dark:stroke-white/5"
|
||||
/>
|
||||
</div>
|
||||
<svg
|
||||
viewBox="0 0 1113 440"
|
||||
aria-hidden="true"
|
||||
className="absolute left-1/2 top-0 ml-[-19rem] w-[69.5625rem] fill-white blur-[26px] dark:hidden"
|
||||
>
|
||||
<path d="M.016 439.5s-9.5-300 434-300S882.516 20 882.516 20V0h230.004v439.5H.016Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
38
src/components/Layout.jsx
Normal file
38
src/components/Layout.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import Link from 'next/link'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Header } from '@/components/Header'
|
||||
import { Logo } from '@/components/Logo'
|
||||
import { Navigation } from '@/components/Navigation'
|
||||
import { Prose } from '@/components/Prose'
|
||||
import { SectionProvider } from '@/components/SectionProvider'
|
||||
|
||||
export function Layout({ children, sections = [] }) {
|
||||
return (
|
||||
<SectionProvider sections={sections}>
|
||||
<div className="lg:ml-72 xl:ml-80">
|
||||
<motion.header
|
||||
layoutScroll
|
||||
className="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex"
|
||||
>
|
||||
<div className="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-zinc-900/10 lg:px-6 lg:pb-8 lg:pt-4 lg:dark:border-white/10 xl:w-80">
|
||||
<div className="hidden lg:flex">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-6" />
|
||||
</Link>
|
||||
</div>
|
||||
<Header />
|
||||
<Navigation className="hidden lg:mt-10 lg:block" />
|
||||
</div>
|
||||
</motion.header>
|
||||
<div className="relative px-4 pt-14 sm:px-6 lg:px-8">
|
||||
<main className="py-16">
|
||||
<Prose as="article">{children}</Prose>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</SectionProvider>
|
||||
)
|
||||
}
|
||||
82
src/components/Libraries.jsx
Normal file
82
src/components/Libraries.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import logoGo from '@/images/logos/go.svg'
|
||||
import logoNode from '@/images/logos/node.svg'
|
||||
import logoPhp from '@/images/logos/php.svg'
|
||||
import logoPython from '@/images/logos/python.svg'
|
||||
import logoRuby from '@/images/logos/ruby.svg'
|
||||
|
||||
const libraries = [
|
||||
{
|
||||
href: '#',
|
||||
name: 'PHP',
|
||||
description:
|
||||
'A popular general-purpose scripting language that is especially suited to web development.',
|
||||
logo: logoPhp,
|
||||
},
|
||||
{
|
||||
href: '#',
|
||||
name: 'Ruby',
|
||||
description:
|
||||
'A dynamic, open source programming language with a focus on simplicity and productivity.',
|
||||
logo: logoRuby,
|
||||
},
|
||||
{
|
||||
href: '#',
|
||||
name: 'Node.js',
|
||||
description:
|
||||
'Node.js® is an open-source, cross-platform JavaScript runtime environment.',
|
||||
logo: logoNode,
|
||||
},
|
||||
{
|
||||
href: '#',
|
||||
name: 'Python',
|
||||
description:
|
||||
'Python is a programming language that lets you work quickly and integrate systems more effectively.',
|
||||
logo: logoPython,
|
||||
},
|
||||
{
|
||||
href: '#',
|
||||
name: 'Go',
|
||||
description:
|
||||
'An open-source programming language supported by Google with built-in concurrency.',
|
||||
logo: logoGo,
|
||||
},
|
||||
]
|
||||
|
||||
export function Libraries() {
|
||||
return (
|
||||
<div className="my-16 xl:max-w-none">
|
||||
<Heading level={2} id="official-libraries">
|
||||
Official libraries
|
||||
</Heading>
|
||||
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-t border-zinc-900/5 pt-10 dark:border-white/5 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3">
|
||||
{libraries.map((library) => (
|
||||
<div key={library.name} className="flex flex-row-reverse gap-6">
|
||||
<div className="flex-auto">
|
||||
<h3 className="text-sm font-semibold text-zinc-900 dark:text-white">
|
||||
{library.name}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{library.description}
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
<Button href={library.href} variant="text" arrow="right">
|
||||
Read more
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
<Image
|
||||
src={library.logo}
|
||||
alt=""
|
||||
className="h-12 w-12"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
10
src/components/Logo.jsx
Normal file
10
src/components/Logo.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export function Logo(props) {
|
||||
return (
|
||||
<div>
|
||||
<img src='/img/logo.svg' alt="some file" height='200'
|
||||
width='180' className="dark:hidden"/>
|
||||
<img src='/img/logo-dark.svg' alt="some file" height='200'
|
||||
width='180' className="hidden dark:block"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
115
src/components/MobileNavigation.jsx
Normal file
115
src/components/MobileNavigation.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { createContext, Fragment, useContext } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { create } from 'zustand'
|
||||
|
||||
import { Header } from '@/components/Header'
|
||||
import { Navigation } from '@/components/Navigation'
|
||||
|
||||
function MenuIcon(props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 10 9"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M.5 1h9M.5 8h9M.5 4.5h9" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function XIcon(props) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 10 9"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="m1.5 1 7 7M8.5 1l-7 7" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const IsInsideMobileNavigationContext = createContext(false)
|
||||
|
||||
export function useIsInsideMobileNavigation() {
|
||||
return useContext(IsInsideMobileNavigationContext)
|
||||
}
|
||||
|
||||
export const useMobileNavigationStore = create((set) => ({
|
||||
isOpen: false,
|
||||
open: () => set({ isOpen: true }),
|
||||
close: () => set({ isOpen: false }),
|
||||
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
|
||||
}))
|
||||
|
||||
export function MobileNavigation() {
|
||||
let isInsideMobileNavigation = useIsInsideMobileNavigation()
|
||||
let { isOpen, toggle, close } = useMobileNavigationStore()
|
||||
let ToggleIcon = isOpen ? XIcon : MenuIcon
|
||||
|
||||
return (
|
||||
<IsInsideMobileNavigationContext.Provider value={true}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
||||
aria-label="Toggle navigation"
|
||||
onClick={toggle}
|
||||
>
|
||||
<ToggleIcon className="w-2.5 stroke-zinc-900 dark:stroke-white" />
|
||||
</button>
|
||||
{!isInsideMobileNavigation && (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog onClose={close} className="fixed inset-0 z-50 lg:hidden">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="duration-300 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 top-14 bg-zinc-400/20 backdrop-blur-sm dark:bg-black/40" />
|
||||
</Transition.Child>
|
||||
|
||||
<Dialog.Panel>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="duration-300 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Header />
|
||||
</Transition.Child>
|
||||
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="duration-500 ease-in-out"
|
||||
enterFrom="-translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="duration-500 ease-in-out"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="-translate-x-full"
|
||||
>
|
||||
<motion.div
|
||||
layoutScroll
|
||||
className="fixed bottom-0 left-0 top-14 w-full overflow-y-auto bg-white px-4 pb-4 pt-6 shadow-lg shadow-zinc-900/10 ring-1 ring-zinc-900/7.5 dark:bg-zinc-900 dark:ring-zinc-800 min-[416px]:max-w-sm sm:px-6 sm:pb-10"
|
||||
>
|
||||
<Navigation />
|
||||
</motion.div>
|
||||
</Transition.Child>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)}
|
||||
</IsInsideMobileNavigationContext.Provider>
|
||||
)
|
||||
}
|
||||
54
src/components/ModeToggle.jsx
Normal file
54
src/components/ModeToggle.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
function SunIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
d="M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MoonIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModeToggle() {
|
||||
function disableTransitionsTemporarily() {
|
||||
document.documentElement.classList.add('[&_*]:!transition-none')
|
||||
window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('[&_*]:!transition-none')
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
disableTransitionsTemporarily()
|
||||
|
||||
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
let isSystemDarkMode = darkModeMediaQuery.matches
|
||||
let isDarkMode = document.documentElement.classList.toggle('dark')
|
||||
|
||||
if (isDarkMode === isSystemDarkMode) {
|
||||
delete window.localStorage.isDarkMode
|
||||
} else {
|
||||
window.localStorage.isDarkMode = isDarkMode
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
|
||||
aria-label="Toggle dark mode"
|
||||
onClick={toggleMode}
|
||||
>
|
||||
<SunIcon className="h-5 w-5 stroke-zinc-900 dark:hidden" />
|
||||
<MoonIcon className="hidden h-5 w-5 stroke-white dark:block" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
420
src/components/Navigation.jsx
Normal file
420
src/components/Navigation.jsx
Normal file
@@ -0,0 +1,420 @@
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import clsx from 'clsx'
|
||||
import { AnimatePresence, motion, useIsPresent } from 'framer-motion'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { useIsInsideMobileNavigation } from '@/components/MobileNavigation'
|
||||
import { useSectionStore } from '@/components/SectionProvider'
|
||||
import { Tag } from '@/components/Tag'
|
||||
import { remToPx } from '@/lib/remToPx'
|
||||
|
||||
function useInitialValue(value, condition = true) {
|
||||
let initialValue = useRef(value).current
|
||||
return condition ? initialValue : value
|
||||
}
|
||||
|
||||
function TopLevelNavItem({ href, children }) {
|
||||
return (
|
||||
<li className="md:hidden">
|
||||
<Link
|
||||
href={href}
|
||||
className="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function NavLink({ href, tag, active, isAnchorLink = false, children }) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={clsx(
|
||||
'flex justify-between gap-2 py-1 pr-3 text-sm transition',
|
||||
isAnchorLink ? 'pl-7' : 'pl-4',
|
||||
active
|
||||
? 'text-zinc-900 dark:text-white'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{children}</span>
|
||||
{tag && (
|
||||
<Tag variant="small" color="zinc">
|
||||
{tag}
|
||||
</Tag>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function VisibleSectionHighlight({ group, pathname }) {
|
||||
let [sections, visibleSections] = useInitialValue(
|
||||
[
|
||||
useSectionStore((s) => s.sections),
|
||||
useSectionStore((s) => s.visibleSections),
|
||||
],
|
||||
useIsInsideMobileNavigation()
|
||||
)
|
||||
|
||||
let isPresent = useIsPresent()
|
||||
let firstVisibleSectionIndex = Math.max(
|
||||
0,
|
||||
[{ id: '_top' }, ...sections].findIndex(
|
||||
(section) => section.id === visibleSections[0]
|
||||
)
|
||||
)
|
||||
let itemHeight = remToPx(2)
|
||||
let height = isPresent
|
||||
? Math.max(1, visibleSections.length) * itemHeight
|
||||
: itemHeight
|
||||
let top =
|
||||
group.links.findIndex((link) => link.href === pathname) * itemHeight +
|
||||
firstVisibleSectionIndex * itemHeight
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 0.2 } }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-x-0 top-0 bg-zinc-800/2.5 will-change-transform dark:bg-white/2.5"
|
||||
style={{ borderRadius: 8, height, top }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ActivePageMarker({ group, pathname }) {
|
||||
let itemHeight = remToPx(2)
|
||||
let offset = remToPx(0.25)
|
||||
let activePageIndex = group.links.findIndex((link) => link.href === pathname)
|
||||
let top = offset + activePageIndex * itemHeight
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
className="absolute left-2 h-6 w-px bg-orange-500"
|
||||
// className="absolute left-2 h-6 w-px bg-emerald-500"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 0.2 } }}
|
||||
exit={{ opacity: 0 }}
|
||||
style={{ top }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationGroup({ group, className }) {
|
||||
// If this is the mobile navigation then we always render the initial
|
||||
// state, so that the state does not change during the close animation.
|
||||
// The state will still update when we re-open (re-render) the navigation.
|
||||
let isInsideMobileNavigation = useIsInsideMobileNavigation()
|
||||
let [router, sections] = useInitialValue(
|
||||
[useRouter(), useSectionStore((s) => s.sections)],
|
||||
isInsideMobileNavigation
|
||||
)
|
||||
|
||||
let isActiveGroup =
|
||||
group.links.findIndex((link) => link.href === router.pathname) !== -1
|
||||
|
||||
return (
|
||||
<li className={clsx('relative mt-6', className)}>
|
||||
<motion.h2
|
||||
layout="position"
|
||||
className="text-xs font-semibold text-zinc-900 dark:text-white"
|
||||
>
|
||||
{group.title}
|
||||
</motion.h2>
|
||||
<div className="relative mt-3 pl-2">
|
||||
<AnimatePresence initial={!isInsideMobileNavigation}>
|
||||
{isActiveGroup && (
|
||||
<VisibleSectionHighlight group={group} pathname={router.pathname} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<motion.div
|
||||
layout
|
||||
className="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"
|
||||
/>
|
||||
<AnimatePresence initial={false}>
|
||||
{isActiveGroup && (
|
||||
<ActivePageMarker group={group} pathname={router.pathname} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<ul role="list" className="border-l border-transparent">
|
||||
{group.links.map((link) => (
|
||||
<motion.li key={link.href} layout="position" className="relative">
|
||||
<NavLink href={link.href} active={link.href === router.pathname}>
|
||||
{link.title}
|
||||
</NavLink>
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
{link.href === router.pathname && sections.length > 0 && (
|
||||
<motion.ul
|
||||
role="list"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { delay: 0.1 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.15 },
|
||||
}}
|
||||
>
|
||||
{sections.map((section) => (
|
||||
<li key={section.id}>
|
||||
<NavLink
|
||||
href={`${link.href}#${section.id}`}
|
||||
tag={section.tag}
|
||||
isAnchorLink
|
||||
>
|
||||
{section.title}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</motion.ul>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export const docsNavigation = [
|
||||
{
|
||||
title: 'Guides',
|
||||
links: [
|
||||
{ title: 'Introduction', href: '/docs/introduction' },
|
||||
{ title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
{ title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
{ title: 'Getting Started', href: '/docs/getting-started' },
|
||||
{ title: 'Integrations', href: '/docs/integrations' },
|
||||
{ title: 'Examples', href: '/docs/examples' },
|
||||
{ title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
{ title: 'Reference', href: '/docs/reference' },
|
||||
{ title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
{ title: 'Other', href: '/docs/other' },
|
||||
{ title: 'FAQ', href: '/docs/faq' },
|
||||
],
|
||||
},
|
||||
// {
|
||||
// title: 'Introduction',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'How Netbird Works',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'How-to Guides',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'Getting Started',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'Integrations',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'NetBird vs. Traditional VPN',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'Reference',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'Why Wireguard with NetBird?',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'Other',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: 'FAQ',
|
||||
// links: [
|
||||
// { title: 'Introduction', href: '/docs/introduction' },
|
||||
// { title: 'How Netbird Works', href: '/docs/how-netbird-works' },
|
||||
// { title: 'How-to Guides', href: '/docs/how-to-guides' },
|
||||
// { title: 'Getting Started', href: '/docs/getting-started' },
|
||||
// { title: 'Integrations', href: '/docs/integrations' },
|
||||
// { title: 'Examples', href: '/docs/examples' },
|
||||
// { title: 'NetBird vs. Traditional VPN', href: '/docs/netbird-vs-traditional-vpn' },
|
||||
// { title: 'Reference', href: '/docs/reference' },
|
||||
// { title: 'Why Wireguard with NetBird?', href: '/docs/why-wireguard-with-netbird' },
|
||||
// { title: 'Other', href: '/docs/other' },
|
||||
// { title: 'FAQ', href: '/docs/faq' },
|
||||
// ],
|
||||
// },
|
||||
]
|
||||
|
||||
export const apiNavigation = [
|
||||
{
|
||||
title: 'API',
|
||||
links: [
|
||||
{ title: 'Accounts', href: '/accounts' },
|
||||
{ title: 'Users', href: '/users' },
|
||||
{ title: 'Tokens', href: '/tokens' },
|
||||
{ title: 'Peers', href: '/peers' },
|
||||
{ title: 'Setup Keys', href: '/setup-keys' },
|
||||
{ title: 'Groups', href: '/groups' },
|
||||
{ title: 'Rules', href: '/rules' },
|
||||
{ title: 'Policies', href: '/policies' },
|
||||
{ title: 'Routes', href: '/routes' },
|
||||
{ title: 'DNS', href: '/dns' },
|
||||
{ title: 'Events', href: '/events' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function Navigation(props) {
|
||||
let router = useRouter()
|
||||
return (
|
||||
<nav {...props}>
|
||||
<ul role="list">
|
||||
<TopLevelNavItem href="https://netbird.io/">Home</TopLevelNavItem>
|
||||
<TopLevelNavItem href="/docs/introduction">Docs</TopLevelNavItem>
|
||||
<TopLevelNavItem href="/api/introduction">API</TopLevelNavItem>
|
||||
<TopLevelNavItem href="https://netbird.io/blog/">Blog</TopLevelNavItem>
|
||||
<TopLevelNavItem href="https://github.com/netbirdio/netbird">Github</TopLevelNavItem>
|
||||
<TopLevelNavItem href="#">Support</TopLevelNavItem>
|
||||
{
|
||||
router.route.startsWith('/docs') && docsNavigation.map((group, groupIndex) => (
|
||||
<NavigationGroup
|
||||
key={group.title}
|
||||
group={group}
|
||||
className={groupIndex === 0 && 'md:mt-0'}
|
||||
/>
|
||||
)) ||
|
||||
!router.route.startsWith('/docs') && apiNavigation.map((group, groupIndex) => (
|
||||
<NavigationGroup
|
||||
key={group.title}
|
||||
group={group}
|
||||
className={groupIndex === 0 && 'md:mt-0'}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<li className="sticky bottom-0 z-10 mt-6 min-[416px]:hidden">
|
||||
<Button href="https://app.netbird.io/" variant="filled" className="w-full">
|
||||
Sign in
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
10
src/components/Prose.jsx
Normal file
10
src/components/Prose.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Prose({ as: Component = 'div', className, ...props }) {
|
||||
return (
|
||||
<Component
|
||||
className={clsx(className, 'prose dark:prose-invert')}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
159
src/components/Resources.jsx
Normal file
159
src/components/Resources.jsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import Link from 'next/link'
|
||||
import { motion, useMotionTemplate, useMotionValue } from 'framer-motion'
|
||||
|
||||
import { GridPattern } from '@/components/GridPattern'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { ChatBubbleIcon } from '@/components/icons/ChatBubbleIcon'
|
||||
import { EnvelopeIcon } from '@/components/icons/EnvelopeIcon'
|
||||
import { UserIcon } from '@/components/icons/UserIcon'
|
||||
import { UsersIcon } from '@/components/icons/UsersIcon'
|
||||
|
||||
const resources = [
|
||||
{
|
||||
href: '/contacts',
|
||||
name: 'Contacts',
|
||||
description:
|
||||
'Learn about the contact model and how to create, retrieve, update, delete, and list contacts.',
|
||||
icon: UserIcon,
|
||||
pattern: {
|
||||
y: 16,
|
||||
squares: [
|
||||
[0, 1],
|
||||
[1, 3],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
href: '/conversations',
|
||||
name: 'Conversations',
|
||||
description:
|
||||
'Learn about the conversation model and how to create, retrieve, update, delete, and list conversations.',
|
||||
icon: ChatBubbleIcon,
|
||||
pattern: {
|
||||
y: -6,
|
||||
squares: [
|
||||
[-1, 2],
|
||||
[1, 3],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
href: '/messages',
|
||||
name: 'Messages',
|
||||
description:
|
||||
'Learn about the message model and how to create, retrieve, update, delete, and list messages.',
|
||||
icon: EnvelopeIcon,
|
||||
pattern: {
|
||||
y: 32,
|
||||
squares: [
|
||||
[0, 2],
|
||||
[1, 4],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
href: '/groups',
|
||||
name: 'Groups',
|
||||
description:
|
||||
'Learn about the group model and how to create, retrieve, update, delete, and list groups.',
|
||||
icon: UsersIcon,
|
||||
pattern: {
|
||||
y: 22,
|
||||
squares: [[0, 1]],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
function ResourceIcon({ icon: Icon }) {
|
||||
return (
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-zinc-900/5 ring-1 ring-zinc-900/25 backdrop-blur-[2px] transition duration-300 group-hover:bg-white/50 group-hover:ring-zinc-900/25 dark:bg-white/7.5 dark:ring-white/15 dark:group-hover:bg-orange-300/10 dark:group-hover:ring-orange-400">
|
||||
{/*<div className="flex h-7 w-7 items-center justify-center rounded-full bg-zinc-900/5 ring-1 ring-zinc-900/25 backdrop-blur-[2px] transition duration-300 group-hover:bg-white/50 group-hover:ring-zinc-900/25 dark:bg-white/7.5 dark:ring-white/15 dark:group-hover:bg-emerald-300/10 dark:group-hover:ring-emerald-400">*/}
|
||||
<Icon className="h-5 w-5 fill-zinc-700/10 stroke-zinc-700 transition-colors duration-300 group-hover:stroke-zinc-900 dark:fill-white/10 dark:stroke-zinc-400 dark:group-hover:fill-orange-300/10 dark:group-hover:stroke-orange-400" />
|
||||
{/*<Icon className="h-5 w-5 fill-zinc-700/10 stroke-zinc-700 transition-colors duration-300 group-hover:stroke-zinc-900 dark:fill-white/10 dark:stroke-zinc-400 dark:group-hover:fill-emerald-300/10 dark:group-hover:stroke-emerald-400" />*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ResourcePattern({ mouseX, mouseY, ...gridProps }) {
|
||||
let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)`
|
||||
let style = { maskImage, WebkitMaskImage: maskImage }
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none">
|
||||
<div className="absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50">
|
||||
<GridPattern
|
||||
width={72}
|
||||
height={56}
|
||||
x="50%"
|
||||
className="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5 dark:fill-white/1 dark:stroke-white/2.5"
|
||||
{...gridProps}
|
||||
/>
|
||||
</div>
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-2xl bg-gradient-to-r from-[#D7EDEA] to-[#F4FBDF] opacity-0 transition duration-300 group-hover:opacity-100 dark:from-[#202D2E] dark:to-[#303428]"
|
||||
style={style}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100"
|
||||
style={style}
|
||||
>
|
||||
<GridPattern
|
||||
width={72}
|
||||
height={56}
|
||||
x="50%"
|
||||
className="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 dark:fill-white/2.5 dark:stroke-white/10"
|
||||
{...gridProps}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Resource({ resource }) {
|
||||
let mouseX = useMotionValue(0)
|
||||
let mouseY = useMotionValue(0)
|
||||
|
||||
function onMouseMove({ currentTarget, clientX, clientY }) {
|
||||
let { left, top } = currentTarget.getBoundingClientRect()
|
||||
mouseX.set(clientX - left)
|
||||
mouseY.set(clientY - top)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={resource.href}
|
||||
onMouseMove={onMouseMove}
|
||||
className="group relative flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 dark:bg-white/2.5 dark:hover:shadow-black/5"
|
||||
>
|
||||
<ResourcePattern {...resource.pattern} mouseX={mouseX} mouseY={mouseY} />
|
||||
<div className="absolute inset-0 rounded-2xl ring-1 ring-inset ring-zinc-900/7.5 group-hover:ring-zinc-900/10 dark:ring-white/10 dark:group-hover:ring-white/20" />
|
||||
<div className="relative rounded-2xl px-4 pb-4 pt-16">
|
||||
<ResourceIcon icon={resource.icon} />
|
||||
<h3 className="mt-4 text-sm font-semibold leading-7 text-zinc-900 dark:text-white">
|
||||
<Link href={resource.href}>
|
||||
<span className="absolute inset-0 rounded-2xl" />
|
||||
{resource.name}
|
||||
</Link>
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
{resource.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Resources() {
|
||||
return (
|
||||
<div className="my-16 xl:max-w-none">
|
||||
<Heading level={2} id="resources">
|
||||
Resources
|
||||
</Heading>
|
||||
<div className="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 dark:border-white/5 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{resources.map((resource) => (
|
||||
<Resource key={resource.href} resource={resource} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
517
src/components/Search.jsx
Normal file
517
src/components/Search.jsx
Normal file
@@ -0,0 +1,517 @@
|
||||
import { forwardRef, Fragment, useEffect, useId, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { createAutocomplete } from '@algolia/autocomplete-core'
|
||||
import { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
process.env.NEXT_PUBLIC_DOCSEARCH_APP_ID,
|
||||
process.env.NEXT_PUBLIC_DOCSEARCH_API_KEY
|
||||
)
|
||||
|
||||
function useAutocomplete() {
|
||||
let id = useId()
|
||||
let router = useRouter()
|
||||
let [autocompleteState, setAutocompleteState] = useState({})
|
||||
|
||||
let [autocomplete] = useState(() =>
|
||||
createAutocomplete({
|
||||
id,
|
||||
placeholder: 'Find something...',
|
||||
defaultActiveItemId: 0,
|
||||
onStateChange({ state }) {
|
||||
setAutocompleteState(state)
|
||||
},
|
||||
shouldPanelOpen({ state }) {
|
||||
return state.query !== ''
|
||||
},
|
||||
navigator: {
|
||||
navigate({ itemUrl }) {
|
||||
autocomplete.setIsOpen(true)
|
||||
router.push(itemUrl)
|
||||
},
|
||||
},
|
||||
getSources() {
|
||||
return [
|
||||
{
|
||||
sourceId: 'documentation',
|
||||
getItemInputValue({ item }) {
|
||||
return item.query
|
||||
},
|
||||
getItemUrl({ item }) {
|
||||
let url = new URL(item.url)
|
||||
return `${url.pathname}${url.hash}`
|
||||
},
|
||||
onSelect({ itemUrl }) {
|
||||
router.push(itemUrl)
|
||||
},
|
||||
getItems({ query }) {
|
||||
return getAlgoliaResults({
|
||||
searchClient,
|
||||
queries: [
|
||||
{
|
||||
query,
|
||||
indexName: process.env.NEXT_PUBLIC_DOCSEARCH_INDEX_NAME,
|
||||
params: {
|
||||
hitsPerPage: 5,
|
||||
highlightPreTag:
|
||||
'<mark class="underline bg-transparent text-orange-500">',
|
||||
highlightPostTag: '</mark>',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return { autocomplete, autocompleteState }
|
||||
}
|
||||
|
||||
function resolveResult(result) {
|
||||
let allLevels = Object.keys(result.hierarchy)
|
||||
let hierarchy = Object.entries(result._highlightResult.hierarchy).filter(
|
||||
([, { value }]) => Boolean(value)
|
||||
)
|
||||
let levels = hierarchy.map(([level]) => level)
|
||||
|
||||
let level =
|
||||
result.type === 'content'
|
||||
? levels.pop()
|
||||
: levels
|
||||
.filter(
|
||||
(level) =>
|
||||
allLevels.indexOf(level) <= allLevels.indexOf(result.type)
|
||||
)
|
||||
.pop()
|
||||
|
||||
return {
|
||||
titleHtml: result._highlightResult.hierarchy[level].value,
|
||||
hierarchyHtml: hierarchy
|
||||
.slice(0, levels.indexOf(level))
|
||||
.map(([, { value }]) => value),
|
||||
}
|
||||
}
|
||||
|
||||
function SearchIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function NoResultsIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12.01 12a4.237 4.237 0 0 0 1.24-3c0-.62-.132-1.207-.37-1.738M12.01 12A4.237 4.237 0 0 1 9 13.25c-.635 0-1.237-.14-1.777-.388M12.01 12l3.24 3.25m-3.715-9.661a4.25 4.25 0 0 0-5.975 5.908M4.5 15.5l11-11"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function LoadingIcon(props) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
|
||||
<circle cx="10" cy="10" r="5.5" strokeLinejoin="round" />
|
||||
<path
|
||||
stroke={`url(#${id})`}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.5 10a5.5 5.5 0 1 0-5.5 5.5"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={id}
|
||||
x1="13"
|
||||
x2="9.5"
|
||||
y1="9"
|
||||
y2="15"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="currentColor" />
|
||||
<stop offset="1" stopColor="currentColor" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchResult({ result, resultIndex, autocomplete, collection }) {
|
||||
let id = useId()
|
||||
let { titleHtml, hierarchyHtml } = resolveResult(result)
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
'group block cursor-default px-4 py-3 aria-selected:bg-zinc-50 dark:aria-selected:bg-zinc-800/50',
|
||||
resultIndex > 0 && 'border-t border-zinc-100 dark:border-zinc-800'
|
||||
)}
|
||||
aria-labelledby={`${id}-hierarchy ${id}-title`}
|
||||
{...autocomplete.getItemProps({
|
||||
item: result,
|
||||
source: collection.source,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
id={`${id}-title`}
|
||||
aria-hidden="true"
|
||||
className="text-sm font-medium text-zinc-900 group-aria-selected:text-orange-500 dark:text-white"
|
||||
// className="text-sm font-medium text-zinc-900 group-aria-selected:text-emerald-500 dark:text-white"
|
||||
dangerouslySetInnerHTML={{ __html: titleHtml }}
|
||||
/>
|
||||
{hierarchyHtml.length > 0 && (
|
||||
<div
|
||||
id={`${id}-hierarchy`}
|
||||
aria-hidden="true"
|
||||
className="mt-1 truncate whitespace-nowrap text-2xs text-zinc-500"
|
||||
>
|
||||
{hierarchyHtml.map((item, itemIndex, items) => (
|
||||
<Fragment key={itemIndex}>
|
||||
<span dangerouslySetInnerHTML={{ __html: item }} />
|
||||
<span
|
||||
className={
|
||||
itemIndex === items.length - 1
|
||||
? 'sr-only'
|
||||
: 'mx-2 text-zinc-300 dark:text-zinc-700'
|
||||
}
|
||||
>
|
||||
/
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchResults({ autocomplete, query, collection }) {
|
||||
if (collection.items.length === 0) {
|
||||
return (
|
||||
<div className="p-6 text-center">
|
||||
<NoResultsIcon className="mx-auto h-5 w-5 stroke-zinc-900 dark:stroke-zinc-600" />
|
||||
<p className="mt-2 text-xs text-zinc-700 dark:text-zinc-400">
|
||||
Nothing found for{' '}
|
||||
<strong className="break-words font-semibold text-zinc-900 dark:text-white">
|
||||
‘{query}’
|
||||
</strong>
|
||||
. Please try again.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ul role="list" {...autocomplete.getListProps()}>
|
||||
{collection.items.map((result, resultIndex) => (
|
||||
<SearchResult
|
||||
key={result.objectID}
|
||||
result={result}
|
||||
resultIndex={resultIndex}
|
||||
autocomplete={autocomplete}
|
||||
collection={collection}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchInput = forwardRef(function SearchInput(
|
||||
{ autocomplete, autocompleteState, onClose },
|
||||
inputRef
|
||||
) {
|
||||
let inputProps = autocomplete.getInputProps({})
|
||||
|
||||
return (
|
||||
<div className="group relative flex h-12">
|
||||
<SearchIcon className="pointer-events-none absolute left-3 top-0 h-full w-5 stroke-zinc-500" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={clsx(
|
||||
'flex-auto appearance-none bg-transparent pl-10 text-zinc-900 outline-none placeholder:text-zinc-500 focus:w-full focus:flex-none dark:text-white sm:text-sm [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden [&::-webkit-search-results-button]:hidden [&::-webkit-search-results-decoration]:hidden',
|
||||
autocompleteState.status === 'stalled' ? 'pr-11' : 'pr-4'
|
||||
)}
|
||||
{...inputProps}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
event.key === 'Escape' &&
|
||||
!autocompleteState.isOpen &&
|
||||
autocompleteState.query === ''
|
||||
) {
|
||||
// In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the
|
||||
// bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI.
|
||||
document.activeElement?.blur()
|
||||
|
||||
onClose()
|
||||
} else {
|
||||
inputProps.onKeyDown(event)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{autocompleteState.status === 'stalled' && (
|
||||
<div className="absolute inset-y-0 right-3 flex items-center">
|
||||
<LoadingIcon className="h-5 w-5 animate-spin stroke-zinc-200 text-zinc-900 dark:stroke-zinc-800 dark:text-orange-400" />
|
||||
{/*<LoadingIcon className="h-5 w-5 animate-spin stroke-zinc-200 text-zinc-900 dark:stroke-zinc-800 dark:text-emerald-400" />*/}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
function AlgoliaLogo(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 71 16" role="img" aria-label="Algolia" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M34.98 8.81V.19a.189.189 0 0 0-.218-.186l-1.615.254a.19.19 0 0 0-.16.187l.006 8.741c0 .414 0 2.966 3.07 3.056a.19.19 0 0 0 .195-.19v-1.304a.187.187 0 0 0-.164-.187c-1.115-.128-1.115-1.522-1.115-1.75v-.002Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path d="M61.605 3.352H59.98a.189.189 0 0 0-.189.189v8.514c0 .104.085.189.189.189h1.625a.189.189 0 0 0 .188-.19V3.542a.189.189 0 0 0-.188-.189Z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M59.98 2.285h1.625a.189.189 0 0 0 .188-.189V.19a.189.189 0 0 0-.218-.187l-1.624.255a.189.189 0 0 0-.16.186v1.652c0 .104.085.189.189.189ZM57.172 8.81V.19a.189.189 0 0 0-.218-.186l-1.615.254a.19.19 0 0 0-.16.187l.006 8.741c0 .414 0 2.966 3.07 3.056a.19.19 0 0 0 .196-.19v-1.304a.187.187 0 0 0-.164-.187c-1.115-.128-1.115-1.522-1.115-1.75v-.002ZM52.946 4.568a3.628 3.628 0 0 0-1.304-.906 4.347 4.347 0 0 0-1.666-.315c-.601 0-1.157.101-1.662.315a3.822 3.822 0 0 0-1.304.906c-.367.39-.652.86-.856 1.408-.204.55-.296 1.196-.296 1.868 0 .671.103 1.18.306 1.734.204.554.484 1.027.846 1.42.361.39.795.691 1.3.91.504.218 1.283.33 1.676.335.392 0 1.177-.122 1.686-.335.51-.214.943-.52 1.305-.91.361-.393.641-.866.84-1.42.199-.555.295-1.063.295-1.734 0-.672-.107-1.318-.32-1.868a4.203 4.203 0 0 0-.846-1.408Zm-1.421 5.239c-.367.504-.882.758-1.539.758-.657 0-1.172-.25-1.539-.758-.367-.504-.55-1.088-.55-1.958 0-.86.178-1.573.545-2.076.367-.504.882-.752 1.538-.752.658 0 1.172.248 1.539.752.367.498.556 1.215.556 2.076 0 .87-.184 1.449-.55 1.958ZM29.35 3.352H27.77c-1.547 0-2.909.815-3.703 2.051a4.643 4.643 0 0 0-.736 2.519 4.611 4.611 0 0 0 1.949 3.783 2.574 2.574 0 0 0 1.542.428l.034-.002.084-.006.032-.004.088-.011.02-.003c1.052-.163 1.97-.986 2.268-2.01v1.85c0 .105.085.19.19.19h1.612a.189.189 0 0 0 .19-.19V3.541a.189.189 0 0 0-.19-.189H29.35Zm0 6.62c-.39.326-.896.448-1.435.484l-.016.002a1.68 1.68 0 0 1-.107.003c-1.352 0-2.468-1.149-2.468-2.54 0-.328.063-.64.173-.927.36-.932 1.241-1.591 2.274-1.591h1.578v4.57ZM69.009 3.352H67.43c-1.547 0-2.908.815-3.703 2.051a4.643 4.643 0 0 0-.736 2.519 4.611 4.611 0 0 0 1.949 3.783 2.575 2.575 0 0 0 1.542.428l.034-.002.084-.006.033-.004.087-.011.02-.003c1.053-.163 1.97-.986 2.269-2.01v1.85c0 .105.084.19.188.19h1.614a.189.189 0 0 0 .188-.19V3.541a.189.189 0 0 0-.188-.189h-1.802Zm0 6.62c-.39.326-.895.448-1.435.484l-.016.002a1.675 1.675 0 0 1-.107.003c-1.352 0-2.468-1.149-2.468-2.54 0-.328.063-.64.174-.927.359-.932 1.24-1.591 2.273-1.591h1.579v4.57ZM42.775 3.352h-1.578c-1.547 0-2.909.815-3.704 2.051a4.63 4.63 0 0 0-.735 2.519 4.6 4.6 0 0 0 1.65 3.555c.094.083.194.16.298.228a2.575 2.575 0 0 0 2.966-.08c.52-.37.924-.913 1.103-1.527v1.608h-.004v.354c0 .7-.182 1.225-.554 1.58-.372.354-.994.532-1.864.532-.356 0-.921-.02-1.491-.078a.19.19 0 0 0-.2.136l-.41 1.379a.19.19 0 0 0 .155.24c.688.1 1.36.15 1.748.15 1.565 0 2.725-.343 3.484-1.03.688-.621 1.061-1.564 1.127-2.832V3.54a.189.189 0 0 0-.19-.189h-1.801Zm0 2.051s.021 4.452 0 4.587c-.386.312-.867.435-1.391.47l-.016.001a1.751 1.751 0 0 1-.233 0c-1.293-.067-2.385-1.192-2.385-2.54 0-.327.063-.64.174-.927.359-.931 1.24-1.591 2.273-1.591h1.578Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path d="M8.725.001C4.356.001.795 3.523.732 7.877c-.064 4.422 3.524 8.085 7.946 8.111a7.94 7.94 0 0 0 3.849-.96.187.187 0 0 0 .034-.305l-.748-.663a.528.528 0 0 0-.555-.094 6.461 6.461 0 0 1-2.614.513c-3.574-.043-6.46-3.016-6.404-6.59a6.493 6.493 0 0 1 6.485-6.38h6.485v11.527l-3.68-3.269a.271.271 0 0 0-.397.042 3.014 3.014 0 0 1-5.416-1.583 3.02 3.02 0 0 1 3.008-3.248 3.02 3.02 0 0 1 3.005 2.75.537.537 0 0 0 .176.356l.958.85a.187.187 0 0 0 .308-.106c.07-.37.094-.755.067-1.15a4.536 4.536 0 0 0-4.23-4.2A4.53 4.53 0 0 0 4.203 7.87c-.067 2.467 1.954 4.593 4.421 4.648a4.498 4.498 0 0 0 2.756-.863l4.808 4.262a.32.32 0 0 0 .531-.239V.304a.304.304 0 0 0-.303-.303h-7.69Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchButton(props) {
|
||||
let [modifierKey, setModifierKey] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
setModifierKey(
|
||||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl '
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 dark:bg-white/5 dark:text-zinc-400 dark:ring-inset dark:ring-white/10 dark:hover:ring-white/20 lg:flex focus:[&:not(:focus-visible)]:outline-none"
|
||||
{...props}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 stroke-current" />
|
||||
Find something...
|
||||
<kbd className="ml-auto text-2xs text-zinc-400 dark:text-zinc-500">
|
||||
<kbd className="font-sans">{modifierKey}</kbd>
|
||||
<kbd className="font-sans">K</kbd>
|
||||
</kbd>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5 lg:hidden focus:[&:not(:focus-visible)]:outline-none"
|
||||
aria-label="Find something..."
|
||||
{...props}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 stroke-zinc-900 dark:stroke-white" />
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchDialog({ open, setOpen, className }) {
|
||||
let router = useRouter()
|
||||
let formRef = useRef()
|
||||
let panelRef = useRef()
|
||||
let inputRef = useRef()
|
||||
let { autocomplete, autocompleteState } = useAutocomplete()
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return
|
||||
}
|
||||
|
||||
function onRouteChange() {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
router.events.on('routeChangeStart', onRouteChange)
|
||||
router.events.on('hashChangeStart', onRouteChange)
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', onRouteChange)
|
||||
router.events.off('hashChangeStart', onRouteChange)
|
||||
}
|
||||
}, [open, setOpen, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
return
|
||||
}
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault()
|
||||
setOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [open, setOpen])
|
||||
|
||||
return (
|
||||
<Transition.Root
|
||||
show={open}
|
||||
as={Fragment}
|
||||
afterLeave={() => autocomplete.setQuery('')}
|
||||
>
|
||||
<Dialog
|
||||
onClose={setOpen}
|
||||
className={clsx('fixed inset-0 z-50', className)}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-zinc-400/25 backdrop-blur-sm dark:bg-black/40" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto px-4 py-4 sm:px-6 sm:py-20 md:py-32 lg:px-8 lg:py-[15vh]">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="mx-auto overflow-hidden rounded-lg bg-zinc-50 shadow-xl ring-1 ring-zinc-900/7.5 dark:bg-zinc-900 dark:ring-zinc-800 sm:max-w-xl">
|
||||
<div {...autocomplete.getRootProps({})}>
|
||||
<form
|
||||
ref={formRef}
|
||||
{...autocomplete.getFormProps({
|
||||
inputElement: inputRef.current,
|
||||
})}
|
||||
>
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
autocomplete={autocomplete}
|
||||
autocompleteState={autocompleteState}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
<div
|
||||
ref={panelRef}
|
||||
className="border-t border-zinc-200 bg-white empty:hidden dark:border-zinc-100/5 dark:bg-white/2.5"
|
||||
{...autocomplete.getPanelProps({})}
|
||||
>
|
||||
{autocompleteState.isOpen && (
|
||||
<>
|
||||
<SearchResults
|
||||
autocomplete={autocomplete}
|
||||
query={autocompleteState.query}
|
||||
collection={autocompleteState.collections[0]}
|
||||
/>
|
||||
<p className="flex items-center justify-end gap-2 border-t border-zinc-100 px-4 py-2 text-xs text-zinc-400 dark:border-zinc-800 dark:text-zinc-500">
|
||||
Search by{' '}
|
||||
<AlgoliaLogo className="h-4 fill-[#003DFF] dark:fill-zinc-400" />
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)
|
||||
}
|
||||
|
||||
function useSearchProps() {
|
||||
let buttonRef = useRef()
|
||||
let [open, setOpen] = useState(false)
|
||||
|
||||
return {
|
||||
buttonProps: {
|
||||
ref: buttonRef,
|
||||
onClick() {
|
||||
setOpen(true)
|
||||
},
|
||||
},
|
||||
dialogProps: {
|
||||
open,
|
||||
setOpen(open) {
|
||||
let { width, height } = buttonRef.current.getBoundingClientRect()
|
||||
if (!open || (width !== 0 && height !== 0)) {
|
||||
setOpen(open)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function Search() {
|
||||
let [modifierKey, setModifierKey] = useState()
|
||||
let { buttonProps, dialogProps } = useSearchProps()
|
||||
|
||||
useEffect(() => {
|
||||
setModifierKey(
|
||||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl '
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="hidden lg:block lg:max-w-md lg:flex-auto">
|
||||
<button
|
||||
type="button"
|
||||
className="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 dark:bg-white/5 dark:text-zinc-400 dark:ring-inset dark:ring-white/10 dark:hover:ring-white/20 lg:flex focus:[&:not(:focus-visible)]:outline-none"
|
||||
{...buttonProps}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 stroke-current" />
|
||||
Find something...
|
||||
<kbd className="ml-auto text-2xs text-zinc-400 dark:text-zinc-500">
|
||||
<kbd className="font-sans">{modifierKey}</kbd>
|
||||
<kbd className="font-sans">K</kbd>
|
||||
</kbd>
|
||||
</button>
|
||||
<SearchDialog className="hidden lg:block" {...dialogProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function MobileSearch() {
|
||||
let { buttonProps, dialogProps } = useSearchProps()
|
||||
|
||||
return (
|
||||
<div className="contents lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5 lg:hidden focus:[&:not(:focus-visible)]:outline-none"
|
||||
aria-label="Find something..."
|
||||
{...buttonProps}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 stroke-zinc-900 dark:stroke-white" />
|
||||
</button>
|
||||
<SearchDialog className="lg:hidden" {...dialogProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
117
src/components/SectionProvider.jsx
Normal file
117
src/components/SectionProvider.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { createStore, useStore } from 'zustand'
|
||||
|
||||
import { remToPx } from '@/lib/remToPx'
|
||||
|
||||
function createSectionStore(sections) {
|
||||
return createStore((set) => ({
|
||||
sections,
|
||||
visibleSections: [],
|
||||
setVisibleSections: (visibleSections) =>
|
||||
set((state) =>
|
||||
state.visibleSections.join() === visibleSections.join()
|
||||
? {}
|
||||
: { visibleSections }
|
||||
),
|
||||
registerHeading: ({ id, ref, offsetRem }) =>
|
||||
set((state) => {
|
||||
return {
|
||||
sections: state.sections.map((section) => {
|
||||
if (section.id === id) {
|
||||
return {
|
||||
...section,
|
||||
headingRef: ref,
|
||||
offsetRem,
|
||||
}
|
||||
}
|
||||
return section
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
function useVisibleSections(sectionStore) {
|
||||
let setVisibleSections = useStore(sectionStore, (s) => s.setVisibleSections)
|
||||
let sections = useStore(sectionStore, (s) => s.sections)
|
||||
|
||||
useEffect(() => {
|
||||
function checkVisibleSections() {
|
||||
let { innerHeight, scrollY } = window
|
||||
let newVisibleSections = []
|
||||
|
||||
for (
|
||||
let sectionIndex = 0;
|
||||
sectionIndex < sections.length;
|
||||
sectionIndex++
|
||||
) {
|
||||
let { id, headingRef, offsetRem } = sections[sectionIndex]
|
||||
let offset = remToPx(offsetRem)
|
||||
let top = headingRef.current.getBoundingClientRect().top + scrollY
|
||||
|
||||
if (sectionIndex === 0 && top - offset > scrollY) {
|
||||
newVisibleSections.push('_top')
|
||||
}
|
||||
|
||||
let nextSection = sections[sectionIndex + 1]
|
||||
let bottom =
|
||||
(nextSection?.headingRef.current.getBoundingClientRect().top ??
|
||||
Infinity) +
|
||||
scrollY -
|
||||
remToPx(nextSection?.offsetRem ?? 0)
|
||||
|
||||
if (
|
||||
(top > scrollY && top < scrollY + innerHeight) ||
|
||||
(bottom > scrollY && bottom < scrollY + innerHeight) ||
|
||||
(top <= scrollY && bottom >= scrollY + innerHeight)
|
||||
) {
|
||||
newVisibleSections.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
setVisibleSections(newVisibleSections)
|
||||
}
|
||||
|
||||
let raf = window.requestAnimationFrame(() => checkVisibleSections())
|
||||
window.addEventListener('scroll', checkVisibleSections, { passive: true })
|
||||
window.addEventListener('resize', checkVisibleSections)
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(raf)
|
||||
window.removeEventListener('scroll', checkVisibleSections)
|
||||
window.removeEventListener('resize', checkVisibleSections)
|
||||
}
|
||||
}, [setVisibleSections, sections])
|
||||
}
|
||||
|
||||
const SectionStoreContext = createContext()
|
||||
|
||||
const useIsomorphicLayoutEffect =
|
||||
typeof window === 'undefined' ? useEffect : useLayoutEffect
|
||||
|
||||
export function SectionProvider({ sections, children }) {
|
||||
let [sectionStore] = useState(() => createSectionStore(sections))
|
||||
|
||||
useVisibleSections(sectionStore)
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
sectionStore.setState({ sections })
|
||||
}, [sectionStore, sections])
|
||||
|
||||
return (
|
||||
<SectionStoreContext.Provider value={sectionStore}>
|
||||
{children}
|
||||
</SectionStoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSectionStore(selector) {
|
||||
let store = useContext(SectionStoreContext)
|
||||
return useStore(store, selector)
|
||||
}
|
||||
58
src/components/Tag.jsx
Normal file
58
src/components/Tag.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
const variantStyles = {
|
||||
medium: 'rounded-lg px-1.5 ring-1 ring-inset',
|
||||
}
|
||||
|
||||
const colorStyles = {
|
||||
emerald: {
|
||||
small: 'text-emerald-500 dark:text-emerald-400',
|
||||
medium:
|
||||
'ring-emerald-300 dark:ring-emerald-400/30 bg-emerald-400/10 text-emerald-500 dark:text-emerald-400',
|
||||
},
|
||||
sky: {
|
||||
small: 'text-sky-500',
|
||||
medium:
|
||||
'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400',
|
||||
},
|
||||
amber: {
|
||||
small: 'text-amber-500',
|
||||
medium:
|
||||
'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400',
|
||||
},
|
||||
rose: {
|
||||
small: 'text-red-500 dark:text-rose-500',
|
||||
medium:
|
||||
'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400',
|
||||
},
|
||||
zinc: {
|
||||
small: 'text-zinc-400 dark:text-zinc-500',
|
||||
medium:
|
||||
'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400',
|
||||
},
|
||||
}
|
||||
|
||||
const valueColorMap = {
|
||||
get: 'emerald',
|
||||
post: 'sky',
|
||||
put: 'amber',
|
||||
delete: 'rose',
|
||||
}
|
||||
|
||||
export function Tag({
|
||||
children,
|
||||
variant = 'medium',
|
||||
color = valueColorMap[children.toLowerCase()] ?? 'emerald',
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
'font-mono text-[0.625rem] font-semibold leading-6',
|
||||
variantStyles[variant],
|
||||
colorStyles[color][variant]
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/BellIcon.jsx
Normal file
17
src/components/icons/BellIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function BellIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.438 8.063a5.563 5.563 0 0 1 11.125 0v2.626c0 1.182.34 2.34.982 3.332L17.5 15.5h-15l.955-1.479c.641-.993.982-2.15.982-3.332V8.062Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.5 15.5v0a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v0"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
11
src/components/icons/BoltIcon.jsx
Normal file
11
src/components/icons/BoltIcon.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export function BoltIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.5 11.5 10 2v5.5a1 1 0 0 0 1 1h4.5L10 18v-5.5a1 1 0 0 0-1-1H4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/BookIcon.jsx
Normal file
17
src/components/icons/BookIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function BookIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m10 5.5-7.5-3v12l7.5 3m0-12 7.5-3v12l-7.5 3m0-12v12"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m17.5 2.5-7.5 3v12l7.5-3v-12Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
23
src/components/icons/CalendarIcon.jsx
Normal file
23
src/components/icons/CalendarIcon.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
export function CalendarIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.5 6.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-11a2 2 0 0 1-2-2v-9Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.5 6.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v2h-15v-2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5.5 5.5v-3M14.5 5.5v-3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
15
src/components/icons/CartIcon.jsx
Normal file
15
src/components/icons/CartIcon.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export function CartIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
d="M5.98 11.288 3.5 5.5h14l-2.48 5.788A2 2 0 0 1 13.18 12.5H7.82a2 2 0 0 1-1.838-1.212Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m3.5 5.5 2.48 5.788A2 2 0 0 0 7.82 12.5h5.362a2 2 0 0 0 1.839-1.212L17.5 5.5h-14Zm0 0-1-2M6.5 14.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2ZM14.5 14.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/ChatBubbleIcon.jsx
Normal file
17
src/components/icons/ChatBubbleIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function ChatBubbleIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10 16.5c4.142 0 7.5-3.134 7.5-7s-3.358-7-7.5-7c-4.142 0-7.5 3.134-7.5 7 0 1.941.846 3.698 2.214 4.966L3.5 17.5c2.231 0 3.633-.553 4.513-1.248A8.014 8.014 0 0 0 10 16.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.5 8.5h5M8.5 11.5h3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/CheckIcon.jsx
Normal file
17
src/components/icons/CheckIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function CheckIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m7.5 10.5 2 2c1-3.5 3-5 3-5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/ChevronRightLeftIcon.jsx
Normal file
17
src/components/icons/ChevronRightLeftIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function ChevronRightLeftIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M1.5 10A6.5 6.5 0 0 1 8 3.5h4a6.5 6.5 0 1 1 0 13H8A6.5 6.5 0 0 1 1.5 10Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m7.5 7.5-3 2.5 3 2.5M12.5 7.5l3 2.5-3 2.5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/ClipboardIcon.jsx
Normal file
17
src/components/icons/ClipboardIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function ClipboardIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.5 6v10a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1l-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4h-1a2 2 0 0 0-2 2Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m13.5 4-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4l.724-1.447A1 1 0 0 1 8.118 2h3.764a1 1 0 0 1 .894.553L13.5 4Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
19
src/components/icons/CogIcon.jsx
Normal file
19
src/components/icons/CogIcon.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export function CogIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
fillRule="evenodd"
|
||||
d="M11.063 1.5H8.937l-.14 1.128c-.086.682-.61 1.22-1.246 1.484-.634.264-1.37.247-1.912-.175l-.898-.699-1.503 1.503.699.898c.422.543.44 1.278.175 1.912-.264.635-.802 1.16-1.484 1.245L1.5 8.938v2.124l1.128.142c.682.085 1.22.61 1.484 1.244.264.635.247 1.37-.175 1.913l-.699.898 1.503 1.503.898-.699c.543-.422 1.278-.44 1.912-.175.635.264 1.16.801 1.245 1.484l.142 1.128h2.124l.142-1.128c.085-.683.61-1.22 1.244-1.484.635-.264 1.37-.247 1.913.175l.898.699 1.503-1.503-.699-.898c-.422-.543-.44-1.278-.175-1.913.264-.634.801-1.16 1.484-1.245l1.128-.14V8.937l-1.128-.14c-.683-.086-1.22-.611-1.484-1.246-.264-.634-.247-1.37.175-1.912l.699-.898-1.503-1.503-.898.699c-.543.422-1.278.44-1.913.175-.634-.264-1.16-.802-1.244-1.484L11.062 1.5ZM10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.938 1.5h2.124l.142 1.128c.085.682.61 1.22 1.244 1.484v0c.635.264 1.37.247 1.913-.175l.898-.699 1.503 1.503-.699.898c-.422.543-.44 1.278-.175 1.912v0c.264.635.801 1.16 1.484 1.245l1.128.142v2.124l-1.128.142c-.683.085-1.22.61-1.484 1.244v0c-.264.635-.247 1.37.175 1.913l.699.898-1.503 1.503-.898-.699c-.543-.422-1.278-.44-1.913-.175v0c-.634.264-1.16.801-1.245 1.484l-.14 1.128H8.937l-.14-1.128c-.086-.683-.611-1.22-1.246-1.484v0c-.634-.264-1.37-.247-1.912.175l-.898.699-1.503-1.503.699-.898c.422-.543.44-1.278.175-1.913v0c-.264-.634-.802-1.16-1.484-1.245l-1.128-.14V8.937l1.128-.14c.682-.086 1.22-.61 1.484-1.246v0c.264-.634.247-1.37-.175-1.912l-.699-.898 1.503-1.503.898.699c.543.422 1.278.44 1.912.175v0c.635-.264 1.16-.802 1.245-1.484L8.938 1.5Z"
|
||||
/>
|
||||
<circle cx="10" cy="10" r="2.5" fill="none" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/CopyIcon.jsx
Normal file
17
src/components/icons/CopyIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function CopyIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.5 5.5v-1a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5.5 7.5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2v-8Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/DocumentIcon.jsx
Normal file
17
src/components/icons/DocumentIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function DocumentIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.5 4.5v11a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-8h-5v-5h-6a2 2 0 0 0-2 2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m11.5 2.5 5 5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/EnvelopeIcon.jsx
Normal file
17
src/components/icons/EnvelopeIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function EnvelopeIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.5 5.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v8a3 3 0 0 1-3 3h-9a3 3 0 0 1-3-3v-8Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10 10 4.526 5.256c-.7-.607-.271-1.756.655-1.756h9.638c.926 0 1.355 1.15.655 1.756L10 10Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/FaceSmileIcon.jsx
Normal file
17
src/components/icons/FaceSmileIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function FaceSmileIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.5 6.5v2M12.5 6.5v2M5.5 11.5s1 3 4.5 3 4.5-3 4.5-3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
22
src/components/icons/FolderIcon.jsx
Normal file
22
src/components/icons/FolderIcon.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export function FolderIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M17.5 15.5v-8a2 2 0 0 0-2-2h-2.93a2 2 0 0 1-1.664-.89l-.812-1.22A2 2 0 0 0 8.43 2.5H4.5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2Z"
|
||||
/>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
d="M8.43 2.5H4.5a2 2 0 0 0-2 2v1h9l-1.406-2.11A2 2 0 0 0 8.43 2.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m11.5 5.5-1.406-2.11A2 2 0 0 0 8.43 2.5H4.5a2 2 0 0 0-2 2v1h9Zm0 0h2"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/LinkIcon.jsx
Normal file
12
src/components/icons/LinkIcon.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function LinkIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m5.056 11.5-1.221-1.222a4.556 4.556 0 0 1 6.443-6.443L11.5 5.056M7.5 7.5l5 5m2.444-4 1.222 1.222a4.556 4.556 0 0 1-6.444 6.444L8.5 14.944"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/ListIcon.jsx
Normal file
17
src/components/icons/ListIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function ListIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.5 4.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-11a2 2 0 0 1-2-2v-11Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6.5 6.5h7M6.5 13.5h7M6.5 10h7"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
13
src/components/icons/MagnifyingGlassIcon.jsx
Normal file
13
src/components/icons/MagnifyingGlassIcon.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
export function MagnifyingGlassIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path strokeWidth="0" d="M2.5 8.5a6 6 0 1 1 12 0 6 6 0 0 1-12 0Z" />
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m13 13 4.5 4.5m-9-3a6 6 0 1 1 0-12 6 6 0 0 1 0 12Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
19
src/components/icons/MapPinIcon.jsx
Normal file
19
src/components/icons/MapPinIcon.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export function MapPinIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 2.5A5.5 5.5 0 0 0 4.5 8c0 3.038 5.5 9.5 5.5 9.5s5.5-6.462 5.5-9.5A5.5 5.5 0 0 0 10 2.5Zm0 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.5 8a5.5 5.5 0 1 1 11 0c0 3.038-5.5 9.5-5.5 9.5S4.5 11.038 4.5 8Z"
|
||||
/>
|
||||
<circle cx="10" cy="8" r="1.5" fill="none" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
16
src/components/icons/PackageIcon.jsx
Normal file
16
src/components/icons/PackageIcon.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export function PackageIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
d="m10 9.5-7.5-4v9l7.5 4v-9ZM10 9.5l7.5-4v9l-7.5 4v-9Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m2.5 5.5 7.5 4m-7.5-4v9l7.5 4m-7.5-13 7.5-4 7.5 4m-7.5 4v9m0-9 7.5-4m-7.5 13 7.5-4v-9m-11 6 .028-3.852L13.5 3.5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/PaperAirplaneIcon.jsx
Normal file
17
src/components/icons/PaperAirplaneIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function PaperAirplaneIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M17 3L1 9L8 12M17 3L11 19L8 12M17 3L8 12"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11 19L8 12L17 3L11 19Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/PaperClipIcon.jsx
Normal file
12
src/components/icons/PaperClipIcon.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function PaperClipIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m15.56 7.375-3.678-3.447c-2.032-1.904-5.326-1.904-7.358 0s-2.032 4.99 0 6.895l6.017 5.639c1.477 1.384 3.873 1.384 5.35 0 1.478-1.385 1.478-3.63 0-5.015L10.21 6.122a1.983 1.983 0 0 0-2.676 0 1.695 1.695 0 0 0 0 2.507l4.013 3.76"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/ShapesIcon.jsx
Normal file
17
src/components/icons/ShapesIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function ShapesIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.5 7.5v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1ZM11.5 16.5v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m2.5 17.5 3-6 3 6h-6ZM14.5 2.5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
11
src/components/icons/ShirtIcon.jsx
Normal file
11
src/components/icons/ShirtIcon.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export function ShirtIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12.5 1.5s0 2-2.5 2-2.5-2-2.5-2h-2L2.207 4.793a1 1 0 0 0 0 1.414L4.5 8.5v10h11v-10l2.293-2.293a1 1 0 0 0 0-1.414L14.5 1.5h-2Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
17
src/components/icons/SquaresPlusIcon.jsx
Normal file
17
src/components/icons/SquaresPlusIcon.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export function SquaresPlusIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.5 4.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2ZM8.5 13.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2ZM17.5 4.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.5 11.5v6M17.5 14.5h-6"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
19
src/components/icons/TagIcon.jsx
Normal file
19
src/components/icons/TagIcon.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export function TagIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 8.69499V3H8.69499C9.18447 3 9.65389 3.19444 10 3.54055L16.4594 10C17.1802 10.7207 17.1802 11.8893 16.4594 12.61L12.61 16.4594C11.8893 17.1802 10.7207 17.1802 10 16.4594L3.54055 10C3.19444 9.65389 3 9.18447 3 8.69499ZM7 8.5C7.82843 8.5 8.5 7.82843 8.5 7C8.5 6.17157 7.82843 5.5 7 5.5C6.17157 5.5 5.5 6.17157 5.5 7C5.5 7.82843 6.17157 8.5 7 8.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 3V8.69499C3 9.18447 3.19444 9.65389 3.54055 10L10 16.4594C10.7207 17.1802 11.8893 17.1802 12.61 16.4594L16.4594 12.61C17.1802 11.8893 17.1802 10.7207 16.4594 10L10 3.54055C9.65389 3.19444 9.18447 3 8.69499 3H3Z"
|
||||
/>
|
||||
<circle cx="7" cy="7" r="1.5" fill="none" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
24
src/components/icons/UserIcon.jsx
Normal file
24
src/components/icons/UserIcon.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
export function UserIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
strokeWidth="0"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 .5a9.5 9.5 0 0 1 5.598 17.177C14.466 15.177 12.383 13.5 10 13.5s-4.466 1.677-5.598 4.177A9.5 9.5 0 0 1 10 .5ZM12.5 8a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10 .5a9.5 9.5 0 0 1 5.598 17.177A9.458 9.458 0 0 1 10 19.5a9.458 9.458 0 0 1-5.598-1.823A9.5 9.5 0 0 1 10 .5Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.402 17.677C5.534 15.177 7.617 13.5 10 13.5s4.466 1.677 5.598 4.177M10 5.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
28
src/components/icons/UsersIcon.jsx
Normal file
28
src/components/icons/UsersIcon.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
export function UsersIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.046 16H1.955a.458.458 0 0 1-.455-.459C1.5 13.056 3.515 11 6 11h.5"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.5 15.454C7.5 12.442 9.988 10 13 10s5.5 2.442 5.5 5.454a.545.545 0 0 1-.546.546H8.045a.545.545 0 0 1-.545-.546Z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6.5 4a2 2 0 1 1 0 4 2 2 0 0 1 0-4Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13 2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
112
src/components/mdx.jsx
Normal file
112
src/components/mdx.jsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Heading } from '@/components/Heading'
|
||||
|
||||
export const a = Link
|
||||
export { Button } from '@/components/Button'
|
||||
export { CodeGroup, Code as code, Pre as pre } from '@/components/Code'
|
||||
|
||||
export const h2 = function H2(props) {
|
||||
return <Heading level={2} {...props} />
|
||||
}
|
||||
|
||||
function InfoIcon(props) {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" {...props}>
|
||||
<circle cx="8" cy="8" r="8" strokeWidth="0" />
|
||||
<path
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M6.75 7.75h1.5v3.5"
|
||||
/>
|
||||
<circle cx="8" cy="4" r=".5" fill="none" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Note({ children }) {
|
||||
return (
|
||||
<div className="my-6 flex gap-2.5 rounded-2xl border border-orange-500/20 bg-orange-50/50 p-4 leading-6 text-orange-900 dark:border-orange-500/30 dark:bg-orange-500/5 dark:text-orange-200 dark:[--tw-prose-links-hover:theme(colors.orange.300)] dark:[--tw-prose-links:theme(colors.white)]">
|
||||
<InfoIcon className="mt-1 h-4 w-4 flex-none fill-orange-500 stroke-white dark:fill-orange-200/20 dark:stroke-orange-200" />
|
||||
<div className="[&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Row({ children }) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Col({ children, sticky = false }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'[&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
||||
sticky && 'xl:sticky xl:top-24'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Properties({ children }) {
|
||||
return (
|
||||
<div className="my-6">
|
||||
<ul
|
||||
role="list"
|
||||
className="m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-zinc-900/5 p-0 dark:divide-white/5"
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Property({ name, type, required, min, max, minLen, maxLen, enumList, children }) {
|
||||
return (
|
||||
<li className="m-0 px-0 py-4 first:pt-0 last:pb-0">
|
||||
<dl className="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<dt className="sr-only">Name</dt>
|
||||
<dd>
|
||||
<code>{name}</code>
|
||||
</dd>
|
||||
<dt className="sr-only">Type</dt>
|
||||
<dd className="font-mono text-xs text-zinc-400 dark:text-zinc-500">
|
||||
{type}
|
||||
</dd>
|
||||
<dt className="sr-only">Required</dt>
|
||||
<dd className="font-mono text-xs text-red-600 dark:text-red-600">
|
||||
{required && 'required'}
|
||||
</dd>
|
||||
<dd className="font-mono text-xs text-zinc-400 dark:text-zinc-500">
|
||||
{!required && 'optional'}
|
||||
</dd>
|
||||
<dt className="sr-only">Enum</dt>
|
||||
<dd className="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
{/*{enumList && "Possible values: [" + enumList.split(',').forEach((type) => (<tag>{type}</tag>)) + "]"}*/}
|
||||
{/* {enumList && <div><text>Possible Values: [</text></div>}enumList.split(',').map(type => '<code>'+ type + '</code>')}*/}
|
||||
{min && !max && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10">>={min}</code></div>}
|
||||
{max && !min && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10"><={max}</code></div>}
|
||||
{min && max && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10">>={min}</code><text> and </text><code className="text-sky-600 bg-sky-400/10"><={max}</code></div>}
|
||||
{minLen && !maxLen && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10">>={minLen} {type === "string" ? "characters" : "objects"}</code></div>}
|
||||
{maxLen && !minLen && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10"><={maxLen} {type === "string" ? "characters" : "objects"}</code></div>}
|
||||
{minLen && maxLen && <div><strong>Possible Values: </strong><code className="text-sky-600 bg-sky-400/10">>={minLen} {type === "string" ? "characters" : "objects"}</code><text> and </text><code className="text-sky-600 bg-sky-400/10"><={maxLen} {type === "string" ? "characters" : "objects"}</code></div>}
|
||||
</dd>
|
||||
<dt className="sr-only">Description</dt>
|
||||
<dd className="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
||||
{children}
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
14
src/images/logos/go.svg
Normal file
14
src/images/logos/go.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<g fill="#00ACD7" clip-path="url(#a)">
|
||||
<path fill-rule="evenodd"
|
||||
d="M5.8 19.334c-.08 0-.093-.054-.067-.107l.4-.533a.421.421 0 0 1 .227-.08h6.893c.08 0 .094.053.067.106l-.334.507c-.04.053-.133.12-.2.12L5.8 19.32v.014Zm-2.92 1.773c-.08 0-.093-.04-.053-.107l.4-.52c.04-.053.133-.093.213-.093h8.8c.093 0 .133.053.107.12l-.16.453c-.014.08-.094.134-.174.134H2.88v.013Zm4.68 1.773c-.08 0-.107-.053-.067-.12l.267-.48c.053-.053.133-.12.2-.12h3.866c.08 0 .12.067.12.134l-.04.466c0 .08-.08.134-.133.134L7.56 22.88Zm20.053-3.906-3.24.853c-.293.08-.32.093-.56-.2-.293-.32-.506-.533-.92-.733a3.36 3.36 0 0 0-3.493.293 4.107 4.107 0 0 0-1.973 3.667 3.027 3.027 0 0 0 2.613 3.04c1.306.173 2.413-.294 3.28-1.28l.533-.707H20.12c-.4 0-.507-.267-.373-.587.253-.6.72-1.6.986-2.106a.533.533 0 0 1 .48-.307h7.04c-.04.533-.04 1.04-.12 1.573-.213 1.387-.733 2.667-1.586 3.787a8.053 8.053 0 0 1-5.507 3.28 6.839 6.839 0 0 1-5.2-1.28A6.065 6.065 0 0 1 13.386 24c-.24-2.106.374-4 1.654-5.666A8.573 8.573 0 0 1 20.44 15a6.667 6.667 0 0 1 5.12.934c1.027.666 1.76 1.6 2.253 2.733.107.173.027.267-.2.32v-.013Z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
d="M34 29.667a7.253 7.253 0 0 1-4.707-1.707 6.066 6.066 0 0 1-2.08-3.733 7.373 7.373 0 0 1 1.56-5.827 8.107 8.107 0 0 1 5.413-3.226 7.173 7.173 0 0 1 5.507.986 6.015 6.015 0 0 1 2.72 4.307 7.467 7.467 0 0 1-2.227 6.547 8.854 8.854 0 0 1-4.626 2.48c-.534.093-1.054.106-1.547.173H34Zm4.613-7.813c-.027-.254-.027-.44-.067-.64a3.186 3.186 0 0 0-3.933-2.547 4.227 4.227 0 0 0-3.387 3.36A3.187 3.187 0 0 0 33 25.68c1.066.454 2.133.4 3.16-.133a4.227 4.227 0 0 0 2.453-3.68v-.013Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M4 4h40v40H4z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
src/images/logos/node.svg
Normal file
4
src/images/logos/node.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="#89D42C"
|
||||
d="M23.675 39.82a2.48 2.48 0 0 1-1.19-.313l-3.764-2.236c-.568-.31-.285-.425-.114-.48.765-.256.906-.313 1.698-.765.086-.057.198-.029.284.028l2.888 1.727c.113.057.254.057.34 0l11.296-6.54c.113-.057.17-.17.17-.312v-13.05c0-.143-.057-.256-.17-.313l-11.296-6.51c-.114-.057-.256-.057-.34 0L12.18 17.567c-.114.057-.17.198-.17.311V30.93c0 .114.056.255.17.312l3.087 1.784c1.67.849 2.717-.143 2.717-1.133V19.01a.344.344 0 0 1 .34-.34h1.443a.344.344 0 0 1 .341.34v12.882c0 2.237-1.218 3.539-3.342 3.539-.65 0-1.16 0-2.604-.708l-2.975-1.698A2.39 2.39 0 0 1 10 30.959V17.904c0-.849.452-1.642 1.189-2.066l11.296-6.54a2.527 2.527 0 0 1 2.379 0l11.297 6.54a2.39 2.39 0 0 1 1.188 2.066V30.96c0 .85-.452 1.642-1.188 2.066l-11.297 6.54a2.896 2.896 0 0 1-1.189.256v-.001Zm3.482-8.976c-4.954 0-5.973-2.264-5.973-4.19a.344.344 0 0 1 .34-.34h1.472c.169 0 .311.114.311.284.226 1.5.878 2.236 3.879 2.236 2.378 0 3.397-.538 3.397-1.812 0-.736-.283-1.274-3.992-1.642-3.086-.311-5.012-.99-5.012-3.454 0-2.293 1.926-3.652 5.154-3.652 3.623 0 5.407 1.246 5.634 3.963a.459.459 0 0 1-.086.256c-.056.056-.141.113-.226.113h-1.472a.332.332 0 0 1-.311-.256c-.34-1.555-1.217-2.066-3.539-2.066-2.605 0-2.916.906-2.916 1.585 0 .821.368 1.076 3.878 1.53 3.483.452 5.124 1.104 5.124 3.539-.027 2.491-2.066 3.907-5.662 3.907Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
10
src/images/logos/php.svg
Normal file
10
src/images/logos/php.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="#6181B6" fill-rule="evenodd"
|
||||
d="M14.643 21.762h-1.77l-.964 4.965h1.57c1.043 0 1.82-.198 2.33-.59.51-.393.853-1.047 1.03-1.966.173-.882.095-1.503-.232-1.866-.328-.362-.98-.543-1.962-.543h-.002Z"
|
||||
clip-rule="evenodd" />
|
||||
<path fill="#6181B6"
|
||||
d="M24 13.29c-12.426 0-22.5 5.3-22.5 11.835 0 6.535 10.074 11.837 22.5 11.837s22.5-5.3 22.5-11.837S36.426 13.29 24 13.29Zm-6.113 13.971a4.55 4.55 0 0 1-1.718 1.032c-.63.203-1.434.308-2.41.308h-2.215l-.612 3.152H8.346l2.307-11.861h4.968c1.494 0 2.585.391 3.27 1.177.687.785.893 1.88.618 3.285a5.34 5.34 0 0 1-.57 1.588c-.28.493-.634.938-1.053 1.319h.002Zm7.546 1.34 1.018-5.247c.119-.598.073-1.005-.128-1.221-.2-.218-.63-.328-1.288-.328h-2.047l-1.32 6.799h-2.566L21.41 16.74h2.561l-.611 3.155h2.282c1.439 0 2.429.25 2.975.75.546.499.708 1.314.492 2.437l-1.073 5.52h-2.604V28.6Zm14.243-4.245a5.215 5.215 0 0 1-.571 1.586 5.356 5.356 0 0 1-1.051 1.319c-.49.467-1.078.82-1.721 1.032-.63.203-1.434.308-2.41.308H31.71l-.614 3.154h-2.581l2.305-11.862h4.968c1.495 0 2.584.393 3.27 1.177.686.784.895 1.878.62 3.285h-.002Z" />
|
||||
<path fill="#6181B6" fill-rule="evenodd"
|
||||
d="M34.81 21.762h-1.765l-.968 4.965h1.571c1.044 0 1.821-.198 2.33-.59.51-.393.852-1.047 1.032-1.966.172-.882.093-1.503-.234-1.866-.326-.362-.983-.543-1.964-.543h-.002Z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
13
src/images/logos/python.svg
Normal file
13
src/images/logos/python.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="#3372A7"
|
||||
d="M23.429 9.008c-7.882 0-7.39 3.418-7.39 3.418l.01 3.541h7.52v1.063H13.062s-5.043-.572-5.043 7.38c0 7.954 4.402 7.671 4.402 7.671h2.627v-3.69s-.142-4.402 4.331-4.402h7.46s4.191.068 4.191-4.05v-6.81s.637-4.12-7.6-4.12Zm-4.147 2.382a1.353 1.353 0 1 1 .001 2.706 1.353 1.353 0 0 1-.001-2.706Z" />
|
||||
<path fill="#FFD235"
|
||||
d="M23.653 39.894c7.881 0 7.39-3.418 7.39-3.418l-.01-3.541h-7.52v-1.063H34.02s5.043.572 5.043-7.381-4.402-7.67-4.402-7.67h-2.627v3.69s.142 4.402-4.332 4.402h-7.46s-4.19-.068-4.19 4.05v6.81s-.637 4.12 7.6 4.12Zm4.147-2.381a1.353 1.353 0 1 1-.002-2.707 1.353 1.353 0 0 1 .002 2.706Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M8 9h31.122v31H8z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 864 B |
4
src/images/logos/ruby.svg
Normal file
4
src/images/logos/ruby.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="#D91505"
|
||||
d="M33.735 10.41c3.376.585 4.334 2.893 4.262 5.311l.017-.035-1.519 19.912-19.752 1.352h.017c-1.639-.069-5.294-.218-5.46-5.328l1.83-3.34 3.139 7.331.56 1.306L19.95 26.74l-.032.007.017-.034 10.302 3.29-1.555-6.044-1.101-4.341 9.817-.634-.684-.567-7.048-5.746 4.073-2.272-.004.012v-.001ZM17.01 15.966c3.963-3.932 9.079-6.256 11.044-4.274 1.96 1.98-.118 6.796-4.089 10.726-3.966 3.931-9.02 6.382-10.98 4.405-1.967-1.98.05-6.921 4.02-10.853l.005-.004Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
0
src/images/static/.nojekyll
Normal file
0
src/images/static/.nojekyll
Normal file
8
src/lib/remToPx.js
Normal file
8
src/lib/remToPx.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export function remToPx(remValue) {
|
||||
let rootFontSize =
|
||||
typeof window === 'undefined'
|
||||
? 16
|
||||
: parseFloat(window.getComputedStyle(document.documentElement).fontSize)
|
||||
|
||||
return parseFloat(remValue) * rootFontSize
|
||||
}
|
||||
40
src/pages/_app.jsx
Normal file
40
src/pages/_app.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Head from 'next/head'
|
||||
import { Router, useRouter } from 'next/router'
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
|
||||
import { Layout } from '@/components/Layout'
|
||||
import * as mdxComponents from '@/components/mdx'
|
||||
import { useMobileNavigationStore } from '@/components/MobileNavigation'
|
||||
|
||||
import '@/styles/tailwind.css'
|
||||
import 'focus-visible'
|
||||
|
||||
function onRouteChange() {
|
||||
useMobileNavigationStore.getState().close()
|
||||
}
|
||||
|
||||
Router.events.on('routeChangeStart', onRouteChange)
|
||||
Router.events.on('hashChangeStart', onRouteChange)
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
let router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{router.pathname === '/' ? (
|
||||
<title>NetBird Docs</title>
|
||||
) : (
|
||||
router.pathname.startsWith('/docs') ?
|
||||
<title>{`${pageProps.title} - NetBird Docs`}</title> : <title>{`${pageProps.title} - NetBird API`}</title>
|
||||
)}
|
||||
<meta name="description" content={pageProps.description} />
|
||||
</Head>
|
||||
<MDXProvider components={mdxComponents}>
|
||||
<Layout {...pageProps}>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</MDXProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
50
src/pages/_document.jsx
Normal file
50
src/pages/_document.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Head, Html, Main, NextScript } from 'next/document'
|
||||
|
||||
const modeScript = `
|
||||
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
updateMode()
|
||||
darkModeMediaQuery.addEventListener('change', updateModeWithoutTransitions)
|
||||
window.addEventListener('storage', updateModeWithoutTransitions)
|
||||
|
||||
function updateMode() {
|
||||
let isSystemDarkMode = darkModeMediaQuery.matches
|
||||
let isDarkMode = window.localStorage.isDarkMode === 'true' || (!('isDarkMode' in window.localStorage) && isSystemDarkMode)
|
||||
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
|
||||
if (isDarkMode === isSystemDarkMode) {
|
||||
delete window.localStorage.isDarkMode
|
||||
}
|
||||
}
|
||||
|
||||
function disableTransitionsTemporarily() {
|
||||
document.documentElement.classList.add('[&_*]:!transition-none')
|
||||
window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('[&_*]:!transition-none')
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function updateModeWithoutTransitions() {
|
||||
disableTransitionsTemporarily()
|
||||
updateMode()
|
||||
}
|
||||
`
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<script dangerouslySetInnerHTML={{ __html: modeScript }} />
|
||||
</Head>
|
||||
<body className="bg-white antialiased dark:bg-zinc-900">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
170
src/pages/accounts.mdx
Normal file
170
src/pages/accounts.mdx
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Accounts {{ tag: 'GET' , label: '/api/accounts' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of accounts of a user. Always returns a list of one account.
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/accounts">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/accounts \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"settings": {
|
||||
"peer_login_expiration_enabled": "boolean",
|
||||
"peer_login_expiration": "integer"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update an Account {{ tag: 'PUT' , label: '/api/accounts/{accountId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update information about an account
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="accountId" type="string" required={true}>
|
||||
The unique identifier of an account
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="peer_login_expiration_enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
|
||||
</Property>
|
||||
|
||||
<Property name="peer_login_expiration" type="integer" required={true}
|
||||
|
||||
|
||||
>
|
||||
Period of time after which peer login expires (seconds).
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/accounts/{accountId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/accounts/{accountId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"peer_login_expiration_enabled": "boolean",
|
||||
"peer_login_expiration": "integer"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"settings": {
|
||||
"peer_login_expiration_enabled": "boolean",
|
||||
"peer_login_expiration": "integer"
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
716
src/pages/dns.mdx
Normal file
716
src/pages/dns.mdx
Normal file
@@ -0,0 +1,716 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Nameserver Groups {{ tag: 'GET' , label: '/api/dns/nameservers' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all Nameserver Groups
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/dns/nameservers">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/dns/nameservers \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Nameserver Group {{ tag: 'POST' , label: '/api/dns/nameservers' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Nameserver Group
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={40}
|
||||
>
|
||||
Nameserver group name
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group description
|
||||
</Property>
|
||||
|
||||
<Property name="nameservers" type="Nameserver[]" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={2}
|
||||
>
|
||||
Nameserver group
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group status
|
||||
</Property>
|
||||
|
||||
<Property name="groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group tag groups
|
||||
</Property>
|
||||
|
||||
<Property name="primary" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group primary status
|
||||
</Property>
|
||||
|
||||
<Property name="domains" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group domain list
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/dns/nameservers">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/dns/nameservers \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Nameserver Group {{ tag: 'GET' , label: '/api/dns/nameservers/{nsgroupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Nameserver Groups
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="nsgroupId" type="string" required={true}>
|
||||
The unique identifier of a Nameserver Group
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/dns/nameservers/{nsgroupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/dns/nameservers/{nsgroupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Nameserver Group {{ tag: 'PUT' , label: '/api/dns/nameservers/{nsgroupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update/Replace a Nameserver Group
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="nsgroupId" type="string" required={true}>
|
||||
The unique identifier of a Nameserver Group
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={40}
|
||||
>
|
||||
Nameserver group name
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group description
|
||||
</Property>
|
||||
|
||||
<Property name="nameservers" type="Nameserver[]" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={2}
|
||||
>
|
||||
Nameserver group
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group status
|
||||
</Property>
|
||||
|
||||
<Property name="groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group tag groups
|
||||
</Property>
|
||||
|
||||
<Property name="primary" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group primary status
|
||||
</Property>
|
||||
|
||||
<Property name="domains" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Nameserver group domain list
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/dns/nameservers/{nsgroupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/dns/nameservers/{nsgroupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"nameservers": [
|
||||
{
|
||||
"ip": "string",
|
||||
"ns_type": "string",
|
||||
"port": "integer"
|
||||
}
|
||||
],
|
||||
"enabled": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
],
|
||||
"primary": "boolean",
|
||||
"domains": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Nameserver Group {{ tag: 'DELETE' , label: '/api/dns/nameservers/{nsgroupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a Nameserver Group
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="nsgroupId" type="string" required={true}>
|
||||
The unique identifier of a Nameserver Group
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/dns/nameservers/{nsgroupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/dns/nameservers/{nsgroupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve DNS Settings {{ tag: 'GET' , label: '/api/dns/settings' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a DNS settings object
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/dns/settings">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/dns/settings \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"disabled_management_groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update DNS Settings {{ tag: 'PUT' , label: '/api/dns/settings' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Updates a DNS settings object
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="disabled_management_groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Groups whose DNS management is disabled
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/dns/settings">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/dns/settings \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"disabled_management_groups": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"disabled_management_groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
116
src/pages/docs/examples.mdx
Normal file
116
src/pages/docs/examples.mdx
Normal file
@@ -0,0 +1,116 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## NetBird Client on AWS ECS (Terraform)
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/examples/wiretrustee-on-aws-ecs.png" alt="high-level-dia" width="400"/>
|
||||
</p>
|
||||
|
||||
A common way to run containers in the AWS cloud is to use Elastic Container Service (ECS).
|
||||
ECS is a fully managed container orchestration service that makes it easy to deploy, manage, and scale containerized applications.
|
||||
|
||||
It is best practice and common to run this infrastructure behind security guardrails like strict security groups and private subnets.
|
||||
|
||||
Also, a routine for many System's administrators and Developers, is to connect to servers that run their company's software in order to troubleshoot, validate output and even install dependencies.
|
||||
If you have your systems running in a private network, you got a few options to allow communication to hosts in that network:
|
||||
* Add a [bastion host](https://en.wikipedia.org/wiki/Bastion_host) or [jump server](https://en.wikipedia.org/wiki/Jump_server).
|
||||
* Connect a [site-2-site](https://en.wikipedia.org/wiki/Virtual_private_network#Types) VPN.
|
||||
* [Remote access](https://en.wikipedia.org/wiki/Virtual_private_network#Types) VPN.
|
||||
* Allow IP(s) address in the server's security group.
|
||||
|
||||
All these options are valid and proved to work over the years, but they come with some costs that in the short to mid-term you start to deal with:
|
||||
* Hard implementation.
|
||||
* Fragile firewall configuration.
|
||||
* Yet, another server to secure and maintain.
|
||||
|
||||
**In this example, we will run NetBird client configured as a daemon set in ECS deployed with Terraform.**
|
||||
|
||||
This allows you to:
|
||||
|
||||
* Run NetBird as an ECS native service, you can manage and maintain it the same way you do with your other services.
|
||||
* Connect to EC2 running on private subnets without the need to open firewall rules or configure bastion servers.
|
||||
* Access other services connected to your NetBird network and running anywhere.
|
||||
|
||||
### Requirements
|
||||
* Terraform > 1.0.
|
||||
* A NetBird account with a Setup Key.
|
||||
* Another NetBird client in your network to validate the connection (possibly your laptop or a machine you are running this example on).
|
||||
* The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed.
|
||||
* An [AWS account](https://aws.amazon.com/free/).
|
||||
* Your AWS credentials. You can [create a new Access Key on this page](https://console.aws.amazon.com/iam/home?#/security_credentials).
|
||||
### Notice
|
||||
> Before getting started with this example, be aware that creating the resources from it may incur charges from AWS.
|
||||
|
||||
### Getting started
|
||||
|
||||
Clone this repository, download, and install Terraform following the guide [here](https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/aws-get-started).
|
||||
|
||||
Login to https://app.netbird.io and [add your machine as a peer](https://app.netbird.io/add-peer), once you are done with the steps described there, copy your [Setup key](https://app.netbird.io/setup-keys).
|
||||
|
||||
Using a text editor, edit the [variables.tf](https://github.com/wiretrustee/wiretrustee-examples/tree/master/ecs-client-daemon/variables.tf) file, and update the `wt_setup_key` variable with your setup key. Also, make sure that `ssh_public_key_path` variable is pointing to the correct public key path. If necessary, update the remaining variables according to your requirements and their descriptions.
|
||||
|
||||
Before continuing, you may also update the [provider.tf](https://github.com/wiretrustee/wiretrustee-examples/tree/master/ecs-client-daemon/provider.tf) to configure proper AWS region and default tags.
|
||||
|
||||
#### Creating the resources with Terraform
|
||||
Follow the steps below to run terraform and create your test environment:
|
||||
|
||||
1. From the root of the cloned repository, enter the ecs-client-daemon folder and run terraform init to download the modules and providers used in this example.
|
||||
```shell
|
||||
cd ecs-client-daemon
|
||||
terraform init
|
||||
```
|
||||
2. Run terraform plan to get the estimated changes
|
||||
```shell
|
||||
terraform plan -out plan.tf
|
||||
```
|
||||
3. Run terraform apply to create your infrastructure
|
||||
```shell
|
||||
terraform apply plan.tf
|
||||
```
|
||||
|
||||
#### Validating the deployment
|
||||
After a few minutes, the autoscaling group will launch an EC2 instance and there you will find the NetBird's ECS Daemon service running. With that, we can go to our [NetBird dashboard](https://app.netbird.io) and pick the IP of the node that is running NetBird, then we can connect to the node via ssh. For Unix(s) systems:
|
||||
```shell
|
||||
ssh ec2-user@100.64.0.200
|
||||
```
|
||||
Once you've login, you should be able to see the containers running by using the docker command:
|
||||
```shell
|
||||
sudo docker ps
|
||||
```
|
||||
|
||||
#### Deleting the infrastructure resources used in the example
|
||||
Once you are done validating the example, you can remove the resources with the following steps:
|
||||
1. Run terraform plan with the flag `-destroy`
|
||||
```shell
|
||||
terraform plan -out plan.tf -destroy
|
||||
```
|
||||
2. Then execute the apply command:
|
||||
```shell
|
||||
terraform apply plan.tf
|
||||
```
|
||||
|
||||
## NetBird Client in Docker
|
||||
|
||||
One of the simplest ways of running NetBird client application is to use a pre-built [Docker image](https://hub.docker.com/r/netbirdio/netbird).
|
||||
|
||||
**Prerequisites:**
|
||||
* **Docker installed.**
|
||||
If you don't have docker installed, please refer to the installation guide on the official [Docker website](https://docs.docker.com/get-docker/).
|
||||
* **NetBird account.**
|
||||
Register one at [app.netbird.io](https://app.netbird.io/).
|
||||
|
||||
You would need to obtain a [setup key](/overview/setup-keys) to associate NetBird client with your account.
|
||||
|
||||
The setup key could be found in the NetBird Management dashboard under the Setup Keys tab - [https://app.netbird.io/setup-keys](https://app.netbird.io/setup-keys).
|
||||
|
||||
Set the ```NB_SETUP_KEY``` environment variable and run the command.
|
||||
|
||||
```bash
|
||||
docker run --rm --name PEER_NAME --hostname PEER_NAME --cap-add=NET_ADMIN -d -e NB_SETUP_KEY=<SETUP KEY> -v netbird-client:/etc/netbird netbirdio/netbird:latest
|
||||
```
|
||||
|
||||
That is it! Enjoy using NetBird.
|
||||
|
||||
If you would like to learn how to run NetBird Client as an ECS agent on AWS, please refer to [this guide](/examples/aws-ecs-client-daemon).
|
||||
58
src/pages/docs/faq.mdx
Normal file
58
src/pages/docs/faq.mdx
Normal file
@@ -0,0 +1,58 @@
|
||||
import {Note} from "@/components/mdx"; import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## What firewall ports should I open to use NetBird?
|
||||
|
||||
### Incoming ports
|
||||
NetBird's agent doesn't require any incoming port to be open; It negotiates the connection with the support of the signal and relay services.
|
||||
### Outgoing ports
|
||||
NetBird usually won't need open ports, but sometimes you or your IT team needs to secure and verify
|
||||
all outgoing traffic, and that may affect how NetBird clients connect to the [control layer](/overview/architecture)
|
||||
and negotiate the peer-to-peer connections.
|
||||
|
||||
Below is the list of NetBird hosted endpoints and ports they listen to:
|
||||
* Management service:
|
||||
* **Endpoint**: api.wiretrustee.com
|
||||
* **Port**: TCP/443
|
||||
* Signal service:
|
||||
* **Endpoint**: signal2.wiretrustee.com
|
||||
* **Port**: TCP/443
|
||||
* Relay (TURN) service:
|
||||
* **Endpoint**: turn.netbird.io
|
||||
* **Port range**: UDP/5555-65535
|
||||
|
||||
## Why and what are the anonymous usage metrics?
|
||||
|
||||
### Why we added metrics collection?
|
||||
As an open-source project and business, making decisions based on data is essential. We will know our adoption rate, feature usage, and client type with anonymous metrics.
|
||||
|
||||
<Note>
|
||||
The collection is strict to our management system.
|
||||
</Note>
|
||||
|
||||
If the metric collection infringes any internal regulation or policy, it can be disabled by setting the flag `--disable-anonymous-metrics=true` to the management service startup command.
|
||||
|
||||
### What are the metrics being collected?
|
||||
We are collecting the following metrics:
|
||||
* Number of accounts
|
||||
* Number of users
|
||||
* Number of peers
|
||||
* Number of active peers in the last 24 hours
|
||||
* Number of peers per operating system
|
||||
* Number of setup keys usage
|
||||
* Number of peers activated by users
|
||||
* Number of rules
|
||||
* Number of groups
|
||||
* Number of routes
|
||||
* Number of nameservers
|
||||
* Service uptime
|
||||
* Service version
|
||||
* Metrics generation time
|
||||
|
||||
|
||||
### Metrics UUID
|
||||
We are using an installation ID for each management service which is generated once and stored in your management store database. It doesn't have any trace of any other private information, and it helps distinguish each deployment.
|
||||
|
||||
### Metrics pusher IP
|
||||
We are not storing the pusher IP address; it gets discarded once the request is complete.
|
||||
525
src/pages/docs/getting-started.mdx
Normal file
525
src/pages/docs/getting-started.mdx
Normal file
@@ -0,0 +1,525 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern"; import {Note} from "@/components/mdx";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## Quickstart Guide
|
||||
|
||||
Step-by-step video guide on YouTube:
|
||||
|
||||
<div class="videowrapper">
|
||||
<iframe src="https://www.youtube.com/embed/HYlhvr_eu2U" allow="fullscreen;"></iframe>
|
||||
</div>
|
||||
<br/>
|
||||
This guide describes how to quickly get started with NetBird and create a secure private network with 2 connected machines.
|
||||
|
||||
One machine is a Linux laptop, and the other one a EC2 node running on AWS.
|
||||
Both machines are running Linux but NetBird also works on Windows and MacOS.
|
||||
|
||||
1. Sign-up at [https://app.netbird.io/](https://app.netbird.io/)
|
||||
|
||||
You can use your Google, GitHub or Microsoft account.
|
||||
|
||||

|
||||
|
||||
2. After a successful login you will be redirected to the ```Peers``` screen which is empty because you don't have any peers yet.
|
||||
|
||||
Click ```Add peer``` to add a new machine.
|
||||
|
||||

|
||||
|
||||
3. Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps.
|
||||
|
||||

|
||||
|
||||
4. If you installed NetBird Desktop UI you can use it to connect to the network instead of running `netbird up` command. Look for `NetBird` in your application list, run it, and click `Connect`.
|
||||
>
|
||||
|
||||

|
||||
|
||||
5. At this point a browser window pops up starting a device registration process. Click confirm and follow the steps if required.
|
||||
|
||||

|
||||
|
||||
6. On the EC2 node repeat the installation steps and run `netbird up` command.
|
||||
|
||||
```bash
|
||||
sudo netbird up
|
||||
```
|
||||
7. Copy the verification URL from the terminal output and paste it in your browser. Repeat step #5
|
||||
|
||||

|
||||
|
||||
8. Return to ```Peers``` and you should notice 2 new machines with status ```online```
|
||||
|
||||

|
||||
|
||||
9. To test the connection you could try pinging devices:
|
||||
|
||||
On your laptop:
|
||||
```bash
|
||||
ping 100.64.0.2
|
||||
```
|
||||
|
||||
On the EC2 node:
|
||||
```bash
|
||||
ping 100.64.0.1
|
||||
```
|
||||
10. Done! You now have a secure peer-to-peer private network configured.
|
||||
|
||||
<br/>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird release page on GitHub: [releases](https://github.com/netbirdio/netbird/releases/latest)
|
||||
|
||||
## Installation
|
||||
|
||||
### Linux
|
||||
|
||||
**APT/Debian**
|
||||
1. Add the repository:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl gnupg -y
|
||||
curl -sSL https://pkgs.wiretrustee.com/debian/public.key | sudo gpg --dearmor --output /usr/share/keyrings/wiretrustee-archive-keyring.gpg
|
||||
echo 'deb [signed-by=/usr/share/keyrings/wiretrustee-archive-keyring.gpg] https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list
|
||||
```
|
||||
2. Update APT's cache
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
```
|
||||
3. Install the package
|
||||
|
||||
```bash
|
||||
# for CLI only
|
||||
sudo apt-get install netbird
|
||||
# for GUI package
|
||||
sudo apt-get install netbird-ui
|
||||
```
|
||||
|
||||
**RPM/Red hat**
|
||||
|
||||
1. Add the repository:
|
||||
```bash
|
||||
cat <<EOF | sudo tee /etc/yum.repos.d/wiretrustee.repo
|
||||
[Wiretrustee]
|
||||
name=Wiretrustee
|
||||
baseurl=https://pkgs.wiretrustee.com/yum/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=https://pkgs.wiretrustee.com/yum/repodata/repomd.xml.key
|
||||
repo_gpgcheck=1
|
||||
EOF
|
||||
```
|
||||
2. Install the package
|
||||
```bash
|
||||
# for CLI only
|
||||
sudo yum install netbird
|
||||
# for GUI package
|
||||
sudo yum install netbird-ui
|
||||
```
|
||||
|
||||
**Fedora**
|
||||
|
||||
1. Create the repository file:
|
||||
```bash
|
||||
cat <<EOF | sudo tee /etc/yum.repos.d/wiretrustee.repo
|
||||
[Wiretrustee]
|
||||
name=Wiretrustee
|
||||
baseurl=https://pkgs.wiretrustee.com/yum/
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
gpgkey=https://pkgs.wiretrustee.com/yum/repodata/repomd.xml.key
|
||||
repo_gpgcheck=1
|
||||
EOF
|
||||
```
|
||||
2. Import the file
|
||||
```bash
|
||||
sudo dnf config-manager --add-repo /etc/yum.repos.d/wiretrustee.repo
|
||||
```
|
||||
3. Install the package
|
||||
```bash
|
||||
# for CLI only
|
||||
sudo dnf install netbird
|
||||
# for GUI package
|
||||
sudo dnf install netbird-ui
|
||||
```
|
||||
|
||||
|
||||
**NixOS 22.11+/unstable**
|
||||
|
||||
1. Edit your [`configuration.nix`](https://nixos.org/manual/nixos/stable/index.html#sec-changing-config)
|
||||
|
||||
```nix
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
services.netbird.enable = true; # for netbird service & CLI
|
||||
environment.systemPackages = [ pkgs.netbird-ui ]; # for GUI
|
||||
}
|
||||
```
|
||||
2. Build and apply new configuration
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
### macOS
|
||||
**Homebrew install**
|
||||
1. Download and install homebrew at https://brew.sh/
|
||||
2. If wiretrustee was previously installed with homebrew, you will need to run:
|
||||
```bash
|
||||
# Stop and uninstall daemon service:
|
||||
sudo wiretrustee service stop
|
||||
sudo wiretrustee service uninstall
|
||||
# unlik the app
|
||||
brew unlink wiretrustee
|
||||
```
|
||||
> netbird will copy any existing configuration from the Wiretrustee's default configuration paths to the new NetBird's default location
|
||||
|
||||
3. Install the client
|
||||
```bash
|
||||
# for CLI only
|
||||
brew install netbirdio/tap/netbird
|
||||
# for GUI package
|
||||
brew install --cask netbirdio/tap/netbird-ui
|
||||
```
|
||||
4. If you installed CLI only, you need to install and start the client daemon service:
|
||||
```bash
|
||||
sudo netbird service install
|
||||
sudo netbird service start
|
||||
```
|
||||
|
||||
### Windows
|
||||
1. Checkout NetBird [releases](https://github.com/netbirdio/netbird/releases/latest)
|
||||
2. Download the latest Windows release installer ```netbird_installer_<VERSION>_windows_amd64.exe``` (**Switch VERSION to the latest**):
|
||||
3. Proceed with the installation steps
|
||||
4. This will install the UI client in the C:\\Program Files\\NetBird and add the daemon service
|
||||
5. After installing, you can follow the steps from [Running NetBird with SSO Login](#Running-NetBird-with-SSO-Login) steps.
|
||||
> To uninstall the client and service, you can use Add/Remove programs
|
||||
|
||||
⚠️ In case of any issues with the connection on Windows check the firewall settings. With default Windows 11 firewall setup there could be connectivity issue related to egress traffic.
|
||||
|
||||
Recommended way is to add NetBird in firewall settings:
|
||||
|
||||
1. Go to "Control panel".
|
||||
2. Select "Windows Defender Firewall".
|
||||
3. Select "Advanced settings".
|
||||
4. Select "Outbound Rules" -> "New rule".
|
||||
5. In the new rule select "Program" and click "Next".
|
||||
6. Point to the NetBird installation exe file (usually in `C:\Program Files\NetBird\netbird.exe`) and click "Next".
|
||||
7. Select "Allow the connection" and click "Next".
|
||||
8. Select the network in which rule should be applied (Domain, Private, Public) according to your needs and click "Next".
|
||||
9. Provide rule name (e.g. "Netbird Egress Traffic") and click "Finish".
|
||||
10. Disconnect and connect to NetBird.
|
||||
|
||||
|
||||
### Binary Install
|
||||
**Installation from binary (CLI only)**
|
||||
|
||||
1. Checkout NetBird [releases](https://github.com/netbirdio/netbird/releases/latest)
|
||||
2. Download the latest release:
|
||||
```bash
|
||||
curl -L -o ./netbird_<VERSION>.tar.gz https://github.com/netbirdio/netbird/releases/download/v<VERSION>/netbird_<VERSION>_<OS>_<Arch>.tar.gz
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
You need to replace some variables from the URL above:
|
||||
|
||||
- Replace **VERSION** with the latest released verion.
|
||||
- Replace **OS** with "linux", "darwin" for MacOS or "windows"
|
||||
- Replace **Arch** with your target system CPU archtecture
|
||||
|
||||
</Note>
|
||||
|
||||
3. Decompress
|
||||
```bash
|
||||
tar xcf ./netbird_<VERSION>.tar.gz
|
||||
sudo mv netbird /usr/bin/netbird
|
||||
sudo chown root:root /usr/bin/netbird
|
||||
sudo chmod +x /usr/bin/netbird
|
||||
```
|
||||
After that you may need to add /usr/bin in your PATH environment variable:
|
||||
````bash
|
||||
export PATH=$PATH:/usr/bin
|
||||
````
|
||||
4. Install and run the service
|
||||
```bash
|
||||
sudo netbird service install
|
||||
sudo netbird service start
|
||||
```
|
||||
|
||||
### Running NetBird with SSO Login
|
||||
#### Desktop UI Application
|
||||
If you installed the Desktop UI client, you can launch it and click on Connect.
|
||||
> It will open your browser, and you will be prompt for email and password. Follow the instructions.
|
||||
|
||||
<p>
|
||||
<img src="/img/getting-started/netbird-sso-login-ui.gif" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
#### CLI
|
||||
Alternatively, you could use command line. Simply run
|
||||
```bash
|
||||
netbird up
|
||||
```
|
||||
> It will open your browser, and you will be prompt for email and password. Follow the instructions.
|
||||
|
||||
<p>
|
||||
<img src="/img/getting-started/netbird-sso-login-cmd.gif" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
Check connection status:
|
||||
```bash
|
||||
netbird status
|
||||
```
|
||||
|
||||
### Running NetBird with a Setup Key
|
||||
In case you are activating a server peer, you can use a [setup key](/overview/setup-keys) as described in the steps below.
|
||||
> This is especially helpful when you are running multiple server instances with infrastructure-as-code tools like ansible and terraform.
|
||||
|
||||
1. Login to the Management Service. You need to have a `setup key` in hand (see [setup keys](/overview/setup-keys)).
|
||||
|
||||
For all systems:
|
||||
```bash
|
||||
netbird up --setup-key <SETUP KEY>
|
||||
```
|
||||
|
||||
For **Docker**, you can run with the following command:
|
||||
```bash
|
||||
docker run --network host --privileged --rm -d -e NB_SETUP_KEY=<SETUP KEY> -v netbird-client:/etc/netbird netbirdio/netbird:<TAG>
|
||||
```
|
||||
> TAG > 0.6.0 version
|
||||
|
||||
Alternatively, if you are hosting your own Management Service provide `--management-url` property pointing to your Management Service:
|
||||
```bash
|
||||
netbird up --setup-key <SETUP KEY> --management-url http://localhost:33073
|
||||
```
|
||||
|
||||
> You could also omit the `--setup-key` property. In this case, the tool will prompt for the key.
|
||||
|
||||
2. Check connection status:
|
||||
```bash
|
||||
netbird status
|
||||
```
|
||||
|
||||
3. Check your IP:
|
||||
|
||||
On **macOS** :
|
||||
````bash
|
||||
sudo ifconfig utun100
|
||||
````
|
||||
On **Linux**:
|
||||
```bash
|
||||
ip addr show wt0
|
||||
```
|
||||
On **Windows**:
|
||||
```bash
|
||||
netsh interface ip show config name="wt0"
|
||||
```
|
||||
|
||||
### Running NetBird in Docker
|
||||
|
||||
Set the ```NB_SETUP_KEY``` environment variable and run the command.
|
||||
<Note>
|
||||
You can pass other settings as environment variables. See [Environment variables](reference/netbird-commands.md#environment-variables) for details.
|
||||
</Note>
|
||||
```bash
|
||||
docker run --rm --name PEER_NAME --hostname PEER_NAME --cap-add=NET_ADMIN -d -e NB_SETUP_KEY=<SETUP KEY> -v netbird-client:/etc/netbird netbirdio/netbird:latest
|
||||
```
|
||||
|
||||
See [Docker example](examples/netbird-docker.md) for details.
|
||||
|
||||
### Troubleshooting
|
||||
1. If you are using self-hosted version and haven't specified `--management-url`, the client app will use the default URL
|
||||
which is ```https://api.wiretrustee.com:33073```.
|
||||
|
||||
2. If you have specified a wrong `--management-url` (e.g., just by mistake when self-hosting)
|
||||
to override it you can do the following:
|
||||
|
||||
```bash
|
||||
netbird down
|
||||
netbird up --management-url https://<CORRECT HOST:PORT>/
|
||||
```
|
||||
|
||||
To override it see the solution #1 above.
|
||||
|
||||
|
||||
## Self-hosting Guide
|
||||
|
||||
NetBird is open-source and can be self-hosted on your servers.
|
||||
|
||||
It relies on components developed by NetBird Authors [Management Service](https://github.com/netbirdio/netbird/tree/main/management), [Management UI Dashboard](https://github.com/netbirdio/dashboard), [Signal Service](https://github.com/netbirdio/netbird/tree/main/signal),
|
||||
a 3rd party open-source STUN/TURN service [Coturn](https://github.com/coturn/coturn), and an identity provider (available options will be listed later in this guide).
|
||||
|
||||
If you would like to learn more about the architecture please refer to the [Architecture section](/overview/architecture).
|
||||
|
||||
<Note>
|
||||
It might be a good idea to try NetBird before self-hosting.
|
||||
We run NetBird in the cloud, and it will take less than 5 minutes to get started with our managed version. [Check it out!](https://netbird.io/pricing)
|
||||
</Note>
|
||||
|
||||
### Requirements
|
||||
|
||||
- Virtual machine offered by any cloud provider (e.g., AWS, DigitalOcean, Hetzner, Google Cloud, Azure ...).
|
||||
- Any Linux OS.
|
||||
- Docker Compose installed (see [Install Docker Compose](https://docs.docker.com/compose/install/)).
|
||||
- Domain name pointing to the public IP address of your server.
|
||||
- Open TCP ports ```80, 443, 33073, 10000``` (Dashboard HTTP & HTTPS, Management gRCP & HTTP APIs, Signal gRPC API respectively) on your server.
|
||||
- Coturn is used for relay using the STUN/TURN protocols. It requires a listening port, `UDP 3478`, and range of ports, `UDP 49152-65535`, for dynamic relay connections. These are set as defaults in setup file, but can be configured to your requirements.
|
||||
- Maybe a cup of coffee or tea :)
|
||||
|
||||
For this tutorial we will be using domain ```demo.netbird.io``` which points to our Ubuntu 22.04 machine hosted at Hetzner.
|
||||
|
||||
### Step 1: Get the latest stable NetBird code
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
REPO="https://github.com/netbirdio/netbird/"
|
||||
# this command will fetch the latest release e.g. v0.8.7
|
||||
LATEST_TAG=$(basename $(curl -fs -o/dev/null -w %{redirect_url} ${REPO}releases/latest))
|
||||
echo $LATEST_TAG
|
||||
|
||||
# this comman will clone the latest tag
|
||||
git clone --depth 1 --branch $LATEST_TAG $REPO
|
||||
```
|
||||
|
||||
Then switch to the infra folder that contains docker-compose file:
|
||||
|
||||
```bash
|
||||
cd netbird/infrastructure_files/
|
||||
```
|
||||
### Step 2: Prepare configuration files
|
||||
|
||||
To simplify the setup we have prepared a script to substitute required properties in the [docker-compose.yml.tmpl](https://github.com/netbirdio/netbird/tree/main/infrastructure_files/docker-compose.yml.tmpl) and [management.json.tmpl](https://github.com/netbirdio/netbird/tree/main/infrastructure_files/management.json.tmpl) files.
|
||||
|
||||
The [setup.env.example](https://github.com/netbirdio/netbird/tree/main/infrastructure_files/setup.env.example) file contains multiple properties that have to be filled. You need to copy the example file to `setup.env` before updating it.
|
||||
|
||||
```bash
|
||||
## example file, you can copy this file to setup.env and update its values
|
||||
##
|
||||
# Dashboard domain. e.g. app.mydomain.com
|
||||
NETBIRD_DOMAIN=""
|
||||
# OIDC configuration e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
||||
NETBIRD_AUTH_AUDIENCE=""
|
||||
# e.g. netbird-client
|
||||
NETBIRD_AUTH_CLIENT_ID=""
|
||||
# indicates whether to use Auth0 or not: true or false
|
||||
NETBIRD_USE_AUTH0="false"
|
||||
NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"
|
||||
# enables Interactive SSO Login feature (Oauth 2.0 Device Authorization Flow)
|
||||
NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=""
|
||||
# e.g. hello@mydomain.com
|
||||
NETBIRD_LETSENCRYPT_EMAIL=""
|
||||
```
|
||||
|
||||
- Set ```NETBIRD_DOMAIN``` to your domain, e.g. `demo.netbird.io`
|
||||
- Configure ```NETBIRD_LETSENCRYPT_EMAIL``` property.
|
||||
This can be any email address. [Let's Encrypt](https://letsencrypt.org/) will create an account while generating a new certificate.
|
||||
|
||||
<Note>
|
||||
Let's Encrypt will notify you via this email when certificates are about to expire. NetBird supports automatic renewal by default.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
If you want to setup netbird with your own reverse-Proxy and without using the integrated letsencrypt, follow [this step here instead](#advanced-running-netbird-behind-an-existing-reverse-proxy).
|
||||
</Note>
|
||||
|
||||
### Step 3: Configure Identity Provider
|
||||
|
||||
NetBird supports generic OpenID (OIDC) protocol allowing for the integration with any IDP that follows the specification.
|
||||
Pick the one that suits your needs, follow the steps, and continue with this guide:
|
||||
|
||||
- Continue with [Auth0](/integrations/identity-providers/self-hosted/using-netbird-with-auth0) (managed service).
|
||||
- Continue with [Keycloak](/integrations/identity-providers/self-hosted/using-netbird-with-keycloak).
|
||||
|
||||
### Step 4: Disable single account mode (optional)
|
||||
|
||||
NetBird Management service runs in a single account mode by default since version v0.10.1.
|
||||
Management service was creating a separate account for each registered user before v0.10.1.
|
||||
Single account mode ensures that all the users signing up for your self-hosted installation will join the same account/network.
|
||||
In most cases, this is the desired behavior.
|
||||
|
||||
If you want to disable the single-account mode, set `--disable-single-account-mode` flag in the
|
||||
[docker-compose.yml.tmpl](https://github.com/netbirdio/netbird/tree/main/infrastructure_files/docker-compose.yml.tmpl)
|
||||
`command` section of the `management` service.
|
||||
|
||||
### Step 5: Run configuration script
|
||||
Make sure all the required properties set in the ```setup.env``` file and run:
|
||||
|
||||
```bash
|
||||
./configure.sh
|
||||
```
|
||||
|
||||
This will export all the properties as environment variables and generate ```docker-compose.yml``` and ```management.json``` files substituting required variables.
|
||||
|
||||
### Step 6: Run docker compose:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
### Step 7: Check docker logs (Optional)
|
||||
|
||||
```bash
|
||||
docker-compose logs signal
|
||||
docker-compose logs management
|
||||
docker-compose logs coturn
|
||||
docker-compose logs dashboard
|
||||
```
|
||||
|
||||
### Advanced: Running netbird behind an existing reverse-proxy
|
||||
|
||||
If you want to run netbird behind your own reverse-proxy, some additional configuration-steps have to be taken to [Step 2](#step-2--prepare-configuration-files).
|
||||
|
||||
<Note>
|
||||
Not all reverse-proxies are supported as netbird uses *gRPC* for various components.
|
||||
</Note>
|
||||
|
||||
#### Configuration for netbird
|
||||
|
||||
In `setup.env`:
|
||||
- Set ```NETBIRD_DOMAIN``` to your domain, e.g. `demo.netbird.io`
|
||||
- Set ```NETBIRD_DISABLE_LETSENCRYPT=true```
|
||||
- Add ```NETBIRD_MGMT_API_PORT``` to your reverse-proxy TLS-port (default: 443)
|
||||
- Add ```NETBIRD_SIGNAL_PORT``` to your reverse-proxy TLS-port
|
||||
|
||||
Optional:
|
||||
- Add ```TURN_MIN_PORT``` and ```TURN_MAX_PORT``` to configure the port-range used by the Turn-server
|
||||
|
||||
<Note>
|
||||
The `coturn`-service still needs to be directly accessible under your set-domain as it uses UDP for communication.
|
||||
</Note>
|
||||
|
||||
Now you can continue with [Step 3](#step-3-configure-identity-provider).
|
||||
|
||||
#### Configuration for your reverse-proxy
|
||||
|
||||
Depending on your port-mappings and choice of reverse-proxy, how you configure the forwards differs greatly.
|
||||
|
||||
The following endpoints have to be setup:
|
||||
|
||||
Endpoint | Protocol | Target service and internal-port
|
||||
------------------------------- | --------- | --------------------------------
|
||||
/ | HTTP | dashboard:80
|
||||
/signalexchange.SignalExchange/ | gRPC | signal:80
|
||||
/api | HTTP | management:443
|
||||
/management.ManagementService/ | gRPC | management:443
|
||||
|
||||
Make sure your reverse-Proxy is setup to use the HTTP2-Protocol when forwarding.
|
||||
|
||||
<Note>
|
||||
You can find helpful templates with the reverse-proxy-name as suffix (e.g. `docker-compose.yml.tmpl.traefik`)
|
||||
Simply replace the file `docker-compose.yml.tmpl` with the chosen version.
|
||||
</Note>
|
||||
|
||||
### Get in touch
|
||||
|
||||
Feel free to ping us on [Slack](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A) if you have any questions
|
||||
|
||||
- NetBird managed version: [https://app.netbird.io](https://app.netbird.io)
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird) :pray:
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
318
src/pages/docs/how-netbird-works.mdx
Normal file
318
src/pages/docs/how-netbird-works.mdx
Normal file
@@ -0,0 +1,318 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern"; import {Note} from "@/components/mdx";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## Architecture
|
||||
|
||||
### Overview
|
||||
NetBird is an open source platform consisting of a collection of components, responsible for handling peer-to-peer connections, tunneling, authentication, and network management (IPs, keys, ACLs, etc).
|
||||
|
||||
It uses open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn),
|
||||
and [software](https://github.com/netbirdio/netbird) developed by NetBird authors to make secure private networks deployment and management simple.
|
||||
|
||||
NetBird relies on four components - **Client** application (or agent), **Management**, **Signal** and **Relay** services.
|
||||
|
||||
The combination of these elements ensures that direct point-to-point connections are established and only authenticated users (or machines) have access to the resources for which they are authorized.
|
||||
|
||||
A **Peer** is a machine or any device that is connected to the network.
|
||||
It can be a Linux server running in the cloud or on-premises, a personal laptop, or even a Raspberry PI.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/high-level-dia.png" alt="high-level-dia" width="781" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}/>
|
||||
</p>
|
||||
|
||||
With NetBird clients installed and authorized on the Management service, machines form a mesh network connecting to each other directly via an encrypted point-to-point Wireguard tunnel.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/mesh.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}/>
|
||||
</p>
|
||||
|
||||
While it is possible to create a full mesh network, it might be not a desirable outcome. In this case, [ACLs](/overview/acls) can be utilized to limit the access to certain machines.
|
||||
|
||||
Let's now take a closer look at each of NetBird's components.
|
||||
|
||||
### Management Service
|
||||
|
||||
The Management service is the central coordination component for NetBird with a UI dashboard.
|
||||
It keeps the network state, public Wireguard keys of the peers, authenticates and distributes network changes to peers.
|
||||
|
||||
The Management Service's responsibilities include:
|
||||
|
||||
* **Registering and authenticating new peers.** Every new machine has to register itself in the network in order to connect to other machines.
|
||||
After installation, NetBird client requires login that can be done through Identity Provider (IDP) or with a [setup key](/overview/setup-keys).
|
||||
* **Keeping the network map.** The Management service stores information about all the registered peers including Wireguard public key that was sent during the registration process.
|
||||
* **Managing private IP addresses.** Each peer receives a unique private IP with which it can be identified in the network.
|
||||
We use [Carrier Grade NAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT) address space with an allocated address block <em>100.64.0.0/10</em>.
|
||||
* **Synchronizing network changes to peers.** The Management Service keeps a control channel open to each peer sending network updates.
|
||||
Whenever a new peer joins the network, all other peers that are authorized to connect to it receive an update.
|
||||
After that, they are able to establish a connection to the new peer.
|
||||
* **Creating and managing ACLs.** ACL is a list of peers that a given peer has access to. <em>Coming Soon</em>.
|
||||
* **Managing private DNS.** [DNS](/overview/dns) allows referring to each of the peers with a fully qualified domain name (FQDN). <em>Coming Soon</em>.
|
||||
* **Monitoring network activity.** <em>Coming Soon</em>.
|
||||
* **Wireguard key rotation.** <em>Coming Soon</em>.
|
||||
|
||||
The Management service runs in the cloud NetBird-managed. It can also be self-hosted.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/management.png" alt="management-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}/>
|
||||
</p>
|
||||
|
||||
### Client Application
|
||||
|
||||
The NetBird Client application (or agent) is a software that is installed on your machines.
|
||||
It is an entry point to you private network that makes it possible for machines to communicate with each other.
|
||||
Once installed and registered, a machine becomes a peer in the network.
|
||||
|
||||
The Client's roles are the following:
|
||||
|
||||
* **Generating private and public Wireguard keys.** These keys are used for packet encryption between peers and for [Wireguard Cryptokey Routing](https://www.wireguard.com/#cryptokey-routing).
|
||||
To accept the incoming connections, peers have to know each other, therefore, the generated public keys have to be pre-shared on the machines. The client application sends its public key to the Management service which then distributes it to the authorized peers.
|
||||
* **Handling peer registration and authentication.** Each peer has to be authenticated and registered in the system. The client application requests a user to log in with an Identity Provider (IDP) or a [setup key](/overview/setup-keys) so that the peer can be associated with the organization's account.
|
||||
* **Receiving network updates from the Management service.**
|
||||
Each peer receives initial configuration and a list of peers with corresponding public keys and IP addresses so that it can establish a peer-to-peer connection.
|
||||
* **Establishing peer-to-peer Wireguard connection.** To establish a connection with a remote peer, the Client first discovers the most suitable connection candidate, or simply address (IP:port) that other peer can use to connect to it.
|
||||
Then sends it to the remote peer via Signal. This message is encrypted with the peer's private key and a public key of the remote peer.
|
||||
The remote peer does the same and once the peers can reach each other, they establish an encrypted Wireguard tunnel.
|
||||
|
||||
<Note>
|
||||
The **private key**, generated by the Client, **never leaves the machine**, ensuring that only the machine that owns the key can decrypt traffic addressed to it.
|
||||
</Note>
|
||||
|
||||
### Signal Service
|
||||
|
||||
The Signal Service or simply Signal is a lightweight piece of software that helps peers to negotiate direct connections.
|
||||
It does not store any data and no traffic passes through it.
|
||||
|
||||
The only Signal's responsibility is:
|
||||
* **Serve as a notification mechanism for peers.** Before a connection can be established, peers need to find each other and exchange the most suitable connection candidates.
|
||||
This is done through Signal. After a connection has been established, Signal steps out.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/signal.png" alt="signal-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}/>
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Messages that are sent over Signal are **peer-to-peer encrypted**, so Signal can't see the contents.
|
||||
</Note>
|
||||
|
||||
NetBird Signal is very similar to the signaling servers used in [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#the_signaling_server).
|
||||
It runs in the cloud NetBird-managed and can be self-hosted.
|
||||
|
||||
### Relay Service
|
||||
|
||||
The Relay service is a [TURN server](https://webrtc.org/getting-started/turn-server) in WebRTC terminology.
|
||||
In fact, we use an open-source implementation called [Coturn](https://github.com/coturn/coturn).
|
||||
The purpose of this service is to be a "plan B" and relay traffic between peers in case a peer-to-peer connection isn't possible.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/relay.png" alt="relay-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}/>
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Similar to Signal, traffic that flows through the Relay can't be decrypted due to the **Wireguard peer-to-peer encryption**.
|
||||
</Note>
|
||||
|
||||
It runs in the cloud or can be self-hosted.
|
||||
|
||||
[//]: # (### STUN (NAT Traversal))
|
||||
|
||||
|
||||
## General Flow Overview
|
||||
|
||||
Below is a high level, step-by-step overview of the flow of communications within NetBird.
|
||||
|
||||
1. Administrator creates an account at [app.netbird.io](https://app.netbird.io/).
|
||||
2. The system automatically generates a new network with an allocated address block <em>100.64.0.0/10</em>.
|
||||
3. The system automatically generates 2 [setup keys](/overview/setup-keys) that can be used for authenticating new machines.
|
||||
4. Administrator (or a user) installs NetBird client and runs ```netbird up``` command providing one of the setup keys.
|
||||
5. NetBird client generates Wireguard private and public keys along with the initial configuration.
|
||||
6. NetBird client sends a registration request to the NetBird Management service calling Login gRPC endpoint, providing setup key, Wireguard public key and additional information about the machine.
|
||||
7. NetBird Management service checks the provided setup key, registers the machine and returns initial configuration to the NetBird client.
|
||||
8. NetBird client receives initial configuration and starts the engine configuring Wireguard, connecting to the Signal Service channel, and the Management Service network updates channel.
|
||||
9. NetBird client receives network map update from the Management Service that includes a list of peers/machines to connect to, and a private IP address.
|
||||
10. For each peer NetBird client initiates a connection process by sending a connection offer message through the Signal service indicating its intent to connect, and a Wireguard public key.
|
||||
11. If the client wasn't the initiator of the connection and receives an offer message, it checks whether the initiator is in the allowed peers list and sends an acknowledgement message through Signal.
|
||||
12. Once the acknowledgement message has been received, NetBird Client (on both ends) starts a connection negotiation process using [Interactive Connectivity Establishment protocol (ICE)](https://datatracker.ietf.org/doc/html/rfc8445).
|
||||
13. Once the direct connection between peers has been established successfully, NetBird Client starts proxying data to Wireguard.
|
||||
14. In case a direct Wireguard connection is possible (e.g., peers are in the same network or one of the peers has a public IP), NetBird Client establishes a direct Wireguard connection avoiding proxy.
|
||||
15. NetBird Client keeps a connection to the Management service receiving network updates such as new peers joining the network or peers deleted from the network.
|
||||
16. When a new peer joins the network, the NetBird client receives an update and triggers connection (see #10).
|
||||
17. When network administrator removes a peer, the NetBird client receives an update and removes the connection.
|
||||
|
||||
|
||||
## Setup Keys
|
||||
|
||||
Setup key is a pre-authentication key that allows to register new machines in your network.
|
||||
It simply associates a machine with an account on a first run.
|
||||
|
||||
The setup key can be provided as a parameter to the ```netbird up``` command.
|
||||
This makes it possible to run automated deployments with infrastructure-as-code software like Ansible, Cloudformation or Terraform.
|
||||
|
||||
```bash {{ title: 'Starting NetBird Client' }}
|
||||
sudo netbird up --setup-key <SETUP KEY>
|
||||
```
|
||||
|
||||
### Types of Setup Keys
|
||||
|
||||
There are 2 types of setup keys:
|
||||
* **One-off key**. This type of key can be used only once to authenticate a machine.
|
||||
* **Reusable key**. This type of key can be used multiple times to authenticate machines.
|
||||
|
||||
### Using Setup Keys
|
||||
|
||||
Setup keys are available in the NetBird Management dashboard under the Setup Keys tab [https://app.netbird.io/setup-keys](https://app.netbird.io/setup-keys).
|
||||
|
||||
By default, we generate 2 setup keys right after account creation. You can easily add new or revoke keys.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/setup-keys.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
When revoking a key, all machines authenticated with this key will remain connected in the network. The same logic applies when the key expires.
|
||||
</Note>
|
||||
### Expiration
|
||||
|
||||
Setup keys are set to expire after 30 days. When expired, the setup key can't be used anymore.
|
||||
|
||||
### Peer Auto-grouping
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/peer-auto-tagging-setupkey.gif" alt="high-level-dia" width="800" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
NetBird offers a powerful [Access Control feature](/overview/acls) that allows easy access management of your resources.
|
||||
In a basic scenario, you would create multiple groups of peers and create access rules to define what groups can access each other.
|
||||
Adding peers to groups might become time-consuming in large networks with dozens of machines.
|
||||
|
||||
Starting NetBird [v0.9.2](https://github.com/netbirdio/netbird/releases), when creating or updating a setup key,
|
||||
it is possible to specify a list of auto-assign groups. Every peer registered with this key will be automatically added
|
||||
to these groups. All the access control rules enabled for these groups will apply automatically.
|
||||
|
||||
To add `Auto-assign groups`, open the `Setup Keys` tab and create or update any existing setup key.
|
||||
Then use this key to enroll new machine.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/architecture/netbird-peer-auto-tagging-newkey.png" alt="high-level-dia" width="500" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Auto-assign groups will apply only to newly registered machines.
|
||||
</Note>
|
||||
|
||||
|
||||
## Access Control
|
||||
NetBird allows administrators to restrict access to resources (peers) by creating access rules and
|
||||
defining what peer groups are permitted to establish connections with one another.
|
||||
|
||||
<div class="videowrapper">
|
||||
|
||||
<iframe src="https://www.youtube.com/embed/WvbkACjdsHA" allow="fullscreen;" width="800" height="500" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}}></iframe>
|
||||
|
||||
</div>
|
||||
|
||||
### Introduction
|
||||
A NetBird account comes with a `Default` rule that allows all peers of the account to connect to each other forming a full mesh network.
|
||||
In most cases, this is the desired state for a small network or network that has low-security requirements.
|
||||
When you need to restrict access to certain resources that belong to specific users or services within your organization, you can create rules that dictate who can access what.
|
||||
|
||||
Access control rules make use of groups to control connections between peers; these groups can be added as `Source` or `Destination` of a rule and will be evaluated when the Management service distributes the list of peers across your network.
|
||||
|
||||
### Concepts
|
||||
#### Groups
|
||||
A NetBird group works and follows a similar concept to tags in other platforms; they are easily created and can be associated with peers and used in rules to control traffic within your network.
|
||||
|
||||
Some characteristics of groups:
|
||||
- They are unique.
|
||||
- One group can have multiple peers.
|
||||
- Peers can belong to multiple groups.
|
||||
- Rules can have multiple groups in their `Source` and `Destination` lists.
|
||||
- They are created in the `Access Control` or `Peers` tabs.
|
||||
- They can only be deleted via API.
|
||||
- There is a default group called `All`.
|
||||
|
||||
<Note>
|
||||
You can assign groups automatically with the [peer auto-grouping feature](/overview/setup-keys#peer-auto-grouping).
|
||||
</Note>
|
||||
|
||||
#### The All Group
|
||||
The `All` group is a default group to which every peer in your network is automatically added to. This group cannot be modified or deleted.
|
||||
#### Rules
|
||||
Rules are lists of `Source` and `Destination` groups of peers that can communicate with each other.
|
||||
Rules are processed when the Management service distributes a network map to all peers of your account. Because you can only create ALLOW rules, there is no processing
|
||||
order or priority, so the decision to distribute peer information is based on its association with a group belonging to an existing rule.
|
||||
|
||||
Currently, the communication between lists of groups in source and destination lists of a rule is bidirectional,
|
||||
meaning that destinations can also initiate connections to a group of peers listed in the source field of the rule.
|
||||
|
||||
The behavior of a network without any rules is to deny traffic. No peers will be able to communicate with each other.
|
||||
|
||||
<Note>
|
||||
If you need to allow peers from the same group to communicate with each other, just add the same group to the `Source` and `Destination` lists.
|
||||
</Note>
|
||||
|
||||
#### The Default Rule
|
||||
The `Default` rule is created when you first create your account. This rule is very permissive because it allows communication between all peers in your network.
|
||||
It uses the [`All`](#the-all-group) group as a source and destination. If you want to have better
|
||||
control over your network, it is recommended that you delete this rule and create more restricted rules with custom groups.
|
||||
|
||||
<Note>
|
||||
If you need to restrict communication within your network, you can create new rules and use different groups, and then remove the default rule to achieve the desired behavior.
|
||||
</Note>
|
||||
|
||||
#### Multiple Mesh Networks
|
||||
As mentioned above, rules are bidirectional, which is basically the control of how your network will behave as a mesh network.
|
||||
|
||||
There is a `Default` rule, which configures a Default mesh connection between all peers of your network. With rules, you can define smaller mesh networks by grouping peers and adding these groups to `Source` and `Destination` lists.
|
||||
### Managing Rules
|
||||
|
||||
#### Creating Rules
|
||||
After accessing the `Access Control` tab, you can click on the `Add Rule` button to create a new rule. This will open a screen
|
||||
where you need to name the rule, set its status, and add groups to the source and destination lists.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/overview/create-rule.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
If required, you can create new groups by simply entering new names in the input box for either source or destination lists.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/overview/create-group-in-rule.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
Once you are done configuring the rule, click the `Create` button to save it. You will then see your new rule in the table.
|
||||
<p align="center">
|
||||
<img src="/img/overview/new-rule-list.png" alt="high-level-dia" width="600" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Because of its permissiveness, new rules will take effect once you remove the `Default` rule.
|
||||
</Note>
|
||||
|
||||
#### Adding peers to groups
|
||||
If you create a new group when defining a rule, you will need to associate peers with this group.
|
||||
You can do it by accessing the `Peers` tab and clicking the `Groups` column of any peer you want to associate with the new group.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/overview/associate-peer-groups.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
You can assign groups automatically with the [peer auto-grouping feature](/overview/setup-keys#peer-auto-grouping).
|
||||
</Note>
|
||||
|
||||
#### Updating Rules
|
||||
To update a rule, you can click on the rule's `Name` or on either `Sources` and `Destinations` columns. You could also click the menu
|
||||
button of a rule and select `View`. This will open the same screen where you can update rule groups, description, or status.
|
||||
#### Disabling Rules
|
||||
To disable a rule, you should follow the steps of [updating rules](#updating-rules) changing its status, and then click on Save.
|
||||
#### Deleting Rules
|
||||
To delete a rule, you should click on the rule's menu and choose `Delete`. A confirmation window will pop up.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/overview/delete-rule-menu.png" alt="high-level-dia" width="600" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/overview/delete-rule-popup.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
418
src/pages/docs/how-to-guides.mdx
Normal file
418
src/pages/docs/how-to-guides.mdx
Normal file
@@ -0,0 +1,418 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern"; import {Button, Note} from "@/components/mdx";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## Routing traffic to private networks
|
||||
|
||||
<div class="videowrapper">
|
||||
<iframe src="https://www.youtube.com/embed/VQuPuBOAknQ" allow="fullscreen;"></iframe>
|
||||
</div>
|
||||
<br/><br/>
|
||||
|
||||
NetBird provides fast and reliable end-to-end encryption between peers in your network. You can install the agent on every desktop, VM, container, or physical server and have a fast, secure peer-to-peer mesh network. That is the desired configuration, but some cases do not allow for agent installation or can slow down migration from legacy systems:
|
||||
|
||||
- Side-by-side migrations where part of your network is already using NetBird but needs to access services that are not.
|
||||
- Systems that have limited operating system access. e.g., IoT devices, printers, and managed services.
|
||||
- Legacy networks where an administrator is unable to install the agent on all nodes.
|
||||
|
||||
In these cases, you can configure network routes assigning routing peers to connect existing infrastructure. Routing peers will forward packets between your NetBird peers and your other networks; they can masquerade traffic going to your data centers or embedded devices, reducing the need for external route configuration and agent installation.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes.png" alt="high-level-dia" />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
If you want to see the Network Routes feature in action, try our managed version at https://app.netbird.io/routes.
|
||||
|
||||
It's free and simple! :)
|
||||
</Note>
|
||||
|
||||
### Concepts
|
||||
#### Network routes
|
||||
A network route describes the network you want to connect with your NetBird peers. It has an identifier, a network range, a routing peer, and some parameters available for managing priority and masquerading.
|
||||
<Note>
|
||||
Network routes is available for NetBird [v0.9.0](https://github.com/netbirdio/netbird/releases) or later.
|
||||
</Note>
|
||||
#### Network identifiers and ranges
|
||||
Network identifiers are names for each network you want to route traffic from your peers, and ranges are IP ranges declared in CIDR notation which refers to an external network. The combination of identifiers and these ranges makes a single network.
|
||||
#### Routing peer
|
||||
A routing peer is a node that will route packets between your routed network and the other NetBird peers.
|
||||
<Note>
|
||||
Only Linux OS nodes can be assigned as routing peers.
|
||||
</Note>
|
||||
#### High availability routes
|
||||
A highly available route is a combination of multiple routes with the same network identifier and ranges. They have different routing peers offering high-available paths for communication between your peers and external networks.
|
||||
Nodes connected to routing peers will choose one of them to route packets to external networks based on connection type and defined metrics.
|
||||
#### Masquerade
|
||||
Masquerade hides other NetBird network IPs behind the routing peer local address when accessing the target Network range. This option allows access to your private networks without configuring routes on your local routers or other devices.
|
||||
|
||||
If you don't enable this option, you must configure a route to your NetBird network in your external network infrastructure.
|
||||
#### Metric and priority
|
||||
Metric defines prioritization when choosing the main routing peer in a high availability network. Lower metrics have higher priority.
|
||||
|
||||
#### Distribution groups
|
||||
Distribution groups define that peers that belong to groups set in this field will receive the network route.
|
||||
<Note>
|
||||
It doesn't remove the need for the routing peer to be connected to these peers
|
||||
</Note>
|
||||
|
||||
### Managing network routes
|
||||
A network route describes a network you want to connect with your NetBird peers. It has an identifier, a network range, a routing peer, and some parameters available for managing priority and masquerading.
|
||||
|
||||
#### Creating a network route
|
||||
Access the `Network Routes` tab and click the `Add Route` button to create a new route.
|
||||
That will open a route configuration screen where you can add the information about the network you want to route:
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-add-button.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
Now you can enter the details of your route.
|
||||
In the example below, we are creating a route with the following information:
|
||||
|
||||
- Network identifier: `aws-eu-central-1-vpc`
|
||||
- Description: `Production VPC in Frankfurt`
|
||||
- Network range: `172.31.0.0/16`
|
||||
- Routing peer: `aws-nb-europe-router-az-a`
|
||||
- Distribution Groups: `All`
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-create.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
Once you fill in the route information, you can click on the `Save` button to save your new route.
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-saved-new.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
Done! Now every peer connected to your routing peer will be able to send traffic to your external network.
|
||||
|
||||
#### Creating highly available routes
|
||||
To avoid a single point of failure when managing your network, we recommend installing NetBird on every resource.
|
||||
However, you still want to ensure a reliable connection to your private network when running NetBird on every machine is not feasible.
|
||||
NetBird Network Routes feature has a High Availability (HA) mode,
|
||||
allowing one or more NetBird peers to serve as routing peers for the same private network.
|
||||
|
||||
To enable high-available mode, you can click on `Configure` and select a new peer in the `Add additional routing peer` field, then select the distribution groups and click on `Save`.
|
||||
|
||||
In the following screenshot, we are adding the peer `aws-nb-europe-router-az-b` to the `aws-eu-central-1-vpc` route:
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-create-ha.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
This way, nodes connected to both peer `aws-nb-europe-router-az-a` and peer `aws-nb-europe-router-az-b` would have a highly available connection with the network `172.31.0.0/16`.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-saved-new-ha.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Currently, there is no limitation in the number of routes that form a highly available route. Each connected peer will pick one routing peer to use as the router for a network; this decision is based on metric prioritization and connection attributes like direct or relayed connections.
|
||||
</Note>
|
||||
|
||||
#### Filtering routes distribution with groups
|
||||
You can select as many distribution groups as you want for your network route. You can update them at the routing peer or high-availability group level. Keep in mind to link them to peers and, if required, to add access control rules ensuring connectivity between these peers and the routing peers of your route
|
||||
#### Routes without masquerading
|
||||
If you want more transparency and would like to manage your external network routers, you may choose to disable masquerade for your network routes.
|
||||
In this case, the routing peer won't hide any NetBird peer IP and will forward the packets to the target network transparently.
|
||||
|
||||
That will require a routing configuration on your external network router pointing your NetBird network back to your routing peer.
|
||||
This way, devices that don't have the agent installed can communicate with your NetBird peers.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-network-routes-masquerading.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
### Get started
|
||||
<p float="center" >
|
||||
<Button name="button" className="button-5" onClick={() => window.open("https://netbird.io/pricing")}>Use NetBird</Button>
|
||||
</p>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird [latest release](https://github.com/netbirdio/netbird/releases) on GitHub
|
||||
|
||||
|
||||
## Add Users to your network
|
||||
|
||||
Whether you have a network for personal use or manage your company's corporate network, you'd probably want to invite
|
||||
people to your account and join your NetBird network.
|
||||
|
||||
There are two ways of adding users to a NetBird account - indirect and direct.
|
||||
|
||||
### Indirect user invites
|
||||
This way of adding users is managed by the NetBird system and doesn't require administrator input.
|
||||
It works only for organizations with private domains.
|
||||
|
||||
Whenever a new user signs up with a private domain (e.g., @netbird.io),
|
||||
NetBird creates a new account and associates it with the netbird.io organization (domain) automatically. Every consequent user signup with the same @netbird.io domain in their email address will end up under the same organization.
|
||||
|
||||
How does it work? Every time a previously unknown user registers at [app.netbird.io](https://app.netbird.io/),
|
||||
the system classifies the domain part of the email.
|
||||
The domain can fall into one of the following categories - `public`, `private`, or `unclassified`.
|
||||
The domains of the private category are the ones that are automatically grouped under the same account.
|
||||
Public domains are the ones of the public email providers like Gmail.
|
||||
|
||||
<Note>
|
||||
It might happen (unlikely) that the domain classification system didn't classify your company's domain as private.
|
||||
Our system was unsure about your domain and assigned an unclassified or public category to be on the safe side.
|
||||
Just email us at [hello@netbird.io](mailto:hello@netbird.io) or ping us on [Slack](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A) to fix this.
|
||||
</Note>
|
||||
|
||||
### Direct user invites
|
||||
As the name stands, this way of inviting users is straightforward and works through the web UI.
|
||||
To invite a new user, proceed to the `Users` tab and click the <button name="button" className="button-6">Invite User</button> button.
|
||||
A user window will pop up where you can specify the name and email address of the invited user. Optionally, you could select a set of groups with which you want this user to be associated.
|
||||
|
||||
The invited users will receive an email invitation that they have to confirm.
|
||||
After logging in to the system, they will join your network automatically.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/user-invites.gif" alt="high-level-dia" width="800" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
If a user already has a NetBird account, you can't invite them.
|
||||
This is a limitation that is likely to be removed in future versions.
|
||||
</Note>
|
||||
|
||||
### Get started
|
||||
<p float="center" >
|
||||
<Button name="button" className="button-5" onClick={() => window.open("https://netbird.io/pricing")}>Use NetBird</Button>
|
||||
</p>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird [latest release](https://github.com/netbirdio/netbird/releases) on GitHub
|
||||
|
||||
|
||||
## Manage DNS in your network
|
||||
|
||||
<div class="videowrapper">
|
||||
<iframe src="https://www.youtube.com/embed/xxQ_QeEMC0U" allow="fullscreen;"></iframe>
|
||||
</div>
|
||||
<br/><br/>
|
||||
|
||||
You don't need to design a network or configure [DHCP](https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol)
|
||||
as it is automatically done in a single place - the NetBird Management service.
|
||||
NetBird assigns and automatically distributes IP addresses to your peers.
|
||||
Once peers have IPs, they can communicate with one another and establish direct encrypted WireGuard® tunnels.
|
||||
You can use these IPs to access the services running on the connected peers (e.g., SSH).
|
||||
Even though we trust our memory capacity, there is a limit to what we can remember,
|
||||
especially when it comes to IP addresses like this one, 100.128.185.34.
|
||||
|
||||
Starting [v0.11.0](https://github.com/netbirdio/netbird/releases), NetBird automatically assigns a domain name
|
||||
to each peer in a private `netbird.cloud` space that can be used to access the machines. E.g., `my-server.netbird.cloud`.
|
||||
|
||||
Besides accessing machines by their domain names, you can configure NetBird to use your private nameservers,
|
||||
control what nameservers a specific [peer group](https://netbird.io/docs/overview/acls#groups) should use, and set up split DNS.
|
||||
|
||||
<Note>
|
||||
Nameservers is available for NetBird [v0.11.0](https://github.com/netbirdio/netbird/releases) or later.
|
||||
</Note>
|
||||
|
||||
### Concepts
|
||||
#### Local resolver
|
||||
To minimize the number of changes in your system, NetBird will spin up a local DNS resolver.
|
||||
|
||||
This local resolver will be responsible for queries to the domain names of peers registered in your network and forwarding queries to upstream nameservers you configure in the system.
|
||||
|
||||
It listens on the peer's IP, and usually, it will use the default port 53, but if it is in use, it will use the 5053 port.
|
||||
<Note>
|
||||
Custom port support is not builtin into most operating systems. At the time of release, the supported systems are:
|
||||
- MacOS
|
||||
- Linux with systemd-resolved
|
||||
</Note>
|
||||
#### Nameserver
|
||||
Nameserver is an upstream DNS server for name resolution, if a query comes and is not a peer domain name, it will be resolved by one of the upstream servers. You can assign private and public IPs and custom ports. Remember that you might need a network route for private addresses to allow peers to connect to it.
|
||||
#### Match domains
|
||||
Match domains allow you to route queries of names, matching them to specific nameservers. This is useful when you have an internal DNS configuration that only internal servers can resolve.
|
||||
#### All domains option
|
||||
The all domains option defines a default nameserver configuration to resolve all domains that don't have a match domain setting. Because not all operating systems support match domain configuration, we recommend configuring at least one nameserver set with this option enabled per distribution group. You may also consider using the group All for distribution, so you don't have to define multiple sets of nameservers to resolve all domains.
|
||||
<Note>
|
||||
A nameserver set may only be configured with either All domains or match domains, you can have both settings in a single configuration as they overlap.
|
||||
</Note>
|
||||
#### Distribution groups
|
||||
Distribution defines that peers that belong to groups set in this field will receive the nameserver configuration.
|
||||
<Note>
|
||||
When using private nameservers, you may use these groups to link routing peers and clients of the private servers.
|
||||
</Note>
|
||||
|
||||
### Managing nameserver groups
|
||||
A nameserver group defines up to 2 nameservers to resolve DNS to a set of peers in distribution groups.
|
||||
|
||||
#### Creating a nameserver group
|
||||
Access the `DNS` tab and click the `Add Nameserver` button to create a new nameserver.
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-add-button.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
That will open a nameserver selection configuration screen where you can choose between using three predefined public
|
||||
nameservers or using a custom setup.
|
||||
|
||||
##### Selecting predefined nameservers
|
||||
If you choose a predefined public nameserver option, you can select the following nameservers:
|
||||
- [Google DNS servers](https://developers.google.com/speed/public-dns/docs/using)
|
||||
- [Cloudflare DNS servers](https://one.one.one.one/dns/)
|
||||
- [Quad9 DNS servers](https://www.quad9.net/)
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-selection-view-open.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
After selecting one of the three options, you need to assign a peer group for which this nameserver will be effective.
|
||||
In the example below, we chose the "All" group:
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-all-group.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
##### Creating custom nameservers
|
||||
You can also configure a custom nameserver by clicking the `Add custom` button. Now you can enter the details of your nameserver.
|
||||
|
||||
In the example below, we are creating a nameserver with the following information:
|
||||
|
||||
- Name: `Office resolver`
|
||||
- Description: `Berlin office resolver`
|
||||
- Add at least one nameserver: `192.168.0.32` with port `53`
|
||||
- Match mode: `All domains`
|
||||
- Distribution group: `Remote developers`
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-custom.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
#### Creating a nameserver for specific domains
|
||||
Sometimes we want to forward DNS queries to specific nameservers but only for particular domains that match a setting.
|
||||
Taking the example of custom nameservers above, you could select a match mode for only domains listed there.
|
||||
Below you can see the same nameserver setup but only for the `berlinoffice.com` domain:
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-remote-resolver.png" alt="high-level-dia" width="300" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
Currently, only MacOS, Windows 10+, and Linux running systemd-resolved support nameservers without an all domains resolver. For a better experience, we recommend setting at least one all domain resolver to be applied to all groups.
|
||||
</Note>
|
||||
|
||||
#### Distributing the settings with groups
|
||||
You can select as many distribution groups as you want for your nameserver setup. Keep in mind to link them to peers and, if required, to add access control rules when using private nameservers.
|
||||
#### Adding remote private DNS servers
|
||||
To add a private DNS server that is running behind routing peers, you need to create resources to ensure communication between your nameserver clients can communicate. In the Berlin office example from previous steps, we have a peer from the `Office network` that can route traffic to the `192.168.0.32` IP, so we need to ensure that a similar network route exists:
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-remote-route.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
Then we need to confirm that an access rule exists to connect `Remote developers` to `Office network` group:
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/netbird-nameserver-remote-rule.png" alt="high-level-dia" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
### Testing configuration
|
||||
#### Querying records
|
||||
DNS configuration has evolved in the last few years, and each operating system might expose its nameserver configuration differently. Unfortunately, tools like `nslookup` or `dig` didn't get updated to match these OS configurations, and in many cases, they won't use the same servers as your browser to query domain names.
|
||||
|
||||
For these cases, we listed some tools to support your checks:
|
||||
##### MacOS
|
||||
You can use `dscacheutil`:
|
||||
```shell
|
||||
dscacheutil -q host -a name peer-a.netbird.cloud
|
||||
```
|
||||
##### Windows
|
||||
You can use `Resolve-DnsName` on `Powershell`:
|
||||
```shell
|
||||
Resolve-DnsName -Name peer-a.netbird.cloud
|
||||
```
|
||||
##### Linux
|
||||
In most cases, you will be fine with traditional tools because most DNS managers on Linux tend to update the /etc/resolv.conf.
|
||||
```shell
|
||||
dig peer-a.netbird.cloud
|
||||
# or
|
||||
nslookup peer-a.netbird.cloud
|
||||
```
|
||||
If your system is running systemd-resolved, you can also use ```resolvectl```:
|
||||
```shell
|
||||
resolvectl query peer-a.netbird.cloud
|
||||
```
|
||||
### Get started
|
||||
<p float="center" >
|
||||
<Button name="button" className="button-5" onClick={() => window.open("https://netbird.io/pricing")}>Use NetBird</Button>
|
||||
</p>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird [latest release](https://github.com/netbirdio/netbird/releases) on GitHub
|
||||
|
||||
## Monitor system and network activity
|
||||
|
||||
The activity monitoring feature lets you quickly see what's happening with your network.
|
||||
Whether a new machine or user joined your network or the access control policy has been modified, the activity log allows you to track the changes to your network.
|
||||
|
||||
Activity monitoring is enabled by default for every network, and you can access it in the web UI under the [Activity tab](https://app.netbird.io/activity).
|
||||
You can also use the search bar to filter events by activity type.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/activity-monitoring.png" alt="activity-monitoring" width="800" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
<Note>
|
||||
The current version of NetBird tracks network changes that occur in the Management server. E.g., changes related to the list of peers, groups, system settings, setup keys, access control, etc.
|
||||
The future versions will support connection events that occur in NetBird agents (e.g., peer A connected to peer B).
|
||||
</Note>
|
||||
|
||||
### Get started
|
||||
<p float="center" >
|
||||
<Button name="button" className="button-5" onClick={() => window.open("https://netbird.io/pricing")}>Use NetBird</Button>
|
||||
</p>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird [latest release](https://github.com/netbirdio/netbird/releases) on GitHub
|
||||
|
||||
## Configure periodic user authentication
|
||||
|
||||
To ensure a high level of security, NetBird offers a peer login expiration feature that requires users to periodically reauthenticate their devices.
|
||||
Every new network has this feature enabled, and the expiration period is set to 24 hours by default. You can disable this feature and configure the expiration period in the account settings in the web UI https://app.netbird.io/settings.
|
||||
|
||||
<Note>
|
||||
This feature is only applied to peers added with the [interactive SSO login feature](/getting-started/installation#running-netbird-with-sso-login). Peers, added with a setup key, won't be affected.
|
||||
</Note>
|
||||
|
||||
Expired peers will appear in the peers' view with the status `needs login`.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/peer-needs-login.png" alt="peer-needs-login.png" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
### Configure and disable expiration
|
||||
The expiration period can be set to anything between one hour and 180 days.
|
||||
Go to the Web UI Settings tab and set the desired period in the Authentication section.
|
||||
You can also disable the expiration for the whole network in the same section.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/peer-login-expiration.png" alt="peer-login-expiration" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
|
||||
<Note>
|
||||
Enabling peer expiration or changing the expiration period will cause some peers added with the SSO login to disconnect,
|
||||
and re-authentication will be required.
|
||||
</Note>
|
||||
|
||||
### Disable expiration individually per peer
|
||||
Sometimes, you might want to disable peer expiration for some peers.
|
||||
With NetBird you can disable login expiration per peer without disabling expiration globally.
|
||||
In the Peers tab of the web UI click on the peer you want to disable expiration for and use the Login Expiration switch.
|
||||
Peers with `expiration disabled` will be marked with a corresponding label in the peers' table.
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/how-to-guides/individual-peer-login-expiration.png" alt="peer-login-expiration" style={{boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)'}} />
|
||||
</p>
|
||||
|
||||
### Get started
|
||||
<p float="center" >
|
||||
<Button name="button" className="button-5" onClick={() => window.open("https://netbird.io/pricing")}>Use NetBird</Button>
|
||||
</p>
|
||||
|
||||
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
|
||||
- Follow us [on Twitter](https://twitter.com/netbird)
|
||||
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
|
||||
- NetBird [latest release](https://github.com/netbirdio/netbird/releases) on GitHub
|
||||
2
src/pages/docs/integrations.mdx
Normal file
2
src/pages/docs/integrations.mdx
Normal file
@@ -0,0 +1,2 @@
|
||||
export const description =
|
||||
'This guide will.'
|
||||
20
src/pages/docs/introduction.mdx
Normal file
20
src/pages/docs/introduction.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
export const description =
|
||||
'On this page, we’ll introduce NetBird.'
|
||||
|
||||
# Introduction
|
||||
|
||||
NetBird is a simple and fast alternative to corporate VPNs built on top of [WireGuard®](https://www.wireguard.com/) making it easy to create secure private networks for your organization or home.
|
||||
|
||||
NetBird can connect machines running anywhere in just a few clicks.
|
||||
|
||||
It requires near zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth.
|
||||
|
||||
:::tip open-source
|
||||
NetBird is an **open-source** project.
|
||||
|
||||
Check it out on GitHub: **[https://github.com/netbirdio/netbird](https://github.com/netbirdio/netbird)**
|
||||
:::
|
||||
|
||||
There is no centralized VPN server with NetBird - your computers, devices, machines, and servers connect to each other directly over a fast encrypted tunnel.
|
||||
|
||||
It literally takes less than 5 minutes to deploy a secure peer-to-peer VPN with NetBird. Check our [Quick Start Guide](/getting-started/quickstart) to get started.
|
||||
39
src/pages/docs/introductions.mdx
Normal file
39
src/pages/docs/introductions.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Guides } from '@/components/Guides'
|
||||
import { Resources } from '@/components/Resources'
|
||||
import { HeroPattern } from '@/components/HeroPattern'
|
||||
|
||||
export const description =
|
||||
'Learn everything there is to know about the Protocol API and integrate Protocol into your product.'
|
||||
|
||||
export const sections = [
|
||||
{ title: 'Guides', id: 'guides' },
|
||||
{ title: 'Resources', id: 'resources' },
|
||||
]
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
# API Documentation
|
||||
|
||||
Use the Protocol API to access contacts, conversations, group messages, and more and seamlessly integrate your product into the workflows of dozens of devoted Protocol users. {{ className: 'lead' }}
|
||||
|
||||
<div className="not-prose mb-16 mt-6 flex gap-3">
|
||||
<Button href="/quickstart" arrow="right" children="Quickstart" />
|
||||
<Button href="/sdks" variant="outline" children="Explore SDKs" />
|
||||
</div>
|
||||
|
||||
## Getting started {{ anchor: false }}
|
||||
|
||||
To get started, create a new application in your [developer settings](#), then read about how to make requests for the resources you need to access using our HTTP APIs or dedicated client SDKs. When your integration is ready to go live, publish it to our [integrations directory](#) to reach the Protocol community. {{ className: 'lead' }}
|
||||
|
||||
<div className="not-prose">
|
||||
<Button
|
||||
href="/sdks"
|
||||
variant="text"
|
||||
arrow="right"
|
||||
children="Get your API key"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Guides />
|
||||
|
||||
<Resources />
|
||||
34
src/pages/docs/netbird-vs-traditional-vpn.mdx
Normal file
34
src/pages/docs/netbird-vs-traditional-vpn.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
# NetBird vs. Traditional VPN
|
||||
|
||||
### Traditional VPN challenges
|
||||
In the traditional VPN model, everything converges on a centralized, protected network where all the clients are connecting to a central VPN server.
|
||||
|
||||
An increasing amount of connections can easily overload the VPN server.
|
||||
Even a short downtime of a server can cause expensive system disruptions, and a remote team's inability to work.
|
||||
|
||||
Centralized VPNs imply all the traffic going through the central server causing network delays and increased traffic usage.
|
||||
|
||||
Such systems require an experienced team to set up and maintain.
|
||||
Configuring firewalls, setting up NATs, SSO integration, and managing access control lists can be a nightmare.
|
||||
|
||||
Traditional centralized VPNs are often compared to a [castle-and-moat](https://en.wikipedia.org/wiki/Moat) model
|
||||
in which once accessed, user is trusted and can access critical infrastructure and resources without any restrictions.
|
||||
|
||||
### NetBird benefits
|
||||
|
||||
NetBird decentralizes networks using direct point-to-point connections, as opposed to traditional models.
|
||||
Consequently, network performance is increased since traffic flows directly between the machines bypassing VPN servers or gateways.
|
||||
To achieve this, NetBird client applications employ signalling servers to find other machines and negotiate connections.
|
||||
These are similar to the signaling servers used in [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#the_signaling_server)
|
||||
|
||||
Thanks to [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal),
|
||||
outlined in the [Why Wireguard with NetBird](/docs/documentation/wireguard-plus-netbird.md) section,
|
||||
NetBird installation doesn't require complex network and firewall configuration.
|
||||
It just works, minimising the maintenance effort.
|
||||
|
||||
Finally, each machine or device in the NetBird network verifies incoming connections accepting only the trusted ones.
|
||||
This is ensured by Wireguard's [Crypto Routing concept](https://www.wireguard.com/#cryptokey-routing).
|
||||
222
src/pages/docs/other.mdx
Normal file
222
src/pages/docs/other.mdx
Normal file
@@ -0,0 +1,222 @@
|
||||
import {Note} from "@/components/mdx"; import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## Google Summer of Code Ideas - 2022
|
||||
|
||||
This page lists the all project ideas for [Google Summer of Code 2022](https://summerofcode.withgoogle.com/).
|
||||
|
||||
We require all applicants to complete a [task](#task).
|
||||
|
||||
Join our [Slack channel](https://join.slack.com/t/wiretrustee/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A) and talk to your potential mentors!
|
||||
|
||||
### Task
|
||||
|
||||
The goal of the task is to get familiar with the system by setting up a self-hosted version of Netbrid.
|
||||
|
||||
1. Carefully review projects' documentation.
|
||||
2. Deploy a self-hosted version of NetBird.
|
||||
3. Register at least 2 peers in the system (can be a laptop, server or even Raspberry PI).
|
||||
<Note>
|
||||
It is possible to set up multiple peers on the same machine. Find out how!
|
||||
</Note>
|
||||
4. Ping machines and make sure that they are reachable.
|
||||
5. We might ask you to provide a generated [setup key](/overview/setup-keys) so that we could test your setup.
|
||||
|
||||
Please reach out to us with any questions. We believe you will have some! :)
|
||||
|
||||
In case this task seems to be difficult, no worries!
|
||||
You could skip #1 and #2 starting with #3 using managed version of NetBird (https://app.netbird.io).
|
||||
|
||||
### Project Ideas
|
||||
|
||||
There are a few categories that our ideas fall into:
|
||||
* **UI** - mostly around our Management service Dashboard ([Github repo](https://github.com/netbirdio/dashboard))
|
||||
* **Client** - our client application ([Github Repo](https://github.com/netbirdio/netbird/tree/main/client))
|
||||
* **Server** - our Management and Signal server applications ([Management Github Repo](https://github.com/netbirdio/netbird/tree/main/management) and [Signal Github Repo](https://github.com/netbirdio/netbird/tree/main/signal))
|
||||
* **Portability** - ability to run client and server applications on different platforms (e.g. OpenWRT)
|
||||
* **Infrastructure** - deployment, CI/CD, scaling, infrastructure as code improvements, etc.
|
||||
* **Integrations** - integration between the project and different platforms, like CI/CD, Kubernetes or specific cloud providers.
|
||||
|
||||
|
||||
### Idea: Make NetBird run on routers
|
||||
<Note>
|
||||
OpenWrt is an open-source project for embedded operating systems based on Linux, primarily used on embedded devices to route network traffic.
|
||||
|
||||
https://openwrt.org/
|
||||
</Note>
|
||||
|
||||
NetBird runs on different platforms and can also be compiled to run on routers. For that, we are thinking to work on this support for the popular wireless router OpenWRT.
|
||||
|
||||
We are already building, but we lack packaging and repository for distribution and this is the main goal of this task.
|
||||
|
||||
> **Category:** Portability/Infrastructure
|
||||
>
|
||||
> **Goal:** Create OpenWRT package (ipk) for one of the popular routers. Setup CI/CD ipk build flow with Github Actions.
|
||||
>
|
||||
> **Skills:** Golang, Linux, bash, OpenWRT, cmake
|
||||
>
|
||||
> **Project Size:** 175 hours
|
||||
>
|
||||
> **Rating:** easy
|
||||
>
|
||||
> **Possible mentor:** [Maycon Santos](https://github.com/mlsmaycon)
|
||||
>
|
||||
> **Reference:** https://medium.com/@theshemul/make-golang-program-for-router-39bf18529b18
|
||||
>
|
||||
> **Reference:** https://openwrt.org/docs/guide-developer/packages
|
||||
|
||||
### Idea: Software Firewall with eBPF
|
||||
<Note>
|
||||
eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in an operating system kernel.
|
||||
|
||||
https://ebpf.io/
|
||||
</Note>
|
||||
When running on machines NetBird client application tries to be as efficient as possible.
|
||||
On Linux machines NetBird can use kernel modules like Wireguard and features like eBPF to be the most efficient.
|
||||
|
||||
Besides connectivity, NetBird can control access. Right now it is achieved on a connection level -
|
||||
if a peer is in the allowed list, it can connect. If not, the connection is rejected.
|
||||
|
||||
It is more advantageous to work on a finer level - defining what packets to allow/block and on which ports.
|
||||
|
||||
For that eBPF can be integrated into the client application.
|
||||
The flow could be the following: client receives rules from the Management service and creates ePBF.
|
||||
|
||||
> **Category:** Client/Server
|
||||
>
|
||||
> **Goal:** MVP of a eBPF port filtering on client applications (Linux)
|
||||
>
|
||||
> **Skills:** Golang, Linux
|
||||
>
|
||||
> **Project Size:** 350 hours
|
||||
>
|
||||
> **Rating:** hard
|
||||
>
|
||||
> **Possible mentor:** [Mikhail Bragin](https://github.com/braginini)
|
||||
>
|
||||
> **Reference:** https://ebpf.io/
|
||||
|
||||
### Idea: NetBird client running in the browser
|
||||
|
||||
<Note>
|
||||
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine.
|
||||
Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client
|
||||
and server applications.
|
||||
</Note>
|
||||
|
||||
NetBird requires a client application installed to connect machines to each other.
|
||||
In some environments it might not be possible or forbidden to install applications.
|
||||
But there always might be an option to run a browser!
|
||||
|
||||
The idea of this project is to make NetBird run in the browser using WASM and WebRTC.
|
||||
A user would log in to the Management Dashboard and open a virtual terminal (e.g. https://xtermjs.org/).
|
||||
Browser will then establish a connection to the network and user would be able to access remote servers.
|
||||
|
||||
> **Category:** UI/Client/Server
|
||||
>
|
||||
> **Goal:** Interactive console running in the browser establishing connection to the NetBird network.
|
||||
>
|
||||
> **Skills:** Golang, Linux, Javascript
|
||||
>
|
||||
> **Project Size:** 350 hours
|
||||
>
|
||||
> **Rating:** hard
|
||||
>
|
||||
> **Possible mentor:** [Mikhail Bragin](https://github.com/braginini) / [Maycon Santos](https://github.com/mlsmaycon)
|
||||
>
|
||||
> **Reference:** https://webassembly.org/
|
||||
>
|
||||
> **Reference:** https://webrtc.org/
|
||||
>
|
||||
> **Reference:** https://xtermjs.org/
|
||||
|
||||
### Idea: Integrate NetBird with Kubernetes
|
||||
|
||||
<Note>
|
||||
Kubernetes, also known as K8s, is an open-source system for automating deployment, scaling, and management of containerized applications.
|
||||
</Note>
|
||||
|
||||
Having a private Kubernetes cluster is a best practices that come with some connectivity challenges,
|
||||
especially to operators and developers that need to access resources running within the cluster like services,
|
||||
pods and the Kubernetes API.
|
||||
|
||||
The idea of this project is to integrate NetBird with Kubernetes by creating a controller that would add an agent
|
||||
per cluster node and would handle the login process of the agent and store the states in form of Kubernetes secrets.
|
||||
As Kubernetes is a cloud-native platform, you can imagine that are some caveats that we will have to deal with, like nodes being removed and added all the time,
|
||||
different network controllers, and different cloud environments.
|
||||
|
||||
> **Category:** Client/Infrastructure/Integrations
|
||||
>
|
||||
> **Goal:** Integrate NetBird with Kubernetes for automatic provision of agents.
|
||||
>
|
||||
> **Skills:** Golang, Linux, Kubernetes API
|
||||
>
|
||||
> **Project Size:** 350 hours
|
||||
>
|
||||
> **Rating:** hard
|
||||
>
|
||||
> **Possible mentor:** [Maycon Santos](https://github.com/mlsmaycon), [Sang Woo Bae](https://github.com/shatoboar)
|
||||
>
|
||||
> **Reference:** https://kubernetes.io/
|
||||
>
|
||||
> **Reference:** https://kubernetes.io/docs/concepts/architecture/controller/
|
||||
>
|
||||
> **Reference:** https://kubernetes.io/blog/2021/06/21/writing-a-controller-for-pod-labels/
|
||||
|
||||
|
||||
### Idea: NetBird Github Action
|
||||
|
||||
<Note>
|
||||
GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.
|
||||
Make code reviews, branch management, and issue triaging work the way you want.
|
||||
</Note>
|
||||
|
||||
We love Github Actions, because of its primary concept, Actions. They allow us to automate complex workflows and provide a simple interface
|
||||
to be configured and executed before building or testing our code.
|
||||
|
||||
The idea of this project is to create a NetBird agent action, that would download and run an ephemeral agent on Github Actions workflows. The benefits of this
|
||||
could be great as it would allow many users that rely on the public runners to access private resources, in a secure and dynamic way.
|
||||
|
||||
> **Category:** Server/Client/Integrations
|
||||
>
|
||||
> **Goal:** Create a NetBird Github Action.
|
||||
>
|
||||
> **Skills:** Golang, Bash, Javascript
|
||||
>
|
||||
> **Project Size:** 175 hours
|
||||
>
|
||||
> **Rating:** easy
|
||||
>
|
||||
> **Possible mentor:** [Maycon Santos](https://github.com/mlsmaycon)
|
||||
>
|
||||
> **Reference:** https://docs.github.com/en/actions
|
||||
>
|
||||
> **Reference:** https://docs.netbird.io/getting-started/quickstart
|
||||
>
|
||||
> **Reference:** https://docs.github.com/en/actions/creating-actions/about-custom-actions
|
||||
|
||||
### Idea: Mobile Client APPs
|
||||
|
||||
Nowadays people are always connected with mobile phones and very often they need to access remote resources with them.
|
||||
|
||||
The idea of this project is to create a NetBird mobile app for either Android or iOS that would follow one of the principles of our product, simplicity.
|
||||
Initially we think to export our Go APIs using GoMobile that would help with a good chuck of the heavy lifting.
|
||||
|
||||
> **Category:** UI/Client/Portability
|
||||
>
|
||||
> **Goal:** Create a NetBird Mobile Client App.
|
||||
>
|
||||
> **Skills:** Golang, Java/Kotlin, XCode, Flutter+Dart
|
||||
>
|
||||
> **Project Size:** 350 hours
|
||||
>
|
||||
> **Rating:** hard
|
||||
>
|
||||
> **Possible mentor:** [Mikhail Bragin](https://github.com/braginini)
|
||||
>
|
||||
>
|
||||
> **Reference:** https://github.com/golang/mobile
|
||||
>
|
||||
> **Reference:** https://medium.com/stack-me-up/meet-the-coolest-tech-marriage-flutter-go-369cf11367c9
|
||||
|
||||
345
src/pages/docs/reference.mdx
Normal file
345
src/pages/docs/reference.mdx
Normal file
@@ -0,0 +1,345 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
## NetBird commands
|
||||
|
||||
The NetBird client installation adds a binary called `netbird` to your system. This binary runs as a daemon service to connect
|
||||
your computer or server to the NetBirt network as a peer. But it can also be used as a client to control the daemon service.
|
||||
|
||||
This section will explore the commands available in `netbird`.
|
||||
### Syntax
|
||||
Use the following syntax to run `netbird` commands from your terminal window:
|
||||
```shell
|
||||
netbird [command] [subcommand] [flags]
|
||||
```
|
||||
* `command`: Specifies the operation that you want to perform or a top-level command: `up`, `login`, `down`, `status`, `ssh`, `version`, and `service`
|
||||
* `subcommand`: Specifies the operation to be executed for a top-level command like `service`: `install`, `uninstall`, `start`, and `stop`
|
||||
* `flags`: Specifies optional flags. For example, you can use the `--setup-key` flag to specify the setup key to be used in the commands `login` and `up`
|
||||
|
||||
:::info Help
|
||||
To see detailed command information, use the flag `--help` after each command
|
||||
:::
|
||||
|
||||
### Global flags
|
||||
`netbird` has a set of global flags that are available in every command. They specify settings that are core or shared between two or more commands, e.g. `--setup-key` is used by `login` and `up` to authenticate the client against a management service.
|
||||
|
||||
Below is the list of global flags:
|
||||
```shell
|
||||
--admin-url string Admin Panel URL [http|https]://[host]:[port] (default "https://app.netbird.io")
|
||||
-c, --config string Netbird config file location (default "/etc/netbird/config.json")
|
||||
--daemon-addr string Daemon service address to serve CLI requests [unix|tcp]://[path|host:port] (default "unix:///var/run/netbird.sock")
|
||||
--log-file string sets Netbird log path. If console is specified the the log will be output to stdout (default "/var/log/netbird/client.log")
|
||||
-l, --log-level string sets Netbird log level (default "info")
|
||||
-m, --management-url string Management Service URL [http|https]://[host]:[port] (default "https://api.wiretrustee.com:443")
|
||||
-p, --preshared-key string Sets Wireguard PreSharedKey property. If set, then only peers that have the same key can communicate.
|
||||
-k, --setup-key string Setup key obtained from the Management Service Dashboard (used to register peer)
|
||||
|
||||
```
|
||||
### Environment Variables
|
||||
Every flag of a `netbird` command can be passed as an environment variable. We are using the following rule for the environment variables composition:
|
||||
* `PREFIX_FLAGNAME` and for flags with multiple parts: `PREFIX_FLAGNAMEPART1_FLAGNAMEPART2`
|
||||
* The prefix is always **NB**
|
||||
* The flag parts are separated by a dash ("-") when passing as flags and with an underscore ("_") when passing as an environment variable
|
||||
|
||||
For example, let's check how we can pass `--config` and `--management-url` as environment variables:
|
||||
```shell
|
||||
export NB_CONFIG="/opt/netbird/config.json"
|
||||
export NB_MANAGEMENT_URL="https://api.self-hosted.com:33073"
|
||||
netbird up
|
||||
```
|
||||
The `up` command would process the variables, read the configuration file on `/opt/netbird/config.json` and attempt to connect to the management service running at `https://api.self-hosted.com:33073`.
|
||||
|
||||
### Commands
|
||||
#### up
|
||||
Single command to log in and start the NetBird client. It can send a signal to the daemon service or run in the foreground with the flag `--foreground-mode`.
|
||||
|
||||
The command will check if the peer is logged in and connect to the management service. If the peer is not logged in, by default, it will attempt to initiate an SSO login flow.
|
||||
##### Flags
|
||||
```shell
|
||||
--dns-resolver-address string Sets a custom address for NetBird's local DNS resolver. If set, the agent won't attempt to discover the best ip and port to listen on. An empty string "" clears the previous configuration. E.g. --dns-resolver-address 127.0.0.1:5053 or --dns-resolver-address ""
|
||||
--external-ip-map strings Sets external IPs maps between local addresses and interfaces.You can specify a comma-separated list with a single IP and IP/IP or IP/Interface Name. An empty string "" clears the previous configuration. E.g. --external-ip-map 12.34.56.78/10.0.0.1 or --external-ip-map 12.34.56.200,12.34.56.78/10.0.0.1,12.34.56.80/eth1 or --external-ip-map ""
|
||||
-F, --foreground-mode start service in foreground
|
||||
|
||||
```
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
netbird up
|
||||
```
|
||||
If you are running on a self-hosted environment, you can pass your management url by running the following:
|
||||
```shell
|
||||
netbird up --management-url https://api.self-hosted.com:33073
|
||||
```
|
||||
if you want to run in the foreground, you can use "console" as the value for `--log-file` and run the command with sudo:
|
||||
```shell
|
||||
sudo netbird up --log-file console
|
||||
```
|
||||
:::info
|
||||
On Windows, you may need to run the command from an elevated terminal session.
|
||||
:::
|
||||
In case you need to use a setup key, use the `--setup-key` flag :
|
||||
```shell
|
||||
netbird up --setup-key AAAA-BBB-CCC-DDDDDD
|
||||
```
|
||||
|
||||
#### login
|
||||
Command to authenticate the NetBird client to a management service. If the peer is not logged in, by default, it will attempt to initiate an SSO login flow.
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
netbird login
|
||||
```
|
||||
If you are running on a self-hosted environment, you can pass your management url by running the following:
|
||||
```shell
|
||||
netbird login --management-url https://api.self-hosted.com:33073
|
||||
```
|
||||
In case you need to use a setup key, use the `--setup-key` flag:
|
||||
```shell
|
||||
netbird login --setup-key AAAA-BBB-CCC-DDDDDD
|
||||
```
|
||||
Passing a management url and a setup key:
|
||||
```shell
|
||||
netbird login --setup-key AAAA-BBB-CCC-DDDDDD --management-url https://api.self-hosted.com:33073
|
||||
```
|
||||
|
||||
#### down
|
||||
Command to stop a connection with the management service and other peers in a NetBird network. After running this command, the daemon service will enter an `Idle` state.
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
netbird down
|
||||
```
|
||||
|
||||
#### status
|
||||
Retrieves the peer status from the daemon service.
|
||||
##### Flags
|
||||
```shell
|
||||
-d, --detail display detailed status information
|
||||
--filter-by-ips strings filters the detailed output by a list of one or more IPs, e.g. --filter-by-ips 100.64.0.100,100.64.0.200
|
||||
--filter-by-status string filters the detailed output by connection status(connected|disconnected), e.g. --filter-by-status connected
|
||||
```
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
netbird status
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
Daemon status: Connected
|
||||
Management: Connected
|
||||
Signal: Connected
|
||||
NetBird IP: 100.119.62.6/16
|
||||
Interface type: Kernel
|
||||
Peers count: 2/3 Connected
|
||||
```
|
||||
|
||||
If you want to see more details about the peer connections, you can use the `--detail` or `-d` flag:
|
||||
```shell
|
||||
netbird status -d
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
Peers detail:
|
||||
Peer:
|
||||
NetBird IP: 100.119.85.4
|
||||
Public key: 2lI3F+fDUWh58g5oRN+y7lPHpNcEVWhiDv/wr1/jiF8=
|
||||
Status: Disconnected
|
||||
-- detail --
|
||||
Connection type: -
|
||||
Direct: false
|
||||
ICE candidate (Local/Remote): -/-
|
||||
Last connection update: 2022-07-07 12:21:31
|
||||
|
||||
Peer:
|
||||
NetBird IP: 100.119.201.225
|
||||
Public key: +jkH8cs/Fo83qdB6dWG16+kAQmGTKYoBYSAdLtSOV10=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:32
|
||||
|
||||
Peer:
|
||||
NetBird IP: 100.119.230.104
|
||||
Public key: R7olj0S8jiYMLfOWK+wDto+j3pE4vR54tLGrEQKgBSw=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:33
|
||||
|
||||
Daemon status: Connected
|
||||
Management: Connected to https://api.netbird.io:33073
|
||||
Signal: Connected to https://signal2.wiretrustee.com:10000
|
||||
NetBird IP: 100.119.62.6/16
|
||||
Interface type: Kernel
|
||||
Peers count: 2/3 Connected
|
||||
```
|
||||
To filter the peers' output by connection status, you can use the `--filter-by-status` flag with either "connected" or "disconnected" as value:
|
||||
```shell
|
||||
netbird status -d --filter-by-status connected
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
Peers detail:
|
||||
Peer:
|
||||
NetBird IP: 100.119.201.225
|
||||
Public key: +jkH8cs/Fo83qdB6dWG16+kAQmGTKYoBYSAdLtSOV10=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:32
|
||||
|
||||
Peer:
|
||||
NetBird IP: 100.119.230.104
|
||||
Public key: R7olj0S8jiYMLfOWK+wDto+j3pE4vR54tLGrEQKgBSw=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:33
|
||||
|
||||
Daemon status: Connected
|
||||
Management: Connected to https://api.netbird.io:33073
|
||||
Signal: Connected to https://signal2.wiretrustee.com:10000
|
||||
NetBird IP: 100.119.62.6/16
|
||||
Interface type: Kernel
|
||||
Peers count: 2/3 Connected
|
||||
```
|
||||
To filter the peers' output by peer IP addresses, you can use the `--filter-by-ips` flag with one or more IPs separated by a comma as a value:
|
||||
```shell
|
||||
netbird status -d --filter-by-ips 100.119.201.225
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
Peers detail:
|
||||
Peer:
|
||||
NetBird IP: 100.119.201.225
|
||||
Public key: +jkH8cs/Fo83qdB6dWG16+kAQmGTKYoBYSAdLtSOV10=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:32
|
||||
|
||||
Daemon status: Connected
|
||||
Management: Connected to https://api.netbird.io:33073
|
||||
Signal: Connected to https://signal2.wiretrustee.com:10000
|
||||
NetBird IP: 100.119.62.6/16
|
||||
Interface type: Kernel
|
||||
Peers count: 2/3 Connected
|
||||
```
|
||||
You can combine both filters and get the peers that are both connected and with specific IPs:
|
||||
```shell
|
||||
netbird status -d --filter-by-status connected --filter-by-ips 100.119.85.4,100.119.230.104
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
Peers detail:
|
||||
|
||||
Peer:
|
||||
NetBird IP: 100.119.230.104
|
||||
Public key: R7olj0S8jiYMLfOWK+wDto+j3pE4vR54tLGrEQKgBSw=
|
||||
Status: Connected
|
||||
-- detail --
|
||||
Connection type: P2P
|
||||
Direct: true
|
||||
ICE candidate (Local/Remote): host/host
|
||||
Last connection update: 2022-07-07 12:21:33
|
||||
|
||||
Daemon status: Connected
|
||||
Management: Connected to https://api.netbird.io:33073
|
||||
Signal: Connected to https://signal2.wiretrustee.com:10000
|
||||
NetBird IP: 100.119.62.6/16
|
||||
Interface type: Kernel
|
||||
Peers count: 2/3 Connected
|
||||
```
|
||||
<Note>
|
||||
The peer with IP `100.119.85.4` wasn't returned because it was not connected
|
||||
</Note>
|
||||
|
||||
#### ssh
|
||||
Command to connect using ssh to a remote peer in your NetBird network.
|
||||
|
||||
You should run the ssh command with elevated permissions.
|
||||
##### Flags
|
||||
```shell
|
||||
-p, --port int Sets remote SSH port. Defaults to 44338 (default 44338)
|
||||
```
|
||||
##### Arguments
|
||||
The ssh command accepts one argument, `user@host`; this argument indicates the remote host to connect:
|
||||
* `user`: indicates the remote user to login
|
||||
* `host`: indicates the remote peer host IP address
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
sudo netbird ssh user@100.119.230.104
|
||||
```
|
||||
If you the remote peer agent is running the ssh service on a different port, you can use the `--port` or `-p` flag:
|
||||
```shell
|
||||
sudo netbird ssh -p 3434 user@100.119.230.104
|
||||
```
|
||||
|
||||
#### version
|
||||
Outputs the `netbird` command version.
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
netbird version
|
||||
```
|
||||
This will output:
|
||||
```shell
|
||||
0.8.2
|
||||
```
|
||||
|
||||
#### service
|
||||
The service command is a top-level command with subcommands to perform operations related to the daemon service.
|
||||
|
||||
You should run the service command with elevated permissions.
|
||||
|
||||
#### service install
|
||||
The install installs the daemon service on the system.
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
sudo netbird service install
|
||||
```
|
||||
You can use the global flags to configure the daemon service. For instance, you can set a debug log level with the flag `--log-level`
|
||||
```shell
|
||||
sudo netbird service install --log-level debug
|
||||
```
|
||||
You can set a custom configuration path with the flag `--config`
|
||||
```shell
|
||||
sudo netbird service install --config /opt/netbird/config.json
|
||||
```
|
||||
|
||||
#### service uninstall
|
||||
The uninstall uninstalls the daemon service from the system.
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
sudo netbird service uninstall
|
||||
```
|
||||
|
||||
#### service start
|
||||
Starts the daemon service
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
sudo netbird service start
|
||||
```
|
||||
|
||||
#### service stop
|
||||
Stops the daemon service
|
||||
##### Usage
|
||||
The minimal form of running the command is:
|
||||
```shell
|
||||
sudo netbird service stop
|
||||
```
|
||||
39
src/pages/docs/why-wireguard-with-netbird.mdx
Normal file
39
src/pages/docs/why-wireguard-with-netbird.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
import {HeroPattern} from "@/components/HeroPattern";
|
||||
|
||||
<HeroPattern />
|
||||
|
||||
# Why Wireguard with NetBird?
|
||||
|
||||
WireGuard is a modern and extremely fast VPN tunnel utilizing state-of-the-art [cryptography](https://www.wireguard.com/protocol/)
|
||||
and NetBird uses Wireguard to establish a secure tunnel between machines.
|
||||
|
||||
Built with simplicity in mind, Wireguard ensures that traffic between two machines is encrypted and flowing, however, it requires a few things to be done beforehand.
|
||||
|
||||
First, in order to connect, the machines have to be configured.
|
||||
On each machine, you need to generate private and public keys and prepare a WireGuard configuration file.
|
||||
The configuration also includes a private IP address that should be unique per machine.
|
||||
|
||||
Secondly, to accept the incoming traffic, the machines have to trust each other.
|
||||
The generated public keys have to be pre-shared on the machines.
|
||||
This works similarly to SSH with its authorised_keys file.
|
||||
|
||||
Lastly, the connectivity between the machines has to be ensured.
|
||||
To make machines reach one another, you are required to set a WireGuard endpoint property which indicates the IP address and port of the remote machine to connect to.
|
||||
On many occasions, machines are hidden behind firewalls and NAT devices,
|
||||
meaning that you may need to configure a port forwarding or open holes in your firewall to ensure the machines are reachable.
|
||||
|
||||
The undertakings mentioned above might not be complicated if you have just a few machines, but the complexity grows as the number of machines increases.
|
||||
|
||||
NetBird simplifies the setup by automatically generating private and public keys, assigning unique private IP addresses, and takes care of sharing public keys between the machines.
|
||||
It is worth mentioning that the private key never leaves the machine.
|
||||
So only the machine that owns the key can decrypt traffic addressed to it.
|
||||
The same applies also to the relayed traffic mentioned below.
|
||||
|
||||
Furthermore, NetBird ensures connectivity by leveraging advanced [NAT traversal techniques](https://en.wikipedia.org/wiki/NAT_traversal)
|
||||
and removing the necessity of port forwarding, opening holes in the firewall, and having a public static IP address.
|
||||
In cases when a direct peer-to-peer connection isn't possible, all traffic is relayed securely between peers.
|
||||
NetBird also monitors the connection health and restarts broken connections.
|
||||
|
||||
There are a few more things that we are working on to make secure private networks simple. A few examples are ACLs, MFA and activity monitoring.
|
||||
|
||||
Check out the WireGuard [Quick Start](https://www.wireguard.com/quickstart/) guide to learn more about configuring "plain" WireGuard without NetBird.
|
||||
74
src/pages/events.mdx
Normal file
74
src/pages/events.mdx
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Events {{ tag: 'GET' , label: '/api/events' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all events
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/events">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/events \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"timestamp": "string",
|
||||
"activity": "string",
|
||||
"activity_code": "string",
|
||||
"initiator_id": "string",
|
||||
"target_id": "string",
|
||||
"meta": "object"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
418
src/pages/groups.mdx
Normal file
418
src/pages/groups.mdx
Normal file
@@ -0,0 +1,418 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Groups {{ tag: 'GET' , label: '/api/groups' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all Groups
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/groups">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/groups \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer",
|
||||
"peers": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Group {{ tag: 'POST' , label: '/api/groups' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Group
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Group name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="peers" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of peers ids
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/groups">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/groups \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"peers": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer",
|
||||
"peers": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Group {{ tag: 'GET' , label: '/api/groups/{groupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Group
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="groupId" type="string" required={true}>
|
||||
The unique identifier of a group
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/groups/{groupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/groups/{groupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer",
|
||||
"peers": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Group {{ tag: 'PUT' , label: '/api/groups/{groupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update/Replace a Group
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="groupId" type="string" required={true}>
|
||||
The unique identifier of a group
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Group name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="peers" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of peers ids
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/groups/{groupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/groups/{groupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"peers": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer",
|
||||
"peers": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Group {{ tag: 'DELETE' , label: '/api/groups/{groupId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a Group
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="groupId" type="string" required={true}>
|
||||
The unique identifier of a group
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/groups/{groupId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/groups/{groupId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
367
src/pages/peers.mdx
Normal file
367
src/pages/peers.mdx
Normal file
@@ -0,0 +1,367 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Peers {{ tag: 'GET' , label: '/api/peers' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all peers
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/peers">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/peers \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"connected": "boolean",
|
||||
"last_seen": "string",
|
||||
"os": "string",
|
||||
"version": "string",
|
||||
"groups": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"ssh_enabled": "boolean",
|
||||
"user_id": "string",
|
||||
"hostname": "string",
|
||||
"ui_version": "string",
|
||||
"dns_label": "string",
|
||||
"login_expiration_enabled": "boolean",
|
||||
"login_expired": "boolean",
|
||||
"last_login": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Peer {{ tag: 'GET' , label: '/api/peers/{peerId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a peer
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="peerId" type="string" required={true}>
|
||||
The unique identifier of a peer
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/peers/{peerId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/peers/{peerId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"connected": "boolean",
|
||||
"last_seen": "string",
|
||||
"os": "string",
|
||||
"version": "string",
|
||||
"groups": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"ssh_enabled": "boolean",
|
||||
"user_id": "string",
|
||||
"hostname": "string",
|
||||
"ui_version": "string",
|
||||
"dns_label": "string",
|
||||
"login_expiration_enabled": "boolean",
|
||||
"login_expired": "boolean",
|
||||
"last_login": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Peer {{ tag: 'PUT' , label: '/api/peers/{peerId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update information about a peer
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="peerId" type="string" required={true}>
|
||||
The unique identifier of a peer
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
|
||||
</Property>
|
||||
|
||||
<Property name="ssh_enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
|
||||
</Property>
|
||||
|
||||
<Property name="login_expiration_enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/peers/{peerId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/peers/{peerId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"ssh_enabled": "boolean",
|
||||
"login_expiration_enabled": "boolean"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"ip": "string",
|
||||
"connected": "boolean",
|
||||
"last_seen": "string",
|
||||
"os": "string",
|
||||
"version": "string",
|
||||
"groups": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"ssh_enabled": "boolean",
|
||||
"user_id": "string",
|
||||
"hostname": "string",
|
||||
"ui_version": "string",
|
||||
"dns_label": "string",
|
||||
"login_expiration_enabled": "boolean",
|
||||
"login_expired": "boolean",
|
||||
"last_login": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Peer {{ tag: 'DELETE' , label: '/api/peers/{peerId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a peer
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="peerId" type="string" required={true}>
|
||||
The unique identifier of a peer
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/peers/{peerId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/peers/{peerId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
578
src/pages/policies.mdx
Normal file
578
src/pages/policies.mdx
Normal file
@@ -0,0 +1,578 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Policies {{ tag: 'GET' , label: '/api/policies' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all Policies
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/policies">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/policies \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
],
|
||||
"id": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Policy {{ tag: 'POST' , label: '/api/policies' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Policy
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy friendly description
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy status
|
||||
</Property>
|
||||
|
||||
<Property name="query" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy Rego query
|
||||
</Property>
|
||||
|
||||
<Property name="rules" type="PolicyRule[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy rule object for policy UI editor
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/policies">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/policies \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
],
|
||||
"id": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Policy {{ tag: 'GET' , label: '/api/policies/{policyId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Policies
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="policyId" type="string" required={true}>
|
||||
The unique identifier of a policy
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/policies/{policyId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/policies/{policyId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
],
|
||||
"id": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Policy {{ tag: 'PUT' , label: '/api/policies/{policyId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update/Replace a Policy
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="policyId" type="string" required={true}>
|
||||
The unique identifier of a policy
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy friendly description
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy status
|
||||
</Property>
|
||||
|
||||
<Property name="query" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy Rego query
|
||||
</Property>
|
||||
|
||||
<Property name="rules" type="PolicyRule[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Policy rule object for policy UI editor
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/policies/{policyId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/policies/{policyId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"query": "string",
|
||||
"rules": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"enabled": "boolean",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"action": "string"
|
||||
}
|
||||
],
|
||||
"id": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Policy {{ tag: 'DELETE' , label: '/api/policies/{policyId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a Policy
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="policyId" type="string" required={true}>
|
||||
The unique identifier of a policy
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/policies/{policyId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/policies/{policyId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
542
src/pages/routes.mdx
Normal file
542
src/pages/routes.mdx
Normal file
@@ -0,0 +1,542 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Routes {{ tag: 'GET' , label: '/api/routes' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all routes
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/routes">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/routes \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"network_type": "string",
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Route {{ tag: 'POST' , label: '/api/routes' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Route
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route description
|
||||
</Property>
|
||||
|
||||
<Property name="network_id" type="string" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={40}
|
||||
>
|
||||
Route network identifier, to group HA routes
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route status
|
||||
</Property>
|
||||
|
||||
<Property name="peer" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Peer Identifier associated with route
|
||||
</Property>
|
||||
|
||||
<Property name="network" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Network range in CIDR format
|
||||
</Property>
|
||||
|
||||
<Property name="metric" type="integer" required={true}
|
||||
|
||||
|
||||
min={1}
|
||||
|
||||
|
||||
max={9999}
|
||||
>
|
||||
Route metric number. Lowest number has higher priority
|
||||
</Property>
|
||||
|
||||
<Property name="masquerade" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Indicate if peer should masquerade traffic to this route's prefix
|
||||
</Property>
|
||||
|
||||
<Property name="groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route group tag groups
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/routes">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/routes \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"network_type": "string",
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Route {{ tag: 'GET' , label: '/api/routes/{routeId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Routes
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="routeId" type="string" required={true}>
|
||||
The unique identifier of a route
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/routes/{routeId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/routes/{routeId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"network_type": "string",
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Route {{ tag: 'PUT' , label: '/api/routes/{routeId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update/Replace a Route
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="routeId" type="string" required={true}>
|
||||
The unique identifier of a route
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route description
|
||||
</Property>
|
||||
|
||||
<Property name="network_id" type="string" required={true}
|
||||
|
||||
|
||||
|
||||
minLen={1}
|
||||
|
||||
maxLen={40}
|
||||
>
|
||||
Route network identifier, to group HA routes
|
||||
</Property>
|
||||
|
||||
<Property name="enabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route status
|
||||
</Property>
|
||||
|
||||
<Property name="peer" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Peer Identifier associated with route
|
||||
</Property>
|
||||
|
||||
<Property name="network" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Network range in CIDR format
|
||||
</Property>
|
||||
|
||||
<Property name="metric" type="integer" required={true}
|
||||
|
||||
|
||||
min={1}
|
||||
|
||||
|
||||
max={9999}
|
||||
>
|
||||
Route metric number. Lowest number has higher priority
|
||||
</Property>
|
||||
|
||||
<Property name="masquerade" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Indicate if peer should masquerade traffic to this route's prefix
|
||||
</Property>
|
||||
|
||||
<Property name="groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Route group tag groups
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/routes/{routeId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/routes/{routeId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"network_type": "string",
|
||||
"description": "string",
|
||||
"network_id": "string",
|
||||
"enabled": "boolean",
|
||||
"peer": "string",
|
||||
"network": "string",
|
||||
"metric": "integer",
|
||||
"masquerade": "boolean",
|
||||
"groups": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Route {{ tag: 'DELETE' , label: '/api/routes/{routeId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a Route
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="routeId" type="string" required={true}>
|
||||
The unique identifier of a route
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/routes/{routeId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/routes/{routeId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
522
src/pages/rules.mdx
Normal file
522
src/pages/rules.mdx
Normal file
@@ -0,0 +1,522 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Rules {{ tag: 'GET' , label: '/api/rules' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all Rules
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/rules">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/rules \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Rule {{ tag: 'POST' , label: '/api/rules' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Rule
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule friendly description
|
||||
</Property>
|
||||
|
||||
<Property name="disabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rules status
|
||||
</Property>
|
||||
|
||||
<Property name="flow" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
</Property>
|
||||
|
||||
<Property name="sources" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of source groups
|
||||
</Property>
|
||||
|
||||
<Property name="destinations" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of destination groups
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/rules">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/rules \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
"string"
|
||||
],
|
||||
"destinations": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Rule {{ tag: 'GET' , label: '/api/rules/{ruleId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Rules
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="ruleId" type="string" required={true}>
|
||||
The unique identifier of a rule
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/rules/{ruleId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/rules/{ruleId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Rule {{ tag: 'PUT' , label: '/api/rules/{ruleId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update/Replace a Rule
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="ruleId" type="string" required={true}>
|
||||
The unique identifier of a rule
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule name identifier
|
||||
</Property>
|
||||
|
||||
<Property name="description" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule friendly description
|
||||
</Property>
|
||||
|
||||
<Property name="disabled" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rules status
|
||||
</Property>
|
||||
|
||||
<Property name="flow" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
</Property>
|
||||
|
||||
<Property name="sources" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of source groups
|
||||
</Property>
|
||||
|
||||
<Property name="destinations" type="string[]" required={false}
|
||||
|
||||
|
||||
>
|
||||
List of destination groups
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/rules/{ruleId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/rules/{ruleId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
"string"
|
||||
],
|
||||
"destinations": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"disabled": "boolean",
|
||||
"flow": "string",
|
||||
"sources": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
],
|
||||
"destinations": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"peers_count": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Rule {{ tag: 'DELETE' , label: '/api/rules/{ruleId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a Rule
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="ruleId" type="string" required={true}>
|
||||
The unique identifier of a rule
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/rules/{ruleId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/rules/{ruleId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
444
src/pages/setup-keys.mdx
Normal file
444
src/pages/setup-keys.mdx
Normal file
@@ -0,0 +1,444 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Setup Keys {{ tag: 'GET' , label: '/api/setup-keys' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all Setup Keys
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/setup-keys">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/setup-keys \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"key": "string",
|
||||
"name": "string",
|
||||
"expires": "string",
|
||||
"type": "string",
|
||||
"valid": "boolean",
|
||||
"revoked": "boolean",
|
||||
"used_times": "integer",
|
||||
"last_used": "string",
|
||||
"state": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"updated_at": "string",
|
||||
"usage_limit": "integer"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Setup Key {{ tag: 'POST' , label: '/api/setup-keys' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a Setup Key
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup Key name
|
||||
</Property>
|
||||
|
||||
<Property name="type" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key type, one-off for single time usage and reusable
|
||||
</Property>
|
||||
|
||||
<Property name="expires_in" type="integer" required={true}
|
||||
|
||||
|
||||
>
|
||||
Expiration time in seconds
|
||||
</Property>
|
||||
|
||||
<Property name="revoked" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key revocation status
|
||||
</Property>
|
||||
|
||||
<Property name="auto_groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key groups to auto-assign to peers registered with this key
|
||||
</Property>
|
||||
|
||||
<Property name="usage_limit" type="integer" required={true}
|
||||
|
||||
|
||||
>
|
||||
A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/setup-keys">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/setup-keys \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"type": "string",
|
||||
"expires_in": "integer",
|
||||
"revoked": "boolean",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"usage_limit": "integer"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"key": "string",
|
||||
"name": "string",
|
||||
"expires": "string",
|
||||
"type": "string",
|
||||
"valid": "boolean",
|
||||
"revoked": "boolean",
|
||||
"used_times": "integer",
|
||||
"last_used": "string",
|
||||
"state": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"updated_at": "string",
|
||||
"usage_limit": "integer"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Setup Key {{ tag: 'GET' , label: '/api/setup-keys/{keyId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Get information about a Setup Key
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="keyId" type="string" required={true}>
|
||||
The unique identifier of a setup key
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/setup-keys/{keyId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/setup-keys/{keyId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"key": "string",
|
||||
"name": "string",
|
||||
"expires": "string",
|
||||
"type": "string",
|
||||
"valid": "boolean",
|
||||
"revoked": "boolean",
|
||||
"used_times": "integer",
|
||||
"last_used": "string",
|
||||
"state": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"updated_at": "string",
|
||||
"usage_limit": "integer"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a Setup Key {{ tag: 'PUT' , label: '/api/setup-keys/{keyId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update information about a Setup Key
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="keyId" type="string" required={true}>
|
||||
The unique identifier of a setup key
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup Key name
|
||||
</Property>
|
||||
|
||||
<Property name="type" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key type, one-off for single time usage and reusable
|
||||
</Property>
|
||||
|
||||
<Property name="expires_in" type="integer" required={true}
|
||||
|
||||
|
||||
>
|
||||
Expiration time in seconds
|
||||
</Property>
|
||||
|
||||
<Property name="revoked" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key revocation status
|
||||
</Property>
|
||||
|
||||
<Property name="auto_groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Setup key groups to auto-assign to peers registered with this key
|
||||
</Property>
|
||||
|
||||
<Property name="usage_limit" type="integer" required={true}
|
||||
|
||||
|
||||
>
|
||||
A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/setup-keys/{keyId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/setup-keys/{keyId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"type": "string",
|
||||
"expires_in": "integer",
|
||||
"revoked": "boolean",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"usage_limit": "integer"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"key": "string",
|
||||
"name": "string",
|
||||
"expires": "string",
|
||||
"type": "string",
|
||||
"valid": "boolean",
|
||||
"revoked": "boolean",
|
||||
"used_times": "integer",
|
||||
"last_used": "string",
|
||||
"state": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"updated_at": "string",
|
||||
"usage_limit": "integer"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
334
src/pages/tokens.mdx
Normal file
334
src/pages/tokens.mdx
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## List all Tokens {{ tag: 'GET' , label: '/api/users/{userId}/tokens' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all tokens for a user
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/users/{userId}/tokens">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/users/{userId}/tokens \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"expiration_date": "string",
|
||||
"created_by": "string",
|
||||
"created_at": "string",
|
||||
"last_used": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a Token {{ tag: 'POST' , label: '/api/users/{userId}/tokens' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Create a new token for a user
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="name" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
Name of the token
|
||||
</Property>
|
||||
|
||||
<Property name="expires_in" type="integer" required={true}
|
||||
|
||||
|
||||
min={1}
|
||||
|
||||
|
||||
max={365}
|
||||
>
|
||||
Expiration in days
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/users/{userId}/tokens">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/users/{userId}/tokens \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "string",
|
||||
"expires_in": "integer"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"plain_token": "string",
|
||||
"personal_access_token": {
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"expiration_date": "string",
|
||||
"created_by": "string",
|
||||
"created_at": "string",
|
||||
"last_used": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Retrieve a Token {{ tag: 'GET' , label: '/api/users/{userId}/tokens/{tokenId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a specific token for a user
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
|
||||
<Property name="tokenId" type="string" required={true}>
|
||||
The unique identifier of a token
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/users/{userId}/tokens/{tokenId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/users/{userId}/tokens/{tokenId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"expiration_date": "string",
|
||||
"created_by": "string",
|
||||
"created_at": "string",
|
||||
"last_used": "string"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a Token {{ tag: 'DELETE' , label: '/api/users/{userId}/tokens/{tokenId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a token for a user
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
|
||||
<Property name="tokenId" type="string" required={true}>
|
||||
The unique identifier of a token
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/users/{userId}/tokens/{tokenId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/users/{userId}/tokens/{tokenId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
373
src/pages/users.mdx
Normal file
373
src/pages/users.mdx
Normal file
@@ -0,0 +1,373 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
## Retrieve Users {{ tag: 'GET' , label: '/api/users' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Returns a list of all users
|
||||
|
||||
#### Query Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="service_user" type="boolean" required={false}>
|
||||
Filters users and returns either regular users or service users
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="GET" label="/api/users">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET https://api.netbird.io/api/users \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
[
|
||||
{
|
||||
"id": "string",
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"role": "string",
|
||||
"status": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"is_current": "boolean",
|
||||
"is_service_user": "boolean"
|
||||
}
|
||||
]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Create a User {{ tag: 'POST' , label: '/api/users' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Creates a new service user or sends an invite to a regular user
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="email" type="string" required={false}
|
||||
|
||||
|
||||
>
|
||||
User's Email to send invite to
|
||||
</Property>
|
||||
|
||||
<Property name="name" type="string" required={false}
|
||||
|
||||
|
||||
>
|
||||
User's full name
|
||||
</Property>
|
||||
|
||||
<Property name="role" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
User's NetBird account role
|
||||
</Property>
|
||||
|
||||
<Property name="auto_groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Groups to auto-assign to peers registered by this user
|
||||
</Property>
|
||||
|
||||
<Property name="is_service_user" type="boolean" required={true}
|
||||
|
||||
|
||||
>
|
||||
Is true if this user is a service user
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="POST" label="/api/users">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST https://api.netbird.io/api/users \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"role": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"is_service_user": "boolean"
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"role": "string",
|
||||
"status": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"is_current": "boolean",
|
||||
"is_service_user": "boolean"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Update a User {{ tag: 'PUT' , label: '/api/users/{userId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Update information about a User
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
#### Request-Body Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="role" type="string" required={true}
|
||||
|
||||
|
||||
>
|
||||
User's NetBird account role
|
||||
</Property>
|
||||
|
||||
<Property name="auto_groups" type="string[]" required={true}
|
||||
|
||||
|
||||
>
|
||||
Groups to auto-assign to peers registered by this user
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="PUT" label="/api/users/{userId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X PUT https://api.netbird.io/api/users/{userId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"role": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: '200' }}
|
||||
{
|
||||
"id": "string",
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"role": "string",
|
||||
"status": "string",
|
||||
"auto_groups": [
|
||||
"string"
|
||||
],
|
||||
"is_current": "boolean",
|
||||
"is_service_user": "boolean"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Delete a User {{ tag: 'DELETE' , label: '/api/users/{userId}' }}
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
Delete a User
|
||||
|
||||
#### Path Parameters
|
||||
<Properties>
|
||||
|
||||
<Property name="userId" type="string" required={true}>
|
||||
The unique identifier of a user
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
||||
<Col sticky>
|
||||
<CodeGroup title="Request" tag="DELETE" label="/api/users/{userId}">
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X DELETE https://api.netbird.io/api/users/{userId} \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
import ApiClient from '@example/protocol-api'
|
||||
|
||||
const client = new ApiClient(token)
|
||||
|
||||
await client.contacts.update('WAz8eIbvDR60rouK', {
|
||||
display_name: 'UncleFrank',
|
||||
})
|
||||
```
|
||||
|
||||
```python
|
||||
from protocol_api import ApiClient
|
||||
|
||||
client = ApiClient(token)
|
||||
|
||||
client.contacts.update("WAz8eIbvDR60rouK", display_name="UncleFrank")
|
||||
```
|
||||
|
||||
```php
|
||||
$client = new \Protocol\ApiClient($token);
|
||||
|
||||
$client->contacts->update('WAz8eIbvDR60rouK', [
|
||||
'display_name' => 'UncleFrank',
|
||||
]);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
35
src/styles/tailwind.css
Normal file
35
src/styles/tailwind.css
Normal file
@@ -0,0 +1,35 @@
|
||||
:root {
|
||||
--shiki-color-text: theme('colors.white');
|
||||
--shiki-token-constant: theme('colors.orange.300');
|
||||
/*--shiki-token-constant: theme('colors.emerald.300');*/
|
||||
--shiki-token-string: theme('colors.orange.300');
|
||||
/*--shiki-token-string: theme('colors.emerald.300');*/
|
||||
--shiki-token-comment: theme('colors.zinc.500');
|
||||
--shiki-token-keyword: theme('colors.sky.300');
|
||||
--shiki-token-parameter: theme('colors.pink.300');
|
||||
--shiki-token-function: theme('colors.violet.300');
|
||||
--shiki-token-string-expression: theme('colors.orange.300');
|
||||
/*--shiki-token-string-expression: theme('colors.emerald.300');*/
|
||||
--shiki-token-punctuation: theme('colors.zinc.200');
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.videowrapper {
|
||||
float: none;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 25px;
|
||||
height: 0;
|
||||
}
|
||||
.videowrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
Reference in New Issue
Block a user